Vue
源码是选用了rollup
做为bundler
,看Vue
的源码时发现:npm script
对应了不一样的构建选项。这也对应了最后打包构建后产出的不一样的包。javascript
不一样于其余的library
,Vue
为何要在最后的打包构建环节输出不一样类型的包呢?接下来咱们经过Vue
的源码以及对应的构建配置中简单的去分析下。html
因为Vue
是基于rollup进行构建的,咱们先来简单了解下rollup
这个bundler
:rollup
是默认使用ES Module
规范而非CommonJS
,所以若是你在你的项目中使用rollup
做为构建工具的话,那么能够放心的使用ES Module
规范,可是若是要引入只遵循了CommonJs
规范的第三包的话,还须要使用相关的插件,插件会帮你将CommonJs
规范的代码转为ES Module
。得益于ES Module
,rollup
在构建前进行静态分析,进行tree-shaking
。关于tree-shaking
的描述请戳我。在构建输出环节,rollup
提供了多种文件输出类型:vue
iife
: 当即执行函数java
cjs
: 遵循CommonJs Module
规范的文件输出node
amd
: 遵循AMD Module
规范的文件输出webpack
umd
: 支持外链
/CommonJs Module
/AMD Module
规范的文件输出web
es
: 将多个遵循ES6 Module
的文件编译成1个ES6 Module
express
接下来咱们就看看Vue
的使用rollup
进行构建的几个不一样的版本(使用于browser
的版本)。npm
npm run dev 对应 rollup -w -c build/config.js --environment TARGET:web-full-dev
rollup
对应的配置信息为:json
// Runtime+compiler development build (Browser) 'web-full-dev': { entry: resolve('web/runtime-with-compiler.js'), dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner },
开发环境下输出的umd
格式的代码,入口文件是runtime-with-compiler.js
,这个入口文件中是将Vue
的构建时
和运行时
的代码都统一进行打包了,经过查看这个入口文件,咱们注意到
... import { compileToFunctions } from './compiler/index' ... const mount = Vue.prototype.$mount Vue.prototype.$mount = function () { }
咱们发现,这个文件当中,首先将原来定义的Vue.prototype.$mount
方法缓存起来,而后将这个方法进行重写,重写后的方法当中,首先判断是否有自定义的render
函数,若是有自定义的render
函数的话,Vue
不会经过自带的compiler
对模板进行编译并生成render
函数。可是若是没有自定义的render
函数,那么会调用compiler
对你定义的模板进行编译,并生成render
函数,因此经过这个rollup
的配置构建出来的代码既支持自定义render
函数,又支持template
模板编译:
// 将模板编译成render函数,并挂载到vm实例的options属性上 const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, delimiters: options.delimiters }, this) options.render = render options.staticRenderFns = staticRenderFns ... // 调用以前缓存的mount函数,TODO: 关于这个函数里面发生了什么请戳我 return mount.call(this, el, hydrating)
接下来看第二种构建方式:
npm run dev:cjs 对应的构建脚本 rollup -w -c build/config.js --environment TARGET:web-runtime-cjs
rollup
对应的配置信息为:
// Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify 'web-runtime-cjs': { entry: resolve('web/runtime.js'), dest: resolve('dist/vue.runtime.common.js'), format: 'cjs', banner }
最后编译输出的文件是遵循CommonJs Module
同时只包含runtime
部分的代码,它能直接被webpack 1.x
和Browserify
直接load
。它对应的入口文件是runtime.js
:
import Vue from './runtime/index' export default Vue
这里没有重写Vue.prototye.$mount
方法,所以在vm
实例的生命周期中,进行到beforeMount
阶段时:
// vm挂载的根元素 if (vm.$options.el) { vm.$mount(vm.$options.el) }
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { // vm.$el为真实的node vm.$el = el // 若是vm上没有挂载render函数 if (!vm.$options.render) { // 空节点 vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } // 钩子函数 callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`${name} patch`, startTag, endTag) } } else { // updateComponent为监听函数, new Watcher(vm, updateComponent, noop) updateComponent = () => { // Vue.prototype._render 渲染函数 // vm._render() 返回一个VNode // 更新dom // vm._render()调用render函数,会返回一个VNode,在生成VNode的过程当中,会动态计算getter,同时推入到dep里面 // 在非ssr状况下hydrating为false vm._update(vm._render(), hydrating) } } // 新建一个_watcher对象 // vm实例上挂载的_watcher主要是为了更新DOM // 在实例化watcher的过程当中,就会执行updateComponent,完成对依赖的变量的收集过程 // vm/expression/cb vm._watcher = new Watcher(vm, updateComponent, noop) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
首先判断vm
实例上是否认义了render
函数。若是没有,那么就会新建一个新的空vnode
并挂载到render
函数上。此外,若是页面的渲染是经过传入根节点
的形式:
new Vue({ el: '#app' })
Vue
便会打出log
信息:
warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.'
意思就是你当前使用的是只包含runtime
打包后的代码,模板的编译器(即构建时)的代码并不包含在里面。所以,你不能经过挂根节点或者是声明式模板的方式去组织你的html
内容,而只能使用render
函数去书写模板内容。不过报错信息里面也给出了提示信息就是,你还能够选择pre-compile
预编译工具去将template
模板编译成render
函数(vue-loader
就起到了这个做用)或者是使用包含了compiler
的输出包,也就是上面分析的即包含compiler
,又包含runtime
的包。
第三种构建方式:
npm run dev:esm 对应的构建脚本为: rollup -w -c build/config.js --environment TARGET:web-runtime-esm
入口文件及最后构建出来的代码内容和第二种同样,只包含runtime
部分的代码,可是输出代码是遵循ES Module
规范的。能够被支持ES Module
的bundler
直接加载,如webpack2
和rollup
。
第四种构建方式:
npm run dev:compiler 对应的构建脚本为: rollup -w -c build/config.js --environment TARGET:web-compiler
不一样于前面3种构建方式:
// Web compiler (CommonJS). 'web-compiler': { entry: resolve('web/compiler.js'), dest: resolve('packages/vue-template-compiler/build.js'), format: 'cjs', external: Object.keys(require('../packages/vue-template-compiler/package.json').dependencies) },
这一构建对应于将关于Vue
模板编译的成render
函数的compiler.js
单独进行打包输出。最后输出的packages/vue-template-compiler/build.js
文件会单独做为一个node_modules
进行发布,在你的开发过程当中,若是使用了webpack
做为构建工具,以及vue-loader
,在开发构建环节,vue-loader
便会经过web compiler
去处理你的*.vue
文件中的模板<template>
当中的内容,将这些模板字符串编译为render
函数。