1. 程式人生 > >[轉] 編譯輸出文件的區別

[轉] 編譯輸出文件的區別

-c 鉤子函數 with ade led conf 多個 工具 try

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

不同於其他的 library , Vue 為什麽要在最後的打包構建環節輸出不同類型的包呢?接下來我們通過 Vue 的源碼以及對應的構建配置中簡單的去分析下。

由於 Vue 是基於 rollup 進行構建的,我們先來簡單了解下 rollup 這個 bundler : rollup 是默認使用 ES Module 規範而非 CommonJS ,因此如果你在你的項目中使用 rollup 作為構建工具的話,那麽可以放心的使用 ES Module 規範,但是如果要引入只遵循了 CommonJs 規範的第三包的話,還需要使用相關的插件,插件會幫你將 CommonJs 規範的代碼轉為 ES Module 。得益於 ES Module , rollup 在構建前進行靜態分析,進行 tree-shaking 。關於 tree-shaking 的描述 請戳我 。在構建輸出環節, rollup 提供了多種文件輸出類型:

  • iife : 立即執行函數

  • cjs : 遵循 CommonJs Module 規範的文件輸出

  • amd : 遵循 AMD Module 規範的文件輸出

  • umd : 支持 外鏈 / CommonJs Module / AMD Module 規範的文件輸出

  • es : 將多個遵循 ES6 Module 的文件編譯成1個 ES6 Module

接下來我們就看看 Vue 的使用 rollup 進行構建的幾個不同的版本(使用於 browser 的版本)。

npm run dev 對應

rollup -w -c build/config.js --environment TARGET:web-full-dev

rollup 對應的配置信息為:

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

[轉] 編譯輸出文件的區別