菜鸟初探Vue源码(一)-- 源码构建

869 阅读3分钟

文章中使用到的是Vue源码的2.6.11版本,初次接触到源码,水平有限,着实有些吃力,写文章的目的也是为了帮助自己更清晰地理解。还望前辈们指教。 关于vue源码的获取,可以从github上clone至本地,点击下载源码

下载解压完毕后,先对源码目录结构初步分析,如下:

  • dist:经过打包构建之后的源码

  • flow:类型检查相关文件(当前版本的vue源码使用的是flow做类型检查)

  • scripts:与npm的scripts字段相关的一些文件

  • src:存放vue源码的文件夹(重点分析)

    • compiler 编译相关
    • core 核心代码
    • platforms 对不同平台的支持
    • server 服务端渲染
    • sfc .vue文件解析
    • shared 共享工具方法

对目录结构有了大致了解后,就开启了本次探索的旅程。

Vue.js 源码是基于Rollup构建的。因为vue是发布在npm上的,打开package.json文件,文件中记录了对npm包的详细描述,其中module字段指定npm包的ESM规范入口文件(main字段指定commonjs规范入口文件)。在进行项目构建时,我们执行npm run build命令实际上是执行scripts字段的build字段(此处为node scripts/build.js

我们打开scripts文件夹下的build.js,代码如下:

let builds = require('./config').getAllBuilds()

可以看到代码中引入了config.js中的getAllBuilds方法,我们打开config.js,最后一句代码如下:

exports.getAllBuilds = () => Object.keys(builds).map(genConfig)

getAllBuilds方法中涉及的两个参数buildsgenConfig又是什么呢?同样在config.js中我们查找builds的定义可以了解到,builds对象自定义了不同版本源码的entry、dest、format、env等配置,用于Rollup构建

const builds = {
    'web-runtime-cjs-dev': {
        entry: resolve('web/entry-runtime.js'),
        dest: resolve('dist/vue.runtime.common.dev.js'),
        format: 'cjs',
        env: 'development',
        banner
    },
    //...此处省略多行代码
}

genConfig函数返回的才是真正的Rollup配置对象

function genConfig (name) {
  const opts = builds[name]
  const config = {
    input: opts.entry,
    external: opts.external,
    plugins: [
      flow(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    },
    onwarn: (msg, warn) => {
      if (!/Circular/.test(msg)) {
        warn(msg)
      }
    }
  }
  return config
}

至此,我们就知道上面build.js中做了什么(获取到所有构建所需的配置)。紧接着根据package.json中scripts字段的参数值进行一层过滤,把不需要打包的过滤掉。调用build方法进行逐个构建。

function build (builds) {
  let built = 0
  const total = builds.length
  const next = () => {
    buildEntry(builds[built]).then(() => {
      built++
      if (built < total) {
        next()
      }
    }).catch(logError)
  }
  next()
}

以上就是Vue.js源码构建的大致过程。

紧接着我们讨论一下Runtime-Only和Runtime-Compiler的问题,相信部分同学在升级至Vue/CLI3后,遇到过下图所示报错。可以通过如下方式解决:

  1. 在根目录下添加配置文件vue.config.js,书写如下配置
const path = require('path')
function resolve (dir) {
    return path.join(__dirname,dir)
  }
module.exports = {
    configureWebpack: config => {
        config.resolve = {
           extensions: ['.js', '.vue', '.json',".css"],
            alias: {
              'vue$': 'vue/dist/vue.esm.js',
              '@': resolve('src'),
            }
        }
    }
}
  1. 将template方式改为render函数
<!--render函数-->
new Vue({
   render: h => h(App),
}).$mount('#app')

<!--template-->
new Vue({
  el: '#app',
  template: '<App/>',
  components: { App }
})

那为什么会存在这样的问题呢,我们来一探究竟。

其实在使用Vue/CLI2创建项目时,也会询问我们使用Runtime-only版本还是Runtime+Compiler版本。这两个版本又有什么区别呢?

  • Runtime-only版本字面意思是只包含运行时版本,是在构建时通过webpack的vue-loader工具将模板预编译成JavaScript,也就是进行了预编译,在最终打好的包里实际上是已经编译好的,在浏览器中可直接运行
  • Runtime+Compiler字面意思为运行时+编译器,是不在打包时进行编译的,是在客户端(浏览器)运行时进行编译的,所以要使用带编译器的完整版本

因为在Vue.js 2.0中,最终渲染都是通过render函数,如果写template属性,则需要编译成render函数,那么这个编译过程会发生在运行时,所以需要带有编译器的版本。很显然,这个编译过程对性能会有一定损耗,所以通常我们更推荐使用 Runtime-Only 的 Vue.js。