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
函數。