大型源码与业务代码不同,并没有显而易见的入口,本文记录如何理清大型文章脉络,快速开启对源码的阅读。
要了解目录结构,从package.json入手。
正常情况下,main代表着入口文件。然而,在vue2源码中,main对应着dist下的文件,我们不得不将视野转向别的方向。
"main": "dist/vue.runtime.common.js",
在根据main寻找线索无果的情况下,我们将视线转移至命令行,rollup的打包方式拥有入口的配置文件,我们由此向下寻找
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",
"build": "node scripts/build.js",
在build.js中,我们发现了两行目标代码
let builds = require('./config').getAllBuilds()
build(builds)
尽管我们没有直接找到入口文件,但通过第一行语句,我们找到了配置项。第二行build正是依据此配置项进行打包,顺理成章的进入config.js,我们发现这正是命令行dev的配置项,此时run和build的配置项统一了,我们的方向是正确的。
面对配置项,首先去找exports,也就是这个文件暴露了什么东西到外面。
if (process.env.TARGET) {
module.exports = genConfig(process.env.TARGET)
} else {
exports.getBuild = genConfig
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}
随即,找到genConfig方法。
function genConfig (name) {
const opts = builds[name]
const config = {
input: opts.entry,
external: opts.external,
plugins: [
flow(),
alias(Object.assign({}, aliases, opts.alias))
].concat(opts.plugins || []),
output: {
file: opts.dest,
format: opts.format,
banner: opts.banner,
name: opts.moduleName || 'Vue'
},
onwarn: (msg, warn) => {
if (!/Circular/.test(msg)) {
warn(msg)
}
}
}
// built-in vars
const vars = {
__WEEX__: !!opts.weex,
__WEEX_VERSION__: weexVersion,
__VERSION__: version
}
// feature flags
Object.keys(featureFlags).forEach(key => {
vars[`process.env.${key}`] = featureFlags[key]
})
// build-specific env
if (opts.env) {
vars['process.env.NODE_ENV'] = JSON.stringify(opts.env)
}
config.plugins.push(replace(vars))
if (opts.transpile !== false) {
config.plugins.push(buble())
}
Object.defineProperty(config, '_name', {
enumerable: false,
value: name
})
return config
}
genConfig方法return了config对象,而config对象依赖常量builds,dev命令执行索引web-full-dev,其配置如下
// 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
},
自此,找到了入口entry,发现entry并不直接是有效地址,而是方法,寻找方法
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)
}
}
方法依赖alias,查找alias兑换到entry的真正路径:src/platforms/web/entry-runtime-with-compiler.js