重要命令
- npm run 命令会将 -- 之后的参数传递给脚本 正确的参数传入命令 npm run dev -- -fesm -p -i reactivity
详细代码分析
// 导入esbuild和其他Node.js内置模块,以及第三方插件esbuild-plugin-polyfill-node。
import esbuild from 'esbuild'
import fs from 'node:fs'
import { dirname, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { parseArgs } from 'node:util'
import { polyfillNode } from 'esbuild-plugin-polyfill-node'
// 创建一个require函数,用于动态加载模块。
const require = createRequire(import.meta.url)
// 获取当前文件的目录路径。
const __dirname = dirname(fileURLToPath(import.meta.url))
// npm run 命令会将 -- 之后的参数传递给脚本 正确的参数传入命令 npm run dev -- -fesm -p -i reactivity
// 使用parseArgs解析命令行参数,提取构建格式(format)、是否为生产环境(prod)和是否内联依赖(inlineDeps)。
// positionals存储位置参数,即目标包的名称。
// 配置选项:定义了三个命令行选项 format、prod 和 inline,分别对应 -f、-p 和 -i 短选项,并设置了默认值。
// 解析参数:使用 parseArgs 函数解析命令行参数,允许位置参数(即不带选项的参数)。
// 提取结果:从解析结果中提取 format、prod 和 inline 的值,并将其赋值给 rawFormat、prod 和 inlineDeps 变量。
// parseArgs 默认返回的数据中包含了位置参数(positionals)、值(values-对应的是options传入的参数)。
const {
values: { format: rawFormat, prod, inline: inlineDeps },
positionals,
} = parseArgs({
// allowPositionals 是一个配置选项,通常用于命令行参数解析库(如 yargs 或 commander)。它的作用是允许解析位置参数(即不带选项的参数)。
// node script.js --format=global --prod file1 file2
// 在这个例子中,file1 和 file2 就是位置参数。由于 allowPositionals 被设置为 true,这些位置参数会被正确解析并存储在 positionals 中。
allowPositionals: true,
options: {
format: {
type: 'string',
short: 'f',
default: 'global',
},
prod: {
type: 'boolean',
short: 'p',
default: false,
},
inline: {
type: 'boolean',
short: 'i',
default: false,
},
},
})
// 确定构建格式,默认为global。
// 确定目标包,默认为vue。
const format = rawFormat || 'global' // 默认打包模式
const targets = positionals.length ? positionals : ['vue']
console.log(`targets: ${targets.join(', ')}, 模式: ${format}, 是否内联依赖:${inlineDeps},存储位置参数,即目标包的名称:${positionals}`)
// 输出文件路径解析:根据目标包和构建格式生成输出文件路径。
// 根据构建格式确定输出格式(iife, cjs, esm)。
const outputFormat = format.startsWith('global')
? 'iife'
: format === 'cjs'
? 'cjs'
: 'esm'
// 根据构建格式生成后缀名。
const postfix = format.endsWith('-runtime')
? `runtime.${format.replace(/-runtime$/, '')}`
: format
// 读取私有包目录。
const privatePackages = fs.readdirSync('packages-private')
// 遍历目标包,确定包的基础路径和包信息。
for (const target of targets) {
const pkgBase = privatePackages.includes(target)
? `packages-private`
: `packages`
const pkgBasePath = `../${pkgBase}/${target}`
const pkg = require(`${pkgBasePath}/package.json`)
const outfile = resolve(
__dirname,
`${pkgBasePath}/dist/${
target === 'vue-compat' ? `vue` : target
}.${postfix}.${prod ? `prod.` : ``}js`,
)
// 生成输出文件路径,并转换为相对于当前工作目录的路径。
const relativeOutfile = relative(process.cwd(), outfile)
// 如果不内联依赖,根据构建格式和目标包,确定需要外部化的依赖项。
// TODO this logic is largely duplicated from rollup.config.js
/** @type {string[]} */
let external = []
if (!inlineDeps) {
// cjs & esm-bundler: external all deps
if (format === 'cjs' || format.includes('esm-bundler')) {
external = [
...external,
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
// for @vue/compiler-sfc / server-renderer
'path',
'url',
'stream',
]
}
if (target === 'compiler-sfc') {
const consolidatePkgPath = require.resolve(
'@vue/consolidate/package.json',
{
paths: [resolve(__dirname, `../packages/${target}/`)],
},
)
const consolidateDeps = Object.keys(
require(consolidatePkgPath).devDependencies,
)
external = [
...external,
...consolidateDeps,
'fs',
'vm',
'crypto',
'react-dom/server',
'teacup/lib/express',
'arc-templates/dist/es5',
'then-pug',
'then-jade',
]
}
}
console.log(external,'external')
/** @type {Array<import('esbuild').Plugin>} */
// 添加日志插件,记录构建完成的信息。
const plugins = [
{
name: 'log-rebuild',
setup(build) {
build.onEnd(() => {
console.log(`built: ${relativeOutfile}`)
})
},
},
]
// 如果构建格式不是cjs且包配置中启用了非浏览器分支,则添加Node环境的polyfill插件。
if (format !== 'cjs' && pkg.buildOptions?.enableNonBrowserBranches) {
plugins.push(polyfillNode())
}
// 使用esbuild的context方法配置构建任务,包括入口文件、输出文件、外部依赖、源码映射、格式、平台、插件和定义的全局变量。
esbuild
.context({
entryPoints: [resolve(__dirname, `${pkgBasePath}/src/index.ts`)],
outfile,
bundle: true,
external,
sourcemap: true,
format: outputFormat,
globalName: pkg.buildOptions?.name,
platform: format === 'cjs' ? 'node' : 'browser',
plugins,
define: {
__COMMIT__: `"dev"`,
__VERSION__: `"${pkg.version}"`,
__DEV__: prod ? `false` : `true`,
__TEST__: `false`,
__BROWSER__: String(
format !== 'cjs' && !pkg.buildOptions?.enableNonBrowserBranches,
),
__GLOBAL__: String(format === 'global'),
__ESM_BUNDLER__: String(format.includes('esm-bundler')),
__ESM_BROWSER__: String(format.includes('esm-browser')),
__CJS__: String(format === 'cjs'),
__SSR__: String(format !== 'global'),
__COMPAT__: String(target === 'vue-compat'),
__FEATURE_SUSPENSE__: `true`,
__FEATURE_OPTIONS_API__: `true`,
__FEATURE_PROD_DEVTOOLS__: `false`,
__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: `true`,
},
})
.then(ctx => ctx.watch()) //启动esbuild的监听模式,以便在文件变化时自动重新构建。
}
执行流程图
graph TD
A[开始] --> B[导入依赖]
B --> C[创建require函数和目录路径]
C --> D[解析命令行参数]
D --> E[确定构建格式和目标包]
E --> F[解析输出文件路径]
F --> G[处理外部依赖]
G --> H[配置插件]
H --> I[配置构建任务]
I --> J[启动esbuild监听模式]
J --> K[结束]
详细步骤
-
导入依赖:
- 导入esbuild和其他Node.js内置模块,以及第三方插件
esbuild-plugin-polyfill-node。
- 导入esbuild和其他Node.js内置模块,以及第三方插件
-
创建require函数和目录路径:
- 创建一个
require函数,用于动态加载模块。 - 获取当前文件的目录路径。
- 创建一个
-
解析命令行参数:
- 使用
parseArgs解析命令行参数,提取构建格式、是否为生产环境和是否内联依赖。 positionals存储位置参数,即目标包的名称。
- 使用
-
确定构建格式和目标包:
- 确定构建格式,默认为
global。 - 确定目标包,默认为
vue。
- 确定构建格式,默认为
-
解析输出文件路径:
- 根据构建格式确定输出格式(
iife,cjs,esm)。 - 根据构建格式生成后缀名。
- 读取私有包目录。
- 遍历目标包,确定包的基础路径和包信息。
- 生成输出文件路径,并转换为相对于当前工作目录的路径。
- 根据构建格式确定输出格式(
-
处理外部依赖:
- 如果不内联依赖,根据构建格式和目标包,确定需要外部化的依赖项。
-
配置插件:
- 添加日志插件,记录构建完成的信息。
- 如果构建格式不是
cjs且包配置中启用了非浏览器分支,则添加Node环境的polyfill插件。
-
配置构建任务:
- 使用esbuild的
context方法配置构建任务,包括入口文件、输出文件、外部依赖、源码映射、格式、平台、插件和定义的全局变量。 - 启动esbuild的监听模式,以便在文件变化时自动重新构建。
- 使用esbuild的
-
结束:
- 构建过程结束。