vue源码构建代码分析
这是xue源码学习记录,如有错误请指出,谢谢!相互学习相互进步。
vue源码目录为
vue ├── src #vue源码 ├── flow #flow定义的数据类型库(vue通过flow来检测数据类型是否正确) ├── examples #demo ├── scripts #vue构建命令 ├── ...
vue内部代码模块比较清晰,这边主要分析scripts内部代码,讲解vue是如何进行构建的.
首先你必须要懂一些rollup,vue内部是通过rollup来进行构建的,rollup是一款js的构建工具,
将各个小模块打包成一个总的模块(只针对js文件,比较轻量,不会有css,img等压缩,比较适合开发插件,
如果是ui组件库的话,还是webpack构建会比较好。)
rollup说明文档:https://rollupjs.cn/
文件主要是scripts下的alias.js,config.js和build.js三个文件组成
alias
主要就是提供文件对应的路径
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'), test: resolve('src/test') // 这个是测试目录是自己添加的 }
其中test是我自己加的,为了测试打包
config
config是为了提供打包的基础配置(即rollup打包配置文件格式),由于打包内容比较多,
所以做成可配置的
const path = require('path') const buble = require('rollup-plugin-buble') // 提供modules名称的 alias 和reslove 功能 const alias = require('rollup-plugin-alias') // 将CommonJS模块转换为 ES2015供Rollup 处理 const cjs = require('rollup-plugin-commonjs') // 变量替换,可以将动态设置的变量提取出来在配置文件中设置 const replace = require('rollup-plugin-replace') // 帮助 Rollup 查找外部模块,然后安装 const node = require('rollup-plugin-node-resolve') const flow = require('rollup-plugin-flow-no-whitespace') const version = process.env.VERSION || require('../package.json').version // 下面是weex服务器端代码,不需要管 // const weexVersion = process.env.WEEX_VERSION || require('../packages/weex-vue-framework/package.json').version // 这边是打包完成后模块外部首行注释代码 const banner = `/* * test-vue.js v${version} * (C) 2014-${new Date().getFullYear()} Enan You * @author zhengjie */ ` // 获取文件夹路径别名 const aliases = require('./alias') // 寻找路径 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打包文件去除,替换成知己的一个测试文件 const builds = { 'test-cjs': { entry: resolve('test/main.js'), dest: resolve('dist/test-cjs.js'), format: 'cjs', // csj格式 module.exports banner }, 'test-es': { entry: resolve('test/main.js'), dest: resolve('dist/test-es.js'), format: 'es', // es格式 export default banner }, 'test-umd': { entry: resolve('test/main.js'), dest: resolve('dist/test-umd.js'), format: 'umd', // 浏览器格式 return banner } } // 根据上面builds打包转换成rollup打包格式 function getConfig(name) { const opts = builds[name] const config = { input: opts.entry, external: opts.external, plugins: [ replace({ __WEEX__: !!opts.weex, __WEEX_VERSION__: null, __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 = getConfig(process.env.TARGET) } else { exports.getBuild = getConfig exports.getAllBuilds = () => Object.keys(builds).map(getConfig) }
其中里面的builds已被替换成我自己的测试文件,用于测试打败es,模块化和浏览器的不同格式。
build
build文件就是根据配置文件进行打包,打包模式分为全部打包,或者是可配置打包,
如果运行npm run build,将会打包所有的配置,
而运行npm run build '参数', 则根据参数配置进行打包
const fs = require('fs') const path = require('path') const zlib = require('zlib') const rollup = require('rollup') const uglify = require('uglify-js') // 检测是否有dist文件 if (!fs.existsSync('dist')) { fs.mkdirSync('dist') } let builds = require('./config').getAllBuilds() // build后面输入的参数 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 { // 这边过滤出weex,不需要管 builds = builds.filter(b => { return b.output.file.indexOf('weex') === -1 }) } // 把需要打包的打包出来 build(builds) // 打包函数 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}) => { // 压缩我文件 if (isProd) { var minified = (banner ? banner + '\n' : '') + uglify.minify(code, { output: { ascii_only: true }, compress: { pure_funcs: ['makeMap'] } }).code 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(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) { return (code.length / 1024).toFixed(2) + 'kb' } // 输入错误信息 function logError (e) { console.log(e) } function blue (str) { return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m' }
测试文件
main.js
import foo from './foo'; export default function () { console.log(foo) }
foo.js
export default "hello rollup"
通过npm run build打包测试,会在dist文件打包出test-cjs.js,test-es.js,test-umd.js
文件内容如下:
test-cjs.js
/* * test-vue.js v1.0.0 * (C) 2014-2018 Enan You * @author zhengjie */ 'use strict'; var foo = "hello rollup" function main () { console.log(foo); } module.exports = main;
test-es.js
/* * test-vue.js v1.0.0 * (C) 2014-2018 Enan You * @author zhengjie */ var foo = "hello rollup" function main () { console.log(foo); } export default main;
test-umd.js
/* * test-vue.js v1.0.0 * (C) 2014-2018 Enan You * @author zhengjie */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); }(this, (function () { 'use strict'; var foo = "hello rollup" function main () { console.log(foo); } return main; })));
这个构建的过程不难,比起webpack的配置文件要容易懂很多
懂得了vue的构建,接下来就可以开始vue源码的学习了