准备粗略地阅读下Vue,跟网上别人家的源码解读文章不同的是,这个系列是完全以第一次去读代码的视角带大家一起去读的,而非看完源码后以上帝的总结视角来写的系列文章。
要看我们就直接找比较新的版本看,fork vue代码到我们自己的仓库上,clone到本地后,执行git tag 发现最新的版本是2.5.16,所以在本地新开一个tag-v2.5.16分支方便阅读以及后续commit(如果要commit的话):
git checkout -b tag-v2.5.16
执行完,我们的本地项目就已经切换到新的tag-v2.5.16 分支上了。
拿到一个项目,我们首先看一下package.json 文件里的scripts 字段:
"scripts": { "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev", "dev:cjs": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-cjs", "dev:esm": "rollup -w -c scripts/config.js --environment TARGET:web-runtime-esm", "dev:test": "karma start test/unit/karma.dev.config.js", "dev:ssr": "rollup -w -c scripts/config.js --environment TARGET:web-server-renderer", "dev:compiler": "rollup -w -c scripts/config.js --environment TARGET:web-compiler ", "dev:weex": "rollup -w -c scripts/config.js --environment TARGET:weex-framework", "dev:weex:factory": "rollup -w -c scripts/config.js --environment TARGET:weex-factory", "dev:weex:compiler": "rollup -w -c scripts/config.js --environment TARGET:weex-compiler ", "build": "node scripts/build.js", "build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer", "build:weex": "npm run build -- weex", "test": "npm run lint && flow check && npm run test:types && npm run test:cover && npm run test:e2e -- --env phantomjs && npm run test:ssr && npm run test:weex", "test:unit": "karma start test/unit/karma.unit.config.js", "test:cover": "karma start test/unit/karma.cover.config.js", "test:e2e": "npm run build -- web-full-prod,web-server-basic-renderer && node test/e2e/runner.js", "test:weex": "npm run build:weex && jasmine JASMINE_CONFIG_PATH=test/weex/jasmine.json", "test:ssr": "npm run build:ssr && jasmine JASMINE_CONFIG_PATH=test/ssr/jasmine.json", "test:sauce": "npm run sauce -- 0 && npm run sauce -- 1 && npm run sauce -- 2", "test:types": "tsc -p ./types/test/tsconfig.json", "lint": "eslint --fix src scripts test", "flow": "flow check", "sauce": "karma start test/unit/karma.sauce.config.js", "bench:ssr": "npm run build:ssr && node benchmarks/ssr/renderToString.js && node benchmarks/ssr/renderToStream.js", "release": "bash scripts/release.sh", "release:weex": "bash scripts/release-weex.sh", "release:note": "node scripts/gen-release-note.js", "commit": "git-cz" },
mmp,但是英雄无所畏惧,因为我打算只看build 命令,所以我们的入口文件就是scripts/build.js 这个文件了。
接下来我们采用调试代码的方式进行阅读。我用的是webstorm这个IDE,先点右上角edit configuration,然后按下图进行配置:
保存后再点右上角的甲壳虫进行debug。
scripts/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() // filter builds via command line arg 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 }) }
我们就将断点打到if (!fs.existsSync(‘dist’)) { 这一行,然后开始往下一点点走(step over)。
下面就单独对这个文件添加一些注释方便读者阅读:
const fs = require('fs') const path = require('path') const zlib = require('zlib') const rollup = require('rollup') const uglify = require('uglify-js') // 如果项目根目录下不存在dist目录,则同步创建dist目录 if (!fs.existsSync('dist')) { fs.mkdirSync('dist') } // 拿到各种build情况对应的配置参数 let builds = require('./config').getAllBuilds() // 通过命令行参数来判断需要针对哪些build情况进行打包,直接`node scripts/build.js`的话,`process.argv`拿到的数组长度为2,故下面的判断流程会进入else分支 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 { // 直接`node scripts/build.js`会进入这个分支,默认会过滤掉weex相关的build,不对weex相关的build情况进行打包 builds = builds.filter(b => { return b.output.file.indexOf('weex') === -1 }) } // 根据过滤后剩下的build案例对应的配置参数,使用rollup库进行打包 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' }
好了,大概的打包流程是这样的,anyway我们是要读源码,所以我们的关注点应该是这里打包时对应的入口文件,从那个文件开始着手阅读代码。
根据let builds = require(‘./config’).getAllBuilds() 这行代码,我们跳转到config.js 文件,发现:
// Runtime+compiler development build (Browser) 'web-full-dev': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.js'), format: 'umd', env: 'development', alias: { he: './entity-decoder' }, banner }, // Runtime+compiler production build (Browser) 'web-full-prod': { entry: resolve('web/entry-runtime-with-compiler.js'), dest: resolve('dist/vue.min.js'), format: 'umd', env: 'production', alias: { he: './entity-decoder' }, banner },
因为我们如果直接通过在html里用script标签引用vue.js进行开发的时候,一般都是引用vue.js的,所以这里我们就看这个web-full-dev 就可以了(它的dest 字段对应的是vue.js 文件)。然后我们通过entry 字段判断我们要看的入口文件为:src/platforms/web/entry-runtime-with-compiler.js 这个文件。