外观
准备粗略地阅读下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 这个文件。