共计 11049 个字符,预计需要花费 28 分钟才能阅读完成。
文件门路:VUE 3.0 源码 /rollup.config.js
上一篇文章中,独自介绍了各种类型的输入文件的差别,明天这篇文章将残缺的介绍一下该配置文件
一、rollup.config.js 配置信息时如何失效的?
scripts/dev.js | scripts/build.js 文件中都有这么一个函数:
execa('rollup', [ '-c', '--environment', [ // ...], ])
要害就在 ‘-c’ 这个参数上:作用是 rollup 打包指定应用配置文件 rollup.config.js
二、整体形容一下该配置文件的执行过程:整体围绕生成 packageConfigs 配置信息的创立开展,创立实现后:export default packageConfigs,具体细节如下:
1、dev 非正式环境下回执行函数 createConfig() 创立配置信息,prod 环境会调用 createProductionConfig() 和 createMinifiedConfig() 两个函数生成相应的配置信息。
2、createConfig 过程:
(1)会配置 input 入口文件。
(2)external 内部依赖的名称。
(3)plugins 插件信息。
(4)调用 createReplacePlugin 函数:配置替换插件中须要替换的变量,打包过程中替换文件中的字符串。
(5)配置 output 输入信息。
(6)设置 onwarn 拦挡正告音讯. 如果未配置,正告信息将被去重并打印到控制台。
(7)配置 treeshake 升高代码体积。
三、上源码,内容有点多,依据下面梳理的思路,能够疾速浏览一下各个环节的代码:
/**
* path 是 node 的默认库咱们在启动我的项目的时候应用的是 node 的环境所以 path 库就能够间接应用了,而不须要在装置了,* 这就是为什么 package.json 中没有 path 的起因
*/
import path from 'path'
/** 带有 typescript 编译器谬误的的 rollup 插件, 它能够打印出 typescript 语法和语义诊断音讯 */
import ts from 'rollup-plugin-typescript2'
/** 替换文件中的指标字符串 */
import replace from '@rollup/plugin-replace'
/**
* 容许 Rollup 从 JSON 文件中导入数据
* 例如:import {version} from '../package.json';
*/
import json from '@rollup/plugin-json'
// 环境变量中指标模块为设置异样解决,因为命令行中输出的模块名跟 packages 目录项模块不匹配导致
if (!process.env.TARGET) {throw new Error('TARGET package must be specified via --environment flag.')
}
// 根目录下 package.json 中定义的版本号
const masterVersion = require('./package.json').version
// 根目录下 packages 门路
const packagesDir = path.resolve(__dirname, 'packages')
// 命令行中输出的模块名称在 packages 目录中所匹配的模块门路
const packageDir = path.resolve(packagesDir, process.env.TARGET)
// packages 目录下某模块名称
const name = path.basename(packageDir)
const resolve = p => path.resolve(packageDir, p)
// 引入以后模块下的 package.json 文件
const pkg = require(resolve(`package.json`))
// 引入以后模块下的 package.json 文件 中设定的 buildOptions 属性
const packageOptions = pkg.buildOptions || {}
// ensure TS checks only once for each build
// 全局记录 TS 检测状态,实现一次检测后记录状态,避免屡次检测,呈现谬误。let hasTSChecked = false
/**
* 一、全局打包: vue(.runtime).global(.prod).js:【应用 CDN 或没有构建工具】*
* 1、全局打包不是 UMD 构建的,它们被打包成 IIFEs,并且仅用于通过 <script src="..."> 间接应用。* format: `iife`
* https://developer.mozilla.org/en-US/docs/Glossary/IIFE
* iife 的全称是 Immediately Invoked Function Expression,即 "立刻调用的函数表达式",* 能够很容易的用 JS 来表白:(function () {}())
* 用于通过 <script src="..."> 间接应用。* 2、若要通过浏览器中的 <script src="..."> 间接应用,则裸露 Vue 全局。* 3、vue.global.js 是蕴含编译器和运行时的“残缺”构建版本,因而它反对动静编译模板。* 4、vue.runtime.global.js 只蕴含运行时,并且须要在构建步骤期间预编译模板。*
* 二、vue(.runtime).esm-browser(.prod).js:【应用 CDN 或没有构建工具】*
* 1、用于通过原生 ES 模块导入应用 (在浏览器中通过 <script type="module"> 来应用)。* 2、与全局构建版本共享雷同的运行时编译、依赖内联和硬编码的 prod/dev 行为。*
* 三、vue(.runtime).esm-bundler.js:【应用构建工具】*
* 1、vue.runtime.esm-bundler.js (默认) 仅运行时,并要求所有模板都要事后编译。* 这是构建工具的默认入口 (通过 package.json 中的 module 字段),* 因为在应用构建工具时,模板通常是事后编译的 (例如:在 *.vue 文件中)。* 2、vue.esm-bundler.js 蕴含运行时编译器。* 如果你应用了一个构建工具,但依然想要运行时的模板编译 (例如,DOM 内 模板或通过内联 JavaScript 字符串的模板),* 请应用这个文件。你须要配置你的构建工具,将 vue 设置为这个文件
*
* 四、vue.cjs(.prod).js:【服务端渲染】*
* 1、通过 require() 在 Node.js 服务器端渲染应用。* 2、如果你将应用程序与带有 target: 'node' 的 webpack 打包在一起,并正确地将 vue 内部化,则将加载此文件。* 3、dev/prod 文件是预构建的,然而会依据 process.env.NODE_ENV 主动加载相应的文件。*/
const outputConfigs = {
'esm-bundler': {file: resolve(`dist/${name}.esm-bundler.js`),
format: `es`
},
'esm-browser': {file: resolve(`dist/${name}.esm-browser.js`),
format: `es`
},
cjs: {file: resolve(`dist/${name}.cjs.js`),
format: `cjs`
},
global: {file: resolve(`dist/${name}.global.js`),
format: `iife`
},
// runtime-only builds, for main "vue" package only
'esm-bundler-runtime': {file: resolve(`dist/${name}.runtime.esm-bundler.js`),
format: `es`
},
'esm-browser-runtime': {file: resolve(`dist/${name}.runtime.esm-browser.js`),
format: 'es'
},
'global-runtime': {file: resolve(`dist/${name}.runtime.global.js`),
format: 'iife'
}
}
// 模块默认打包形式
const defaultFormats = ['esm-bundler', 'cjs']
/**
* 命令行中输出的 -f | --formats 信息会在 scripts/dev.js 中赋值给环境变量 env.FORMATS。* 如 'esm-bundler,cjs' 多个值时两头可用逗号分隔.
* process.env.FORMATS 默认值:'global'
*/
const inlineFormats = process.env.FORMATS && process.env.FORMATS.split(',')
/**
* 模块打包类型优先级:* 第 1 优先级: inlineFormats 即命令行输出的 格局类型
* 第 2 优先级: packageOptions.formats 即命 /packages 目录下子模块 package.json 中 buildOptions 属性定义的 formats
* 第 2 优先级: defaultFormats 即默认打包形式 ['esm-bundler', 'cjs']
*/
const packageFormats = inlineFormats || packageOptions.formats || defaultFormats
/**
* 正式环境打包时 (非 dev 开发环境),须要增加 --prodOnly | -p 参数,* -- 在 scripts/build.js 中,会查看命令行参数设置,参数正确时会设置 env.PROD_ONLY 环境变量。* 1、正式环境 打包配置信息为 [];
* 2、其余非正式环境 通过函数 createConfig 创立配置信息;*/
const packageConfigs = process.env.PROD_ONLY
? []
: packageFormats.map(format => createConfig(format, outputConfigs[format]))
if (process.env.NODE_ENV === 'production') {
packageFormats.forEach(format => {if (packageOptions.prod === false) {return}
if (format === 'cjs') {packageConfigs.push(createProductionConfig(format))
}
if (/^(global|esm-browser)(-runtime)?/.test(format)) {packageConfigs.push(createMinifiedConfig(format))
}
})
}
export default packageConfigs
/**
* 生成非正式环境模块打包配置信息
* @param {*} format 以后模块 formats 中的一个,如: /packages/vue/package.json buildOptions 属性中 formats 列表元素
* @param {*} output 定义输入配置信息 类型:{file: string, format: string} 具体可参考变量: outputConfigs 元素属性值
* @param {*} plugins
* @returns
*/
function createConfig(format, output, plugins = []) {
// 没有定义 package 输入相干配置信息:打印正告信息 & 线程退出
if (!output) {console.log(require('chalk').yellow(`invalid format: "${format}"`))
process.exit(1)
}
// 依据全局环境变量 SOURCE_MAP 信息,给 output 变量设置 sourcemap 属性。// 受控于命令行参数 --formats | -f 的影响
output.sourcemap = !!process.env.SOURCE_MAP
/**
* 给 output 变量设置 externalLiveBindings 属性,默认值(true)* 当设置为 false 时,Rollup 不会生成代码来反对内部导入的模块 (假如导出的模块不会随工夫而扭转的前提下)。* 这将容许 Rollup 生成更优化的代码。请留神,当存在波及内部依赖项的循环依赖项时,这可能会导致问题。* 这将防止大多数状况下 Rollup 在代码中生成 getter,因而在许多状况下可用于使代码 IE8 兼容。*/
output.externalLiveBindings = false
/**
* 判断是否是正式环境打包:依据环境变量__DEV__ || 打包文件的输入门路名称是否以 '.prod.js' 结尾
* __DEV__ 什么时候被赋值的 (大概率跟 process.env.NODE_ENV 相干)
*/
const isProductionBuild =
process.env.__DEV__ === 'false' || /\.prod\.js$/.test(output.file)
/* esm-bundler esm-browser cjs global 类型 详见 38~71 行正文 */
const isBundlerESMBuild = /esm-bundler/.test(format)
const isBrowserESMBuild = /esm-browser/.test(format)
const isNodeBuild = format === 'cjs'
const isGlobalBuild = /global/.test(format)
/**
* 全局打包时,读取模块下 package.json 文件 中设定的 buildOptions 属性中的 name 属性赋值给 output.name
*/
if (isGlobalBuild) {output.name = packageOptions.name}
// 判断是否生成 .d.ts 和 .d.ts.map 文件
const shouldEmitDeclarations = process.env.TYPES != null && !hasTSChecked
/** 配置 TS 插件 */
const tsPlugin = ts({
// check: 设置为 false 可防止对代码进行任何诊断查看。check: process.env.NODE_ENV === 'production' && !hasTSChecked,
/**
* tsconfig 的门路.json。如果您的 tsconfig 在我的项目目录中有其余名称或绝对地位,请设置此选项。* 默认状况下,将尝试加载 /tsconfig.json,但如果文件失落,则不会失败,除非明确设置该值。*/
tsconfig: path.resolve(__dirname, 'tsconfig.json'),
/* 缓存的门路。默认为 node_modules 目录下的一个的文件夹 */
cacheRoot: path.resolve(__dirname, 'node_modules/.rts2_cache'),
/**
* 笼罩原有的 tsconfigDefaults 配置信息 = merge tsconfig.json to tsconfigDefaults;
*/
tsconfigOverride: {
compilerOptions: {
sourceMap: output.sourcemap,
/**
* 为我的项目中 TypeScript 和 JavaScript 生成.d.ts 文件
*/
declaration: shouldEmitDeclarations,
/**
* 开启 --declarationMap,编译器会同时生成 .d.ts 和 .d.ts.map 文件。* 语言服务当初可能正确辨认这些映射文件,并且应用它们来映射到源码。* 也就是说,在应用“跳到定义之处”性能时,会间接跳转到源码文件,而不是 .d.ts 文件。*/
declarationMap: shouldEmitDeclarations
},
// 指定解析 include 属性配置信息时,应该跳过的文件名称
exclude: ['**/__tests__', 'test-dts']
}
})
// we only need to check TS and generate declarations once for each build.
// it also seems to run into weird issues when checking multiple times
// during a single build.
// 防止出现问题,每次构建只做一次 TS 检测
hasTSChecked = true
// 定义模块入口文件
const entryFile = /runtime$/.test(format) ? `src/runtime.ts` : `src/index.ts`
// 指出应将哪些模块视为内部模块,不会与你的库打包在一起。const external =
isGlobalBuild || isBrowserESMBuild
? packageOptions.enableNonBrowserBranches
? // externalize postcss for @vue/compiler-sfc
// because @rollup/plugin-commonjs cannot bundle it properly
['postcss']
: // normal browser builds - non-browser only imports are tree-shaken,
// they are only listed here to suppress warnings.
['source-map', '@babel/parser', 'estree-walker']
: // Node / esm-bundler builds. Externalize everything.
[...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
...['path', 'url'] // for @vue/compiler-sfc
]
// the browser builds of @vue/compiler-sfc requires postcss to be available
// as a global (e.g. http://wzrd.in/standalone/postcss)
/**rollup 通过 `external` + `output.globals` 来标记内部依赖 */
output.globals = {postcss: 'postcss'}
/**
* node 插件
* 1. @rollup/plugin-node-resolve 插件:应用节点解析算法在 node_modules 中查找第三方模块
* 2. @rollup/plugin-commonjs 插件:将 CommonJS 模块转换为 ES6,以便它们能够蕴含在 Rollup bundle 中
* 3. rollup-plugin-node-builtins 插件: 容许 node 内置模块 to be required/imported.
* 4. rollup-plugin-node-globals 插件: 用于嵌入 node 全局信息,包含用于 browserify 的代码,即便它应用过程或缓冲区,也应该能够工作
*/
const nodePlugins =
packageOptions.enableNonBrowserBranches && format !== 'cjs'
? [require('@rollup/plugin-node-resolve').nodeResolve({
// 如果 'true',插件将更优先应用内置模块(例如 'fs','path`)。如果'false',插件将查找本地装置的同名模块。preferBuiltins: true
}),
require('@rollup/plugin-commonjs')({sourceMap: false}),
require('rollup-plugin-node-builtins')(),
require('rollup-plugin-node-globals')()]
: []
return {
// 入口文件
input: resolve(entryFile),
// Global and Browser ESM builds inlines everything so that they can be
// used alone.
// 内部依赖的名称
external,
plugins: [
// 蕴含 JSON 插件
json({
// 为 JSON 对象的每个属性生成命名导出。默认为 true
namedExports: false
}),
tsPlugin,
// 配置替换插件中须要替换的变量,打包过程中替换文件中的字符串。createReplacePlugin(
isProductionBuild,
isBundlerESMBuild,
isBrowserESMBuild,
// isBrowserBuild?
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
!packageOptions.enableNonBrowserBranches,
isGlobalBuild,
isNodeBuild
),
...nodePlugins,
...plugins
],
output,
// 拦挡正告音讯. 如果未配置,正告信息将被去重并打印到控制台。onwarn: (msg, warn) => {if (!/Circular/.test(msg)) {warn(msg)
}
},
treeshake: {
// code from imported modules will only be retained if at least one exported value is used
// 如果引入的模块代码想要保留,至多须要有一个该模块元素被应用过。moduleSideEffects: false
}
}
}
/**
* 配置 Replace 插件
*/
function createReplacePlugin(
isProduction,
isBundlerESMBuild,
isBrowserESMBuild,
isBrowserBuild,
isGlobalBuild,
isNodeBuild
) {
// 打包文件时替换文件中的字符串。const replacements = {__COMMIT__: `"${process.env.COMMIT}"`,
__VERSION__: `"${masterVersion}"`,
__DEV__: isBundlerESMBuild
? // preserve to be handled by bundlers
`(process.env.NODE_ENV !== 'production')`
: // hard coded dev/prod builds
!isProduction,
// this is only used during Vue's internal tests
__TEST__: false,
// If the build is expected to run directly in the browser (global / esm builds)
__BROWSER__: isBrowserBuild,
__GLOBAL__: isGlobalBuild,
__ESM_BUNDLER__: isBundlerESMBuild,
__ESM_BROWSER__: isBrowserESMBuild,
// is targeting Node (SSR)?
__NODE_JS__: isNodeBuild,
// feature flags
__FEATURE_SUSPENSE__: true,
__FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true,
__FEATURE_PROD_DEVTOOLS__: isBundlerESMBuild
? `__VUE_PROD_DEVTOOLS__`
: false,
...(isProduction && isBrowserBuild
? {
'context.onError(': `/*#__PURE__*/ context.onError(`,
'emitError(': `/*#__PURE__*/ emitError(`,
'createCompilerError(': `/*#__PURE__*/ createCompilerError(`,
'createDOMCompilerError(': `/*#__PURE__*/ createDOMCompilerError(`}
: {})
}
// allow inline overrides like
//__RUNTIME_COMPILE__=true yarn build runtime-core
Object.keys(replacements).forEach(key => {if (key in process.env) {replacements[key] = process.env[key]
}
})
return replace(replacements)
}
/**
* Prod 正式环境:format === 'cjs' 生成服务端渲染包时 额定配置信息
* @param {*} format
* @returns
*/
function createProductionConfig(format) {
return createConfig(format, {file: resolve(`dist/${name}.${format}.prod.js`),
format: outputConfigs[format].format
})
}
/**
* Prod 正式环境: global-runtime|esm-browser-runtime 的额定配置
* @param {*} format
* @returns
*/
function createMinifiedConfig(format) {
// minify generated es bundle 压缩 ES code
const {terser} = require('rollup-plugin-terser')
return createConfig(
format,
{file: outputConfigs[format].file.replace(/\.js$/, '.prod.js'),
format: outputConfigs[format].format
},
[
terser({module: /^esm/.test(format), // true is set when format is esm or es
compress: {
// 5 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020;
ecma: 2015,
pure_getters: true
}
})
]
)
}
如果您对“前端源码”情有独钟,能够微信扫码关注上面的公众号二维码,内容始终继续更新中!
以后 VUE3.0 源码正在解析中,欢送捧场!