上文提到 Vue.js 会为开发环境和生产环境输出不同的包,例如 vue.global.js 用于开发环境,它包含必要的警告信息, 而 vue.global.prod.js 用于生产环境,不包含警告信息。实际上,Vue.js 的构建产物除了有环境上的区分之外,还会根据使用场景的不同而输出其他形式的产物。 本节中,我们将讨论这些产物的用途以及在构建阶段如何输出这些产物。
不同类型的产物一定有对应的需求背景,因此我们从需求讲起。首先我们希望开发者可以直接在 HTML 页面中使用 <script>
标签引入框架并使用:
<body>
<script src="/path/to/vue.js"></script><script>
const { createApp } = Vue
// ...
</script>
</body>
为了实现这个需求,我们需要输出一种叫作 IIFE 格式的资源。IIFE 的全称是 Immediately Invoked Function Expression, 即“立即调用函数表达式”,易于用 JS 来表达:
(function(){
// ...
}())
如以上代码所示,这是一个立即调用函数表达式。实际上,vue.global.js 文件就是 IIFE 形式的资源,它的代码结构如下所示:
var Vue = (function(exports){
// ...
exports.createApp = createApp;
// ...
return exports
}({}))
这样当我们使用 <script>
标签直接引入 vue.global.js 文件后,全局变量 Vue 就是可用的了。
在 rollup.js 中,我们可以通过配置 format: 'iife' 来输出这种形式的资源:
const config = {
input: 'input.js',
output: {
file: 'output.js',
format: 'iife' // 指定模块形式
}
}
export default config
不过随着技术的发展和浏览器的支持,现在主流浏览器对原生 ESM 的支持都不错,所以用户除了能够使用 <script>
标签引用 IIFE 格式的资源外, 还可以直接引入 ESM 格式的资源,例如 Vue.js 3 还会输出 vue.esm-browser.js 文件,用户可以直接用 <script type="module">
标签引入:
<script type="module" src="/path/to/vue.esm-browser.js"></script>
为了输出 ESM 格式的资源,rollup.js 的输出格式需要配置为:format: 'esm'。
你可能已经注意到了,为什么 vue.esm-browser.js 文件中会有 -browser 字样? 其实对于 ESM 格式的资源来说,Vue.js 还会输出一个 vue.esm-bundler.js 文件,其中 -browser 变成了 -bundler。 为什么这么做呢?我们知道,无论是 rollup.js 还是 webpack,在寻找资源时,如果 package.json 中存在 module 字段, 那么会优先使用 module 字段指向的资源来代替 main 字段指向的资源。我们可以打开 Vue.js 源码中的 packages/vue/package.json 文件看一下:
{
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",
}
其中 module 字段指向的是 vue.runtime.esm-bundler.js 文件,意思是说,如果项目是使用 webpack 构建的, 那么你使用的 Vue.js 资源就是 vue.runtime.esm-bundler.js 也就是说,带有 -bundler 字样的 ESM 资源是给 rollup.js 或 webpack 等打包工具使用的, 而带有 -browser 字样的 ESM 资源是直接给 <script type="module">
使用的。它们之间有何区别?
这就不得不提到上文中的 __DEV__
常量。当构建用于 <script>
标签的 ESM 资源时,如果是用于开发环境,那么 __DEV__
会设置为 true; 如果是用于生产环境,那么 __DEV__
常量会设置为 false,从而被 Tree-Shaking 移除。但是当我们构建提供给打包工具的 ESM 格式的资源时, 不能直接把 __DEV__
设置为 true 或 false,而要使用 (process.env.NODE_ENV !=='production') 替换 __DEV__
常量。例如下面的源码:
if (__DEV__) {
warn(`useCssModule() is not supported in the global build.`)
}
在带有 -bundler 字样的资源中会变成:
if ((process.env.NODE_ENV !== 'production')) {
warn(`useCssModule() is not supported in the global build.`)
}
这样做的好处是,用户可以通过 webpack 配置自行决定构建资源的目标环境,但是最终效果其实一样,这段代码也只会出现在开发环境中。
用户除了可以直接使用 <script>
标签引入资源外,我们还希望用户可以在 Node.js 中通过 require 语句引用资源,例如:
const Vue = require('vue')
为什么会有这种需求呢?答案是“服务端渲染”。当进行服务端渲染时,Vue.js 的代码是在 Node.js 环境中运行的,而非浏览器环境。 在 Node.js 环境中,资源的模块格式应该是 CommonJS,简称 cjs。 为了能够输出 cjs 模块的资源,我们可以通过修改 rollup.config.js 的配置 format: 'cjs' 来实现:
const config = {
input: 'input.js',
output: {
file: 'output.js',
format: 'cjs' // 指定模块形式
}
}
export default config