vue2.x源码解析三——源码构建

1.Rollup介绍

官网:https://github.com/rollup/rollup
Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下。

1.2rollup和webpack都是区别

webpack更加强大 可以将图片等都便以为css
rollup 更适合于js库的编译,只适用于js部分,别的文件是不管的,并且更加友好

2.Vue.js 源码构建脚本

通常一个基于 NPM 托管的项目,在它的根目录下都会有一个 package.json 文件,它是对项目的描述文件,它的内容实际上是一个标准的 JSON 对象。

我们学习阅读一个项目的源码时,首先当然要看它的package.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 的执行脚本的script 字段
这里总共有 3 条命令,作用都是构建 Vue.js,后面 2 条是在第一条命令的基础上,添加一些环境参数。

当在命令行运行 npm run build 的时候,实际上就会执行 node scripts/build.js,接下来我们来看看它实际是怎么构建的。

3.构建过程

构建运行 node scripts/build.js

3.1 scripts文件下build.js 中:

3.1.1 大致过程

  • 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平台

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
  })
}
  • 3.对过滤后的vue.js文件进行build,文件中用的就是build函数

3.1.2 完整代码:

// 引入包
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) {}

3.2 scripts文件下config.js:

build.js在该文件中拿到了每一种vue.js的相关配置

3.2.1 大致过程

  • 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)
}

3.2.3 具体代码:

引入的文件或组件
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)
}

3.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')
}

3.完整过程

npm run build

执行
script/build.js

build.js————-引入config.js,将配置进行过滤,过滤后再通过build函数进行真正的构建过程

config.js————引入alias.js,配置不同vue.js的配置,根据不同vue.js的配置输出不同构建工具rollup真正的配置

alias.js————–提供文件的路径映射

猜你喜欢

转载自blog.csdn.net/haochangdi123/article/details/80865275