Vue不一樣編譯輸出文件的區別

Vue源碼是選用了rollup做爲bundler,看Vue的源碼時發現:npm script對應了不一樣的構建選項。這也對應了最後打包構建後產出的不一樣的包。javascript

不一樣於其餘的libraryVue爲何要在最後的打包構建環節輸出不一樣類型的包呢?接下來咱們經過Vue的源碼以及對應的構建配置中簡單的去分析下。html

因爲Vue是基於rollup進行構建的,咱們先來簡單瞭解下rollup這個bundlerrollup是默認使用ES Module規範而非CommonJS,所以若是你在你的項目中使用rollup做爲構建工具的話,那麼能夠放心的使用ES Module規範,可是若是要引入只遵循了CommonJs規範的第三包的話,還須要使用相關的插件,插件會幫你將CommonJs規範的代碼轉爲ES Module。得益於ES Modulerollup在構建前進行靜態分析,進行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 Moduleexpress

接下來咱們就看看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.xBrowserify直接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 Modulebundler直接加載,如webpack2rollup

第四種構建方式:

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

文檔請戳我

相關文章
相關標籤/搜索