返回

专栏第二篇-源码构建

  在上一篇文章中,我们大概了解了Vue的目录结构是怎么样的。

  在这一篇文章中,我们会知晓 Vue 是如何构建的。

一、Vue的构建

1.1 根据使用场景打包

在上一篇专栏文章中,我们熟悉了 Vue源码中的代码结构。

其中我们介绍了几个入口文件:

  1. entry-runtime.ts
  2. entry-runtime-with-compiler.ts
  3. entry-runtime-esm.ts
  4. entry-runtime-with-compiler-esm.ts

在我们开发项目结束后,需要进行打包并部署到线上。

通常我们项目打包时需要一个入口文件, webpack通过这个入口文件递归文件进行依赖分析然后打包。

其实框架打包也一样,同样需要一个入口文件进行递归依赖分析打包生成 js文件方便我们进行使用。

和项目不一样的是,项目往往只有一个入口文件,而框架会根据不同的情况采用不同的入口文件进行打包,上面的这四个文件就是Vue框架的入口文件。

Vue是使用rollup进行打包的。

为了方便用户进行使用,Vue在使用rollup进行打包时根据开发环境`和开发模式打包出了不同功能的文件,以适用于不同的使用场景。

构建的rollup打包脚本的代码在scripts/config.js文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const builds = {
'runtime-cjs-dev': {
entry: resolve('web/entry-runtime.ts'),
dest: resolve('dist/vue.runtime.common.dev.js'),
format: 'cjs',
env: 'development',
banner
},
'runtime-cjs-prod': {
entry: resolve('web/entry-runtime.ts'),
dest: resolve('dist/vue.runtime.common.prod.js'),
format: 'cjs',
env: 'production',
banner
},
// 省略
}

总共打包生成 12 个文件,这些文件可以适用于不同的场景。

  1. 根据是否带编译器的角度上分为Full和Runtime-Only 2个版本。Full版本包含编译器和运行时的全部功能。Runtime-Only 仅含运行时功能。
  2. 打包的文件根据使用场景分为 esm、cjs、umd三个版本。其中umd可以通过<script>标签引入直接在浏览器中使用,Vue会暴露一个全局变量 Windows.Vue。而 CommonJS适配const Vue = require(‘vue’)这种 node式的模块系统。ES则适配import Vue from ‘vue’这种es6格式。
  3. 打包的文件根据环境分为 dev/prod,在开发环境中可以使用 dev版本的 js文件,而部署到客户生产环境就可以使用 prod版本的 js文件。 dev版本的文件有一些提示,会在开发者开发时便于调试。

1.2 Full版本的Vue到底是什么意思?

平时我们开发中只使用了 runtime-only 版本。

而 Full版本中不仅有 runtime-only部分,还包括编译器部分。

所谓编译器,就是在Vue内部的编译器可以将模板转化为对应的render函数。

在Vue内部渲染就是调用的 render方法生成 vnode。

因为编译器代码体积比较大,而且如果在运行的时候进行模板编译,极大可能会消耗性能。

所以我们一般在开发项目时,使用 runtime-only版本。

在 webpack预编译阶段,就将.vue文件编译成render函数,在运行时直接运行 render函数就可以获取到对应的 vnode。

通过 runtime-only 和 esm很容易推测出来我们项目在开发阶段中使用的是vue.runtime.esm.js

1.3 使用 Full 还是用 Runtime-Only版本

这需要依据具体情况进行具体分析。

倘若你需要用到 Vue 所提供的 html 模板功能,那就选用 Full 版本。

反之,最好采用 Runtime-only 版本,原因在于它比 Full 版本的文件体积大约小 30%。

*.vue 单文件组件会被 vue-loader 直接构建成为 JavaScript,并未使用到 Vue 的编译器,所以可以使用 Runtime-only 版本。

二、神奇的“__DEV__”

在前面我们说到,在打包成开发环境的包时,往往会存在很多提示信息。

而源码中判断开发环境通常都是使用__DEV__来进行判断。

2.1 __DEV__的本质

在Vue源码中,到处都能看到__DEV__变量的身影。

但是你看不到__DEV__import 引入,甚至你都找不到在哪里定义了这个变量。

这个变量在 Vue 中代表开发环境。

当你在源码中看见 if(__DEV__) 代表这个if中的逻辑只有在开发环境才进入执行。

vue的构建工具是rollup,在rollup打包的时候会使用@rollup/plugin-replace 插件来替换源代码中的__DEV__变量,如下:

1
2
3
4
5
6
7
8
9
10
11
12
......
const replace = require('@rollup/plugin-replace')
......
// built-in vars
const vars = {
......
__DEV__: `process.env.NODE_ENV !== 'production'`,
......
}
......
config.plugins.push(replace(vars))
......

所以__DEV__的本质就是 proess.env.NODE_ENV!== 'production'

2.2 约定俗成的“process.env.NODE_ENV!==’production”

DEV 变量转换为 process.env.NODE_ENV !== 'production' 这样的条件表达式在现代前端构建工具中是一种常见的约定和实践。

原因有以下几点:

  1. 环境区分:通过 process.env.NODE_ENV 可以方便地在代码中区分开发环境(development)与生产环境(production)。例如,在开发环境中启用特定的警告、日志记录或者热加载功能;而在生产环境中则禁用这些功能以优化性能。
  2. Tree-shaking 和 UglifyJS:当打包工具如 Rollup 或 webpack 遇到 process.env.NODE_DEV !== 'production' 包裹的代码块时,在生产环境下会自动去除(tree-shake)其中仅在开发环境中需要的代码,因为该条件表达式最终会被编译器或压缩工具识别为始终为 false,因此包裹的代码不会被打包进最终产物。
  3. 标准化:许多框架和库都采用了这种模式来处理环境相关的逻辑,这样开发者可以统一遵循这个约定,减少配置和理解成本。
  4. 灵活配置:由于构建工具支持 .env 文件或其他方式来设置 NODE_ENV 的值,这使得项目配置更加灵活,团队成员可以根据实际需求快速切换不同环境下的行为。
  5. 跨平台兼容性process.env.NODE_ENV 作为一个标准 Node.js 环境变量接口,已经被广泛接受,并且在各种打包工具中都有相应的机制将其映射到最终浏览器执行的代码中,确保了跨平台的一致性。

一般项目在开发环境中将process.env.NODE_ENV设置为development,在生产环境中设置为production

所以通过process.env.NODE_ENV !== 'production就可以判断现在是处于什么环境下。

2.3 在本地环境配置__DEV__

因为我们这门课程是写一个完整版的vue2。

所以我们也会使用__DEV__这个变量代表开发环境。

我们本地的项目是使用 vue-cli 搭建的。

可以在 webpack配置文件 vue.config.js 文件新增一个配置达到这个目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// vue.config.js
const { defineConfig } = require('@vue/cli-service');
const webpack = require('webpack');
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false,
configureWebpack: {
plugins: [
new webpack.DefinePlugin({
__DEV__: process.env.NODE_ENV === 'development',
}),
],
},
})

DefinePlugin 插件的主要作用是在编译时将预定义的值替换到源代码中的特定变量。

我们执行yarn start启动这个项目打印一下__DEV__为 true即为成功。

1
console.log(__DEV__); // true

本站总访问量