1. 程式人生 > >vue的原始碼學習之三——原始碼構建

vue的原始碼學習之三——原始碼構建

                                 原始碼構建    

  • 介紹

        版本:2.5.17

  • 專案Rollup介紹

      官網:https://github.com/rollup/rollup 
       Vue.js 原始碼是基於 Rollup 構建的,它的構建相關配置都在 scripts 目錄下。

    • Rollup和webpack都是區別

              webpack更加強大 可以將圖片,css等都解析為js。 
              rollup 更適合於js庫的編譯,只適用於js部分,別的檔案是不管的,並且更加友好 

  • 構建指令碼

       通常一個基於 NPM 託管的專案,在它的根目錄下都會有一個 package.json 檔案,它是對專案的描述檔案,它的內容實際上是一個標準的 JSON 物件。

      vue的 package.json我們將其簡寫為:

{
  "name": "vue",
  "version": "2.5.17-beta.0",
  "description": "Reactive, component-oriented view layer for modern web interfaces.",
  "main": "dist/vue.runtime.common.js",
  "module": "dist/vue.runtime.esm.js",
  "script": {
  "build": "node scripts/build.js",
    "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer",
    "build:weex": "npm run build --weex"
}

     當在命令列執行 npm run build 的時候,實際上就會執行 node scripts/build.js

  • 構建過程

    構建執行 node scripts/build.js 

    • scripts檔案下build.js 中

      • 主要過程

             1.通過let builds = require(‘./config’).getAllBuilds()拿到每一種vue.js的相關配置

            2.對所有的配置進行過濾,將package.json中的命令,例如npm run build – web-runtime-cjs,web-server-renderer,就是過濾掉不是web-runtime-cjs,web-server-renderer的,如果沒有傳引數,例如npmrun build 那麼就只將weex過濾,只打包web平臺

      // 拿到所有的構建的配置
      let builds = require('./config').getAllBuilds()
      
      // 將配置進行過濾,將package.json中的命令,例如npm run build -- web-runtime-cjs,web-server-renderer,
      // 就是過濾掉不是web-runtime-cjs,web-server-renderer的,如果沒有傳引數,例如npm run build 那麼就只將weex過濾,只打包web平臺
      if (process.argv[2]) {
        const filters = process.argv[2].split(',')
        builds = builds.filter(b => {
          return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
        })
      } else {
        // filter out weex builds by default
        builds = builds.filter(b => {
          return b.output.file.indexOf('weex') === -1
        })
      }
      
      build(builds)

          3.對過濾後的vue.js檔案進行build,檔案中用的就是build函式

               4.build.js完整程式碼 

    // 引入包
    const fs = require('fs')
    const path = require('path')
    const zlib = require('zlib')
    const rollup = require('rollup')
    const uglify = require('uglify-js')
    
    if (!fs.existsSync('dist')) {
      fs.mkdirSync('dist')
    }
    
    // 拿到所有的構建的配置
    let builds = require('./config').getAllBuilds()
    
    // 將配置進行過濾,將package.json中的命令,例如npm run build -- web-runtime-cjs,web-server-renderer,就是過濾掉不是web-runtime-cjs,web-server-renderer的,如果沒有傳引數,例如npm run build 那麼就只將weex過濾,只打包web平臺
    if (process.argv[2]) {
      const filters = process.argv[2].split(',')
      builds = builds.filter(b => {
        return filters.some(f => b.output.file.indexOf(f) > -1 || b._name.indexOf(f) > -1)
      })
    } else {
      // filter out weex builds by default
      builds = builds.filter(b => {
        return b.output.file.indexOf('weex') === -1
      })
    }
    
    // 過濾後再通過build函式進行真正的構建過程
    build(builds)
    
    // build 在去呼叫buildEntry函式
    function build (builds) {
      let built = 0
      const total = builds.length
      const next = () => {
        buildEntry(builds[built]).then(() => {
          built++
          if (built < total) {
            next()
          }
        }).catch(logError)
      }
      next()
    }
    
    
    function buildEntry (config) {
      const output = config.output
      const { file, banner } = output
      const isProd = /min\.js$/.test(file)
      return rollup.rollup(config)
        .then(bundle => bundle.generate(output))
        .then(({ code }) => {
          // 如果是 min.js 結尾的js就再進行一次壓縮
          if (isProd) {
            var minified = (banner ? banner + '\n' : '') + uglify.minify(code, {
              output: {
                ascii_only: true
              },
              compress: {
                pure_funcs: ['makeMap']
              }
            }).code
            // 呼叫write方法
            return write(file, minified, true)
          } else {
            return write(file, code)
          }
        })
    }
    
    function write (dest, code, zip) {
      return new Promise((resolve, reject) => {
        function report (extra) {
          console.log(blue(path.relative(process.cwd(), dest)) + ' ' + getSize(code) + (extra || ''))
          resolve()
        }
    
      // 呼叫fs.writeFile方法將檔案生成到dist目錄下
        fs.writeFile(dest, code, err => {
          if (err) return reject(err)
          if (zip) {
            zlib.gzip(code, (err, zipped) => {
              if (err) return reject(err)
              report(' (gzipped: ' + getSize(zipped) + ')')
            })
          } else {
            report()
          }
        })
      })
    }
    
    function getSize (code) {}
    
    function logError (e) {}
    
    function blue (str) {}
    

     

    • scripts檔案下config.js 

               build.js在該檔案中拿到了每一種vue.js的相關配置

              大致過程 :       

                    1.通過const aliases = require(‘./alias’),拿到大部分檔案的路徑

                    2.對resolve方法進行封裝,配合aliases檔案,其實就是獲取檔案的路徑,如下面的entry入口檔案

const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

               3.const builds = {},不同版本vue.js的配置

               4.function genConfig (name){} 真正的構建工具rollup的配置,每一種vue.js就會對用不同的配置

              5.將通過vue.js的配置而得到的rollup的具體配置輸出

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

          具體程式碼

引入的檔案或元件
const path = require('path')
const buble = require('rollup-plugin-buble')
const alias = require('rollup-plugin-alias')
const cjs = require('rollup-plugin-commonjs')
const replace = require('rollup-plugin-replace')
const node = require('rollup-plugin-node-resolve')
const flow = require('rollup-plugin-flow-no-whitespace')
const version = process.env.VERSION || require('../package.json').version
const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version

// 註釋內容,也就是程式碼上面對該程式碼的說明
const banner =
  '/*!\n' +
  ' * Vue.js v' + version + '\n' +
  ' * (c) 2014-' + new Date().getFullYear() + ' Evan You\n' +
  ' * Released under the MIT License.\n' +
  ' */'

const weexFactoryPlugin = {
  intro () {
    return 'module.exports = function weexFactory (exports, document) {'
  },
  outro () {
    return '}'
  }
}

// 引入./alias檔案
const aliases = require('./alias')
// 對resolve方法進行封裝,其實就是獲取檔案的路徑,如下面的entry入口檔案
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

// 不同版本vue.js的配置
const builds = {
  // Runtime only (CommonJS). Used by bundlers e.g. Webpack & Browserify
  'web-runtime-cjs': {
    // 入口檔案,resolve函式呼叫aliases檔案,最終拼接該s檔案真正的路徑,如果aliases檔案中沒有定義,那麼就會走resolve函式的else
    entry: resolve('web/entry-runtime.js'),
    // 生成目標檔案
    dest: resolve('dist/vue.runtime.common.js'),
    // 構建出來的檔案的格式
    format: 'cjs',
    // 關於檔案的註釋
    banner
  },
  // Runtime+compiler CommonJS build (CommonJS)
  'web-full-cjs': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.common.js'),
    format: 'cjs',
    alias: { he: './entity-decoder' },
    banner
  },
  // runtime-only production build (Browser)
  'web-runtime-prod': {
  },
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
  },
  // Runtime+compiler production build  (Browser)
  'web-full-prod': {
  },
  // Web compiler (CommonJS).
  'web-compiler': {
  },
  // Web compiler (UMD for in-browser use).
  'web-compiler-browser': {
  },
  // Web server renderer (CommonJS).
  'web-server-renderer': {
  },
  'web-server-renderer-basic': {
  },
  'web-server-renderer-webpack-server-plugin': {
  },
  'web-server-renderer-webpack-client-plugin': {
  },
  // Weex runtime factory
  'weex-factory': {
  },
  // Weex runtime framework (CommonJS).
  'weex-framework': {
  },
  // Weex compiler (CommonJS). Used by Weex's Webpack loader.
  'weex-compiler': {
  }
}

