npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vite-plugin-virtual-mpa-v2

v1.0.9

Published

[![npm version](https://img.shields.io/npm/v/vite-plugin-virtual-mpa-v2)](https://npmjs.com/package/vite-plugin-virtual-mpa-v2) ![license](https://img.shields.io/npm/l/vite-plugin-virtual-mpa-v2) [![install size](https://packagephobia.com/badge?p=vite-plu

Readme

vite-plugin-virtual-mpa-v2

npm version license install size

Vite 多页应用(MPA)插件,支持虚拟文件和 EJS 模板引擎,兼容 Vite 8.0 (Rolldown) 和旧版本 (Rollup)

特性

  • 兼容 Vite 8.0 (Rolldown) 和 Vite 2.x-7.x (Rollup)
  • 支持 EJS 模板引擎,可使用单个模板生成多个页面
  • 虚拟文件支持,开发时文件存在于内存中,构建时写入磁盘
  • 自动页面扫描功能
  • 完整的 HMR 支持
  • HTML 压缩(可选)
  • History Fallback 中间件支持

安装

# npm
npm install vite-plugin-virtual-mpa-v2 -D

# pnpm
pnpm add vite-plugin-virtual-mpa-v2 -D

# yarn
yarn add vite-plugin-virtual-mpa-v2 -D

快速选择指南

根据你的项目需求,选择合适的配置方式:

| 配置方式 | 入口文件数量 | 适用场景 | 配置复杂度 | |---------|------------|---------|-----------| | 共用入口 + 占位符 | 1 个 | 页面逻辑相似,共享初始化代码 | ⭐ 简单 | | 独立入口 | 每个页面 1 个 | 页面差异大,需要独立配置 | ⭐⭐ 中等 | | 手动配置页面 | 自由定义 | 需要完全控制每个页面 | ⭐⭐⭐ 灵活 |


配置方式详解

方式一:共用入口 + 占位符替换(推荐)

适用场景:所有页面共享相同的初始化逻辑(如 Vue 应用初始化、全局组件注册、插件安装等),只有页面组件不同。

工作原理

  • 使用一个共享的入口文件(如 src/main.ts
  • 入口文件中使用占位符(默认 __PAGE__
  • 构建时,插件会将占位符替换为实际页面的组件路径

目录结构

project/
├── index.html              # 模板文件
├── src/
│   ├── main.ts             # 共享入口(所有页面共用)
│   └── pages/
│       ├── home/
│       │   └── index.vue   # 首页组件
│       ├── about/
│       │   └── index.vue   # 关于页面组件
│       └── contact/
│           └── index.vue   # 联系页面组件
└── vite.config.ts

配置示例

// vite.config.ts
import { defineConfig } from 'vite'
import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'

export default defineConfig({
  plugins: [
    createMpaPlugin({
      template: 'index.html',           // 模板文件
      verbose: true,                    // 打印日志
      placeholder: '__PAGE__',          // 占位符(默认值)
      scanOptions: {
        scanDirs: 'src/pages',          // 扫描目录
        entryFile: 'main.ts',           // 入口文件名(相对于页面目录)
        componentFile: 'index.vue',     // 组件文件名(用于检测页面是否存在)
        filename: (name) => `${name}.html`  // 输出文件名规则
      }
    })
  ]
})
// src/main.ts - 共享入口文件
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import Page from '__PAGE__'  // 🔑 占位符,构建时会被替换为实际组件路径
import './style.css'         // ✅ 相对路径会自动转换为绝对路径

// 例如,构建 home 页面时,会替换为:
// import Page from '../../../src/pages/home/index.vue'
// import './style.css' 会被转换为 import '/src/style.css'

const app = createApp(Page)

// 可以在这里添加共享逻辑
// app.use(router)
// app.use(store)

app.mount('#app')
<!-- index.html - 模板文件 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= typeof pageName !== 'undefined' ? pageName : 'App' %></title>
</head>
<body>
  <div id="app"></div>
  <!-- 入口文件会自动注入 -->
</body>
</html>

构建结果

  • 生成 home.htmlabout.htmlcontact.html
  • 每个页面的入口文件中,__PAGE__ 被替换为对应的组件路径

方式二:独立入口

适用场景:每个页面有独立的初始化逻辑,或者不同页面使用不同的框架/库配置。

工作原理

  • 每个页面目录下都有自己的 main.ts 入口文件
  • 插件会自动扫描并使用每个页面的独立入口
  • 如果页面目录没有入口文件,则使用根目录的共享入口(fallback)

目录结构

project/
├── index.html              # 模板文件
├── src/
│   ├── main.ts             # 默认入口(可选,作为 fallback)
│   └── pages/
│       ├── home/
│       │   ├── index.vue   # 首页组件
│       │   └── main.ts     # 首页独立入口 ✅
│       ├── about/
│       │   ├── index.vue
│       │   └── main.ts     # 关于页面独立入口 ✅
│       └── contact/
│           └── index.vue   # 没有 main.ts,使用根目录的 src/main.ts
└── vite.config.ts

配置示例

// vite.config.ts
import { defineConfig } from 'vite'
import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'

export default defineConfig({
  plugins: [
    createMpaPlugin({
      template: 'index.html',
      verbose: true,
      scanOptions: {
        scanDirs: 'src/pages',
        entryFile: 'main.ts',           // 每个页面目录下的入口文件名
        componentFile: 'index.vue',     // 用于检测页面目录是否有效
        filename: (name) => `${name}.html`
      }
    })
  ]
})
// src/pages/home/main.ts - home 页面独立入口
import { createApp } from 'vue'
import Home from './index.vue'

const app = createApp(Home)
// home 页面特有的配置
app.mount('#app')
// src/pages/about/main.ts - about 页面独立入口
import { createApp } from 'vue'
import About from './index.vue'
import { createRouter, createWebHistory } from 'vue-router'

const app = createApp(About)
// about 页面可能需要路由
const router = createRouter(/* ... */)
app.use(router)
app.mount('#app')

优先级规则

  1. 如果页面目录下存在 main.ts(或配置的 entryFile),使用该入口
  2. 否则,使用根目录的 src/main.ts(如果存在)
  3. 如果都不存在,会报错

方式三:手动配置页面

适用场景:需要完全控制每个页面的配置,包括入口、模板、输出文件名等。适合复杂项目或特殊需求。

完整手动配置

// vite.config.ts
import { defineConfig } from 'vite'
import { createMpaPlugin, createPages } from 'vite-plugin-virtual-mpa-v2'

// 使用 createPages 辅助函数创建页面配置
const pages = createPages([
  {
    name: 'home',
    filename: 'index.html',           // 输出文件名
    entry: '/src/pages/home/main.ts', // 入口文件(必须以 / 开头)
    template: 'index.html',           // 可选,指定页面专属模板
    data: { title: '首页' }           // EJS 模板数据
  },
  {
    name: 'about',
    filename: 'about.html',
    entry: '/src/pages/about/main.ts',
    data: { title: '关于我们' }
  },
  {
    name: 'admin',
    filename: 'admin/index.html',     // 支持子目录
    entry: '/src/pages/admin/main.ts',
    template: 'admin.html',           // 使用不同的模板
    data: { title: '管理后台' }
  }
])

export default defineConfig({
  plugins: [
    createMpaPlugin({
      template: 'index.html',         // 默认模板
      pages,                          // 手动配置的页面列表
      verbose: true
    })
  ]
})

混合配置(扫描 + 手动覆盖)

// vite.config.ts
import { defineConfig } from 'vite'
import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'

export default defineConfig({
  plugins: [
    createMpaPlugin({
      template: 'index.html',
      // 自动扫描大部分页面
      scanOptions: {
        scanDirs: 'src/pages',
        entryFile: 'main.ts',
        componentFile: 'index.vue'
      },
      // 手动配置特殊页面(会与扫描结果合并,同名页面会覆盖)
      pages: [
        {
          name: 'special',
          entry: '/src/special-entry.ts',  // 特殊入口
          data: { title: '特殊页面' }
        }
      ]
    })
  ]
})

配置选项

MpaOptions

interface MpaOptions {
  // ========== 基础配置 ==========
  /**
   * 默认模板文件
   * @default 'index.html'
   */
  template?: string

  /**
   * 是否打印日志
   * @default true
   */
  verbose?: boolean

  // ========== 页面配置 ==========
  /**
   * 手动配置页面列表
   * 与 scanOptions 扫描的页面合并,同名页面会覆盖
   */
  pages?: Page[]

  /**
   * 自动扫描配置
   * 配置后会自动扫描目录下的页面
   */
  scanOptions?: ScanOptions

  // ========== 入口解析 ==========
  /**
   * 占位符,用于共用入口模式
   * 支持字符串或正则表达式
   * @default '__PAGE__'
   */
  placeholder?: string | RegExp

  /**
   * 自定义页面组件路径解析
   * 返回值会替换入口文件中的占位符
   */
  resolvePageEntry?: (pageName: string) => string

  // ========== 模板功能 ============
  /**
   * EJS 全局数据,会注入到所有页面模板中
   * 页面专属 data 会覆盖这里的同名属性
   */
  data?: Record<string, any>

  /**
   * 自定义 HTML 转换函数
   * 可以在构建时修改 HTML 内容
   */
  transformHtml?: (
    html: string,
    ctx: TransformHtmlContext
  ) => IndexHtmlTransformResult

  // ========== 服务器配置 ==========
  /**
   * 开发服务器 history fallback 重写规则
   * 用于 SPA 路由支持
   */
  rewrites?: RewriteRule

  /**
   * 预览服务器 history fallback 重写规则
   */
  previewRewrites?: RewriteRule

  /**
   * 开发服务器默认打开的页面
   * - 设置为页面名称(如 'app1'),会自动打开 /app1.html
   * - 设置为文件名(如 'app1.html'),会自动打开该页面
   * - 设置为 false 禁用自动打开功能
   */
  devServerOpenPage?: string | false

  // ========== 监听配置 ==========
  /**
   * 文件变化监听配置
   * 可用于热更新页面配置
   */
  watchOptions?: WatchOptions

  // ========== HTML 压缩 ==========
  /**
   * HTML 压缩配置
   * - true: 使用默认压缩选项
   * - 对象: 自定义压缩选项(基于 html-minifier-terser)
   */
  htmlMinify?: boolean | MinifyOptions
}

Page

interface Page {
  /**
   * 页面名称
   * - 用于生成默认的 rewrite 规则
   * - 不能包含 '/'
   */
  name: string

  /**
   * 输出的 HTML 文件名
   * @default `${name}.html`
   * 支持子目录,如 'admin/index.html'
   */
  filename?: string

  /**
   * 页面专属模板
   * 不指定则使用 MpaOptions.template
   */
  template?: string

  /**
   * 页面入口文件路径
   * - 必须是绝对路径(以 '/' 开头)
   * - 相对于项目根目录
   * @example '/src/pages/home/main.ts'
   */
  entry?: string

  /**
   * 页面专属 EJS 数据
   * 会与 MpaOptions.data 合并,同名属性优先使用这里
   */
  data?: Record<string, any>

  /**
   * 组件路径(相对于项目根目录)
   * 用于占位符替换时计算相对路径
   * 扫描模式下会自动设置
   * @example 'src/pages/home/index.vue'
   */
  componentPath?: string
}

ScanOptions

interface ScanOptions {
  /**
   * 要扫描的目录
   * 可以是单个目录或多个目录
   * @example 'src/pages'
   * @example ['src/pages', 'src/views']
   */
  scanDirs: string | string[]

  /**
   * 页面入口文件名
   * 相对于页面目录
   * @default 'main.ts'
   */
  entryFile?: string

  /**
   * 组件文件名
   * 用于检测页面目录是否有效
   * 只有包含此文件的目录才会被视为页面
   * @default 'index.vue'
   */
  componentFile?: string

  /**
   * 自定义输出文件名
   * @param pageName 页面名称(目录名)
   * @returns 输出的 HTML 文件名
   * @default (name) => `${name}.html`
   */
  filename?: (pageName: string) => string

  /**
   * 自定义模板路径
   * 相对于页面目录
   * 不指定则使用 MpaOptions.template
   * @example 'template.html'  // 使用页面目录下的 template.html
   */
  template?: string
}

WatchOptions

interface WatchOptions {
  /**
   * 包含的文件模式
   * 支持 glob 模式
   */
  include?: FilterPattern

  /**
   * 排除的文件模式
   * 支持 glob 模式
   */
  excluded?: FilterPattern

  /**
   * 要监听的事件类型
   * @default ['add', 'unlink', 'change', 'unlinkDir', 'addDir']
   */
  events?: ('add' | 'unlink' | 'change' | 'unlinkDir' | 'addDir')[]

  /**
   * 事件处理函数
   */
  handler: WatchHandler
}

interface WatchHandler {
  (ctx: {
    server: ViteDevServer
    file: string          // 变化的文件路径
    type: string          // 事件类型
    reloadPages: (pages: Page[]) => void  // 重新加载页面配置
  }): void
}

高级功能

自定义组件路径解析

如果你使用非标准的目录结构,可以通过 resolvePageEntry 自定义组件路径:

// vite.config.ts
export default defineConfig({
  plugins: [
    createMpaPlugin({
      template: 'index.html',
      placeholder: /\$\{pageName\}/g,  // 使用正则作为占位符
      resolvePageEntry: (pageName) => {
        // 自定义路径解析逻辑
        return `./views/${pageName}/View.vue`
      },
      scanOptions: {
        scanDirs: 'src/views',
        componentFile: 'View.vue'  // 自定义组件文件名
      }
    })
  ]
})
// src/main.ts
import { createApp } from 'vue'
import Page from '${pageName}'  // 会被替换为 resolvePageEntry 返回的值

createApp(Page).mount('#app')

开发服务器默认打开页面

配置开发服务器启动时自动打开的页面:

import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'

export default defineConfig({
  plugins: [
    createMpaPlugin({
      template: 'index.html',
      scanOptions: {
        scanDirs: 'src/pages',
      },
      // 方式一:使用页面名称
      devServerOpenPage: 'home',       // 打开 /home.html

      // 方式二:使用完整文件名
      // devServerOpenPage: 'home.html',  // 打开 /home.html

      // 方式三:禁用自动打开
      // devServerOpenPage: false
    })
  ]
})

注意:此配置仅在开发服务器启动时有效,配合 Vite 的 server.open 选项使用。

HTML 压缩

生产构建时压缩 HTML:

import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'

export default defineConfig({
  plugins: [
    createMpaPlugin({
      // ... 其他配置
      htmlMinify: true  // 使用默认配置
    })
  ]
})

自定义压缩选项:

createMpaPlugin({
  htmlMinify: {
    collapseWhitespace: true,
    removeComments: true,
    removeEmptyAttributes: true,
    minifyCSS: true,
    minifyJS: true
  }
})

自定义 HTML 转换

在构建时动态修改 HTML:

import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'

export default defineConfig({
  plugins: [
    createMpaPlugin({
      template: 'index.html',
      transformHtml(html, ctx) {
        // ctx.pageName - 当前页面名称
        // ctx.filename - 输出文件名
        // ctx.path - 页面路径

        return {
          html,
          tags: [
            {
              tag: 'meta',
              injectTo: 'head',
              attrs: {
                name: 'page-name',
                content: ctx.pageName
              }
            },
            {
              tag: 'script',
              injectTo: 'body',
              children: `window.PAGE_NAME = '${ctx.pageName}'`
            }
          ]
        }
      }
    })
  ]
})

EJS 模板

除了页面配置中的 data,所有以 VITE_ 开头的环境变量会自动注入到模板:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title><%= typeof title !== 'undefined' ? title : 'App' %></title>
  <% if (typeof VITE_API_URL !== 'undefined') { %>
  <script>
    window.API_URL = '<%= VITE_API_URL %>'
  </script>
  <% } %>
</head>
<body>
  <div id="app"></div>
</body>
</html>

从 vite-plugin-virtual-mpa 迁移

主要变化

| 特性 | 旧版本 | V2 版本 | |-----|-------|--------| | Vite 兼容性 | Vite 2.x-7.x | Vite 2.x-8.x (支持 Rolldown) | | 虚拟文件实现 | \0 虚拟模块前缀 | 临时文件缓存 | | 独立入口 | 需要手动配置每个页面 | 自动扫描支持 | | 占位符 | 固定 __PAGE__ | 可配置(字符串或正则) | | 组件解析 | 固定规则 | 可自定义 resolvePageEntry |

迁移步骤

  1. 更新依赖:
npm uninstall vite-plugin-virtual-mpa
npm install vite-plugin-virtual-mpa-v2 -D
  1. 更新导入:
// 旧版本
import { createMpaPlugin } from 'vite-plugin-virtual-mpa'

// 新版本
import { createMpaPlugin } from 'vite-plugin-virtual-mpa-v2'
  1. 配置调整(大多数情况下无需调整):

如果使用了独立入口,现在可以更简单地配置:

// 旧版本 - 需要为每个页面指定完整入口
createMpaPlugin({
  pages: [
    { name: 'pageA', entry: '/src/pages/pageA/main.ts' },
    { name: 'pageB', entry: '/src/pages/pageB/main.ts' }
  ]
})

// 新版本 - 自动扫描独立入口
createMpaPlugin({
  scanOptions: {
    scanDirs: 'src/pages',
    entryFile: 'main.ts',
    componentFile: 'index.vue'
  }
})

不兼容的变化

  • 临时文件存储在 node_modules/.mpa-cache/,无需手动清理
  • 虚拟模块前缀从 \0 改为临时文件,提高兼容性

默认 Rewrite 规则

插件会为每个页面自动生成 history fallback 规则:

{
  from: new RegExp(`^/(${pageNames.join('|')})/?$`),
  to: (ctx) => `/${base}/${inputMap[ctx.match[1]]}`
}

例如,配置了页面 homeabout 后:

  • 访问 /home → 重写到 /home.html
  • 访问 /about → 重写到 /about.html

常见问题

Q: TypeScript 报错 "Cannot find module 'PAGE'" 怎么解决?

A: 在使用共用入口 + 占位符模式时,需要在项目中添加类型声明。

方法一:使用包内置的类型声明(推荐)

tsconfig.json 中添加类型引用:

{
  "compilerOptions": {
    "types": ["vite-plugin-virtual-mpa-v2/global"]
  }
}

方法二:在项目中创建类型声明文件

创建 src/vite-env.d.ts(或 src/env.d.ts):

// src/vite-env.d.ts
/// <reference types="vite/client" />

// 声明默认占位符
declare module '__PAGE__' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

// 如果使用自定义占位符,也需要声明
// declare module '${your-placeholder}' {
//   import type { DefineComponent } from 'vue'
//   const component: DefineComponent<{}, {}, any>
//   export default component
// }

方法三:使用 @ts-ignore(不推荐)

// @ts-ignore
import Page from '__PAGE__'

Q: 占位符替换后的路径是什么?

A: 占位符会被替换为从虚拟入口文件(位于 node_modules/.mpa-cache/pages/)到组件文件的相对路径。例如:

  • 虚拟入口位置:node_modules/.mpa-cache/pages/home.ts
  • 组件文件:src/pages/home/index.vue
  • 替换结果:../../src/pages/home/index.vue

注意:在使用扫描模式时,componentPath 会自动设置。手动配置时可以通过 page.componentPath 指定组件路径。

Q: 如何在独立入口模式下共享代码?

A: 可以创建一个共享的初始化函数:

// src/shared/init.ts
export function setupApp(app: App) {
  // 共享配置
  app.use(router)
  app.use(store)
}

// src/pages/home/main.ts
import { createApp } from 'vue'
import { setupApp } from '../../shared/init'
import Home from './index.vue'

const app = createApp(Home)
setupApp(app)
app.mount('#app')

Q: 页面名称有什么限制?

A: 页面名称不能包含 /,因为它用于生成 rewrite 规则。如果需要子目录,请使用 filename 选项:

{
  name: 'admin-dashboard',  // ✅ 正确
  filename: 'admin/dashboard.html',  // 支持子目录
  // ...
}

Q: 共用入口中的相对路径 import 会正常工作吗?

A: 会。插件会自动将入口文件中的相对路径转换为绝对路径。

问题背景

在共用入口模式下,原始入口文件(如 src/main.ts)会被复制到虚拟入口目录(node_modules/.mpa-cache/pages/)。如果入口文件中存在相对路径的 import,直接复制会导致路径解析错误。

原始文件: src/main.ts
虚拟文件: node_modules/.mpa-cache/pages/home.ts

// 如果不处理,相对路径会从虚拟文件位置解析,导致错误
import './style.css'  // ❌ 会尝试从 node_modules/.mpa-cache/pages/style.css 加载

解决方案

插件在生成虚拟入口时,会自动将所有相对路径转换为以 / 开头的绝对路径(相对于项目根目录)。

转换流程

  1. 读取原始入口文件内容
  2. 使用正则表达式匹配所有相对路径的 import/export 语句
  3. 基于原始入口文件所在目录计算绝对路径
  4. 将相对路径替换为绝对路径
// src/main.ts (原始文件)
import './style.css'           // 相对路径
import '../shared/utils.ts'    // 相对路径
import './components/Foo.vue'  // 相对路径

// ↓ 插件转换后 ↓

// node_modules/.mpa-cache/pages/home.ts (虚拟文件)
import '/src/style.css'           // ✅ 绝对路径
import '/shared/utils.ts'         // ✅ 绝对路径
import '/src/components/Foo.vue'  // ✅ 绝对路径

支持的语法

| 语法类型 | 示例 | 转换结果 | |---------|------|---------| | 静态 import | import './style.css' | import '/src/style.css' | | 命名 import | import { foo } from './utils' | import { foo } from '/src/utils' | | 默认 import | import Foo from './Foo.vue' | import Foo from '/src/Foo.vue' | | 动态 import | import('./module.js') | import('/src/module.js') | | export from | export * from './utils' | export * from '/src/utils' | | 命名 export | export { foo } from './bar' | export { foo } from '/src/bar' |

独立入口模式的处理

独立入口模式同样支持相对路径转换:

// src/pages/home/main.ts (原始文件)
import './style.css'        // 页面目录下的样式
import './components/Header.vue'  // 页面目录下的组件

// ↓ 转换后 ↓

import '/src/pages/home/style.css'
import '/src/pages/home/components/Header.vue'

实现原理

核心代码位于 src/virtual-entry.ts

function rewriteRelativeImports(content: string, entryDir: string, root: string): string {
  // 匹配三种模式的相对路径
  const importRegex = /(import\s+(?:[\w*{}\s,]+\s+from\s+)?['"])(\.\.?\/[^'"]+)(['"])/g
  const exportRegex = /(export\s+(?:\*|{[\w\s,]+})\s+from\s+['"])(\.\.?\/[^'"]+)(['"])/g
  const dynamicImportRegex = /(import\s*\(\s*['"])(\.\.?\/[^'"]+)(['"]\s*\))/g

  const replacer = (_match, prefix, relativePath, suffix) => {
    // 1. 基于入口文件目录计算绝对路径
    const absolutePath = path.resolve(entryDir, relativePath)
    // 2. 转换为相对于项目根的绝对路径(以 / 开头)
    const normalizedPath = '/' + path.relative(root, absolutePath).split(path.sep).join('/')
    return `${prefix}${normalizedPath}${suffix}`
  }

  return content
    .replace(importRegex, replacer)
    .replace(exportRegex, replacer)
    .replace(dynamicImportRegex, replacer)
}

许可证

MIT