一个优秀项目,是一个不断打磨的过程,Vue 3.0(Vue-Next)从2018年9月19日尤雨溪老师第一次提交原型代码至今,已经历时2年以上,此刻(rc.1版本刚发出)master分支上已经包含2354次提交。

“预知大道,必先为史”,寻着Vue 3.0的每一次提交,理解其细节,观摩其演化,从Vue 3.0这个优秀的项目上学习/参悟软件工程的“大道”。

代码打包是现代前端项目中重要的一环,这篇文字会记录Vue 3.0在打包上的迭代过程(先暂且叫打包吧,期望后续扩展到构建)。

std-001-369997bb

尤雨溪老师于2018年9月19日上午11点35分,在Github第一次提交代码(3401f6b4),当天最后一次提交是夜里2点(369997bb),让整个原型项目顺利运行起来(npm run build),也以这次提交作为整个学习的开始。

一天工作了15个小时,第二天上午11点43分又有了新的提交,尤雨溪老师优秀并且努力,在自己爱的工作义无反顾的投入——代码要学习,努力和勤奋也要学习。

源代码目录结构

打包的依据是源代码库的设计,Vue设计为多包存储库,是典型的 Monorepo(集中管理)风格,便于一同部署上线,共享构建以及配置脚本等核心流程。

Lerna 是一个优化使用 git 和 npm 管理多包存储库的工作流工具,用于管理具有多个包的 JavaScript 项目。Vue中也使用了这个工具。

|--packages
|  |--vue
|  |  |--index.ts(Vue项目入口文件)
|  |--renderer-dom
|  |  |--index.ts
|  |  |--modules/attrs.ts
|  |  |--modules/class.ts
|  |  |--modules/events.ts
|  |  |--modules/props.ts
|  |  |--modules/style.ts
|  |  |--nodeOps.ts
|  |  |--patchData.ts
|  |  |--teardownVNode.ts
|  |--renderer-server(占位留空)
|  |  |--index.ts
|  |--core
|  |  |--index.ts
|  |  |--component.ts
|  |  |--componentComputed.ts
|  |  |--componentOptions.ts
|  |  |--componentProps.ts
|  |  |--componentProxy.ts
|  |  |--componentState.ts
|  |  |--componentUtils.ts
|  |  |--componentWatch.ts
|  |  |--createRenderer.ts
|  |  |--errorHandling.ts
|  |  |--flags.ts
|  |  |--h.ts
|  |  |--utils.ts
|  |  |--vdom.ts
|  |--observer
|  |  |--index.ts
|  |  |--autorun.ts
|  |  |--baseHandlers.ts
|  |  |--collectionHandlers.ts
|  |  |--computed.ts
|  |  |--lock.ts
|  |  |--operations.ts
|  |  |--state.ts
|  |--scheduler
|  |  |--index.ts
|  |--compiler(占位留空)
|  |  |--index.ts

完整的文件依赖关系如下图所示: Vue Next 369997bb 依赖关系图

打包

项目的打包命令,依据传统定义在package.json的scripts中,包含开发环境("dev": "node scripts/dev.js")和生产环境("build": "node scripts/build.js")两条命令。

Vue 3.0的构建基于rollup。Rollup 是一个 JavaScript 模块打包器,使用 ES6 的标准 ES 模块方案(不是以前的特殊解决方案,如 CommonJS 和 AMD),以自由、无缝地使用 library 中最有用独立函数(Tree-shaking),而不必携带其他未使用的代码。

Rollup 对代码中的 import 进行静态分析,排除任何未实际使用(和没有效果 no effect side)的代码。从而实现像 tree-shaking 的优化,并提供诸如循环引用和动态绑定等高级功能。减轻项目的大小膨胀。

Tree-shaking, 也被称为 "live code inclusion," 它是清除实际上并没有在给定项目中使用的代码的过程,但是它可以更加高效。

Rollup 支持 esm、cjs(CommonJS)、umd、iife 等格式发布。

开发环境的构建过程定义在scripts/dev.js文件中,核心代码如下,借助execa创建命令行应用来执行rollup:

execa(
  'rollup',
  ['-wc', '--environment', `TARGET:${target},FORMATS:${formats || 'global'}`],
  {
    stdio: 'inherit'
  }
)

rollup.config.js文件中主要是不同环境(开发环境和生产环境)、不同文件不同配置参数的生成,在开发环境的默认构建中,其最终使用的配置如下:

  1. TypeScript的插件定义;
  2. 内部子包别名的定义;
  3. 全局占位变量的定义。

可以在命令行中执行rollup -c rollup.config.dev.js --environment TARGET:renderer-dom,FORMATS:global引用下面的配置生成基本相同的结果(备注:和“[‘-c’, ‘./rollup.config.dev.js’, ‘–environment’, TARGET:${target},FORMATS:${formats || 'global'}]”略有差别),开发环境默认生成renderer-dom.global.js一个文件。

const ts = require('rollup-plugin-typescript2')
const alias = require('rollup-plugin-alias')
const replace = require('rollup-plugin-replace')

module.exports = [
  {
    input: './packages/renderer-dom/src/index.ts',
    external: [],
    plugins: [
      ts({
        check: false,
        tsconfig: './tsconfig.json',
        cacheRoot: './node_modules/.rts2_cache',
        tsconfigOverride: {
          compilerOptions: {
            declaration: false
          }
        }
      }),
      alias({
        '@vue/compiler': './packages/compiler',
        '@vue/core': './packages/core',
        '@vue/observer': './packages/observer',
        '@vue/renderer-dom': './packages/renderer-dom',
        '@vue/renderer-server': './packages/renderer-server',
        '@vue/scheduler': './packages/scheduler'
      }),
      replace({
        __DEV__: true,
        __COMPAT__: false
      })
    ],
    output: {
      file: './packages/renderer-dom/dist/renderer-dom.global.js',
      name: 'VueDOMRenderer',
      format: 'iife'
    },
    onwarn: (msg, warn) => {
      if (!/Circular/.test(msg)) {
        warn(msg)
      }
    }
  }
]

生产环境的构建,每个子包输出了esm、cjs(CommonJS)和iife 3种格式,每种格式包含压缩和非压缩两种文件(compiler、core、renderer-server、scheduler 4个字报只包含esm和cjs两种),执行过程和开发环境基本一致,不再赘述。

其他

scripts中有个bootstrap.js文件,用来生成分包中的package.json, README.md,.npmignore,index.js,index.ts等文件。虽然此时只有6个分包,而且项目的后续迭代中,分包增加的数量是有限的,但依然用代码(node scripts/bootstrap.js)代替了ctrl + cctrl + v,这是优秀的工程师对代码的信仰吧。

说明

上面的文字中,可能是错的,伙伴们以源代码为准,斟酌/甄别着看。如果伙伴们发现错误,请回复留言,我会及时修改。