// 構建工具rollup真正的配置,上面的是每一中版本的vue.js的配置,下面才是構建工具rollup真正的配置
function genConfig (name) {
  const opts = builds[name]
  const config = {
    input: opts.entry,
    external: opts.external,
    plugins: [
      replace({
        __WEEX__: !!opts.weex,
        __WEEX_VERSION__: weexVersion,
        __VERSION__: version
      }),
      flow(),
      buble(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    }
  }

  if (opts.env) {
    config.plugins.push(replace({
      'process.env.NODE_ENV': JSON.stringify(opts.env)
    }))
  }

  Object.defineProperty(config, '_name', {
    enumerable: false,
    value: name
  })

  return config
}

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}
  • scripts檔案下alias.js

       真實路徑的對映, 例如 config.js 中的builds物件

entry: resolve('web/entry-runtime.js'),

     web指的就是alias.js中 的

web: resolve('src/platforms/web'),

    完整程式碼:

const path = require('path')

const resolve = p => path.resolve(__dirname, '../', p)
// 其實就是真實路徑的對映
module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  entries: resolve('src/entries'),
  sfc: resolve('src/sfc')

 

  •  完整過程

npm run build

      執行 
      script/build.js

      build.js————-引入config.js,將配置進行過濾,過濾後再通過build函式進行真正的構建過程

     config.js————引入alias.js,配置不同vue.js的配置,根據不同vue.js的配置輸出不同構建工具rollup真正的配置

     alias.js————–提供檔案的路徑對映

學習文件:https://ustbhuangyi.github.io/vue-analysis/components/lifecycle.html