往期文章:
整体流程
- 1.webpack: ./bin/webpack.js 入口
- 2.webpack-cli/bin/cli.js
- 3.webpack-cli/lib/bootstrap.js:初始化命令参数比如 development
- 4.webpack-cli/lib/webpack-cli: run
- 5.webpack: 回到 webpack,然后找到文件 package.json.main -> webpack/lib/index.js -> webpack/lib/webpack.js -> compiler,绕了一大圈,终于特么到了核心文件
-
- hooks,说白了,就是webpack定义了webpack执行过程中插件的事件接口,外部插件通过订阅对应的事件用callback处理
整体流程源码分析
1.我们执行 webpack --mode development/production 时,首先在包里面找 webpack,如果包里没有再到外面找 .bin/webpack -> package.json ("bin": {"webpack": "bin/webpack.js"}) 即 webpack: bin/webpack.js
const runCommand = (command, args) => {
const cp = require('child_process');
return new Promise((resolve, reject) => {
const executedCommand = cp.spawn(command, args, { stdio: 'inherit', shell: true });
executedCommand.on('error', error => reject(error));
// process.exit() 如果返回0,表示退出成功
executedCommand.on('exit', code => (code === 0 ? resolve() : reject()));
});
};
const isInstalled = packageName => {
try {
require.resolve(packageName);
return true;
} catch (err) {
return false;
}
};
const runCli = cli => {
const path = require('path');
const pkgPath = require.resolve(`${cli.package}/package.json`);
// eslint-disable-next-line node/no-missing-require
const pkg = require(pkgPath);
// eslint-disable-next-line node/no-missing-require
require(path.resolve(path.dirname(pkgPath), pkg.bin[cli.binName]));
};
const cli = {
name: 'webpack-cli',
package: 'webpack-cli',
binName: 'webpack-cli',
installed: isInstalled('webpack-cli'),
url: 'https://github.com/webpack/webpack-cli',
};
if (!cli.installed) {
// ....命令交互,安装 webpack-cli成功后
// runCommand
runCli(cli);
} else {
runCli(cli); // 执行 webpack-cli,找到 webpack-cli/bin/cli.js
}
2.webpack-cli/bin/cli.js
require('v8-compile-cache'); // https://www.npmjs.com/package/v8-compile-cache
const runCLI = require('../lib/bootstrap');
process.title = 'webpack';
// 获取 wepback 后面的参数,比如 webpack --mode development, 这里 rawArgs就是 development
const [, , ...rawArgs] = process.argv;
if (packageExists('webpack')) {
runCLI(rawArgs);
v8-compile-cache 这玩意高了,通过使用 V8 的代码缓存来加快实例化时间,
“代码缓存”是在V8 进行的解析和编译工作时设置缓存目录,V8_COMPILE_CACHE_CACHE_DIR 或默认为<os.tmpdir()>/v8-compile-cache-<V8_VERSION>
用法:安装后,直接 require 就行了
-
基准测试 | 模组 | 没有缓存 | 带缓存 | |:--|:--|:--| | babel-core | 218ms | 185ms | | yarn | 153ms | 113ms | | yarn (捆绑)| 228ms | 105ms |
-
核心代码
var wrapper = Module.wrap(content);
var invalidationKey = crypto.createHash('sha1').update(content, 'utf8').digest('hex');
var buffer = this._cacheStore.get(filename, invalidationKey);
var script = new vm.Script(wrapper, {
filename: filename,
lineOffset: 0,
displayErrors: true,
cachedData: buffer,
produceCachedData: true,
});
if (script.cachedDataProduced) {
this._cacheStore.set(filename, invalidationKey, script.cachedData);
} else if (script.cachedDataRejected) {
this._cacheStore.delete(filename);
}
var compiledWrapper = script.runInThisContext({
filename: filename,
lineOffset: 0,
columnOffset: 0,
displayErrors: true,
});
return compiledWrapper;
-
- new vm.script() nodejs 预编译
-
- vm 模块可在 V8 引擎上下文中编译和运行代码
3.webpack-cli/lib/bootstrap.js:初始化命令参数比如 development
const WebpackCLI = require('./webpack-cli');
process.title = 'webpack-cli';
const runCLI = async cliArgs => {
// 初始化参数
const parsedArgs = argParser(core, cliArgs, true, process.title);
const cli = new WebpackCLI();
// core 命令集合
await cli.run(parsedArgsOpts, core);
};
4.webpack-cli/lib/webpack-cli: run
const webpack = packageExists('webpack') ? require('webpack') : undefined;
class WebpackCLI {
createCompiler(options, callback) {
compiler = webpack(options, callback);
}
async run(args) {
compiler = this.createCompiler(options, callback);
return Promise.resolve();
}
}
5.webpack: 回到 webpack,然后找到文件 package.json.main -> webpack/lib/index.js 绑定webpack钩子事件 -> webpack/lib/webpack.js,
const fn = lazyFunction(() => require("./webpack"));
module.exports = mergeExports(fn, {
...
get Cache() {
return require("./Cache");
},
get Chunk() {
return require("./Chunk");
},
get Compilation() {
return require("./Compilation");
},
get Compiler() {
return require("./Compiler");
},
...
get DefinePlugin() {
return require("./DefinePlugin");
},
cache: {
...
},
config: {
...
},
ids: {
...
},
javascript: {
...
},
optimize: {
...
},
runtime: {
...
},
...
experiments: {
...
}
})
6.webpack/lib/webpack.js
// 多入口
const createMultiCompiler = childOptions => {
const compilers = childOptions.map(options => createCompiler(options));
const compiler = new MultiCompiler(compilers);
for (const childCompiler of compilers) {
// 收集依赖
if (childCompiler.options.dependencies) {
compiler.setDependencies(
childCompiler,
childCompiler.options.dependencies
);
}
}
return compiler;
};
// 单入口
const createCompiler = rawOptions => {
// 根据项目webpack.config 组装 webpack 待执行的配置文件
const options = getNormalizedWebpackOptions(rawOptions);
// F(options, "context", () => process.cwd());
// 设置webpack执行上下文所在工作目录
applyWebpackOptionsBaseDefaults(options);
// ** 创建编译实例
const compiler = new Compiler(options.context);
compiler.options = options;
// 将插件挂载在 compiler上
if (Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
if (typeof plugin === "function") {
plugin.call(compiler, compiler);
} else {
plugin.apply(compiler);
}
}
}
// 抹平平台编译环境
compiler.hooks.environment.call();
compiler.hooks.afterEnvironment.call();
// 根据 配置,处理 compiler
new WebpackOptionsApply().process(options, compiler);
// 初始化 hooks 钩子
compiler.hooks.initialize.call();
return compiler;
};
module.exports = (( options, callback ) => {
const create = () => {
// 检查你的 webpack.config 是否符合规范
validateSchema(webpackOptionsSchema, options);
// 此处先只分析 单入口
let compiler = createCompiler(options);
let watch = options.watch;
let watchOptions = options.watchOptions;
return { compiler, watch, watchOptions };
};
if (callback) {
const { compiler, watch, watchOptions } = create();
if (watch) {
compiler.watch(watchOptions, callback);
} else {
compiler.run((err, stats) => {
compiler.close(err2 => {
callback(err || err2, stats);
});
});
}
return compiler;
} else {
const { compiler, watch } = create();
return compiler;
}
});
绕了一大圈,终于特么到了核心处理逻辑: compiler
详细分析 compiler
流程图
代码分析
1.webpack/lib/Compiler.js 入口 run (tapable请参考 轻松搞定Tapable)
const { SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require("tapable");
module.exports = class Compiler {
constructor(context) {
// 定义webpack 的 钩子
this.hooks = Object.freeze({
initialize: new SyncHook([]),
done: new AsyncSeriesHook(["stats"]),
afterDone: new SyncHook(["stats"]),
run: new AsyncSeriesHook(["compiler"]),
emit: new AsyncSeriesHook(["compilation"]),
assetEmitted: new AsyncSeriesHook(["file", "info"]),
afterEmit: new AsyncSeriesHook(["compilation"]),
thisCompilation: new SyncHook(["compilation", "params"]),
...
compile: new SyncHook(["params"]),
make: new AsyncParallelHook(["compilation"]),
...
});
this.webpack = webpack; // webpack.js
this.running = false;
this.cache = new Cache();
...
}
// 1.开始入口 run
run(callback) {
if (this.running) { return callback(...)}
// 打包结束时的callback(涉及 ProgressPlugin,watch、IdleFileCachePlugin)
const finalCallback = (err, stats) => {
this.running = false;
// 打包失败(涉及)
err && this.hooks.failed.call(err);
callback && callback(err, stats);
this.hooks.afterDone.call(stats);
}
// 记录开始时间
const startTime = Date.now();
this.running = true;
const run = () => {
// 编译前(涉及ProgressPlugin、NodeEnvironmentPlugin)
this.hooks.beforeRun.callAsync(this, err => {
if (err) return finalCallback(err);
// 涉及ProgressPlugin
this.hooks.run.callAsync(this, err => {
if (err) return finalCallback(err);
// 开始编译
this.compile((err, compilation) => {
// 如果可以结束
if (this.hooks.shouldEmit.call(compilation) === false) {
compilation.startTime = startTime;
compilation.endTime = Date.now();
const stats = new Stats(compilation);
this.hooks.done.callAsync(stats, err => {
if (err) return finalCallback(err);
return finalCallback(null, stats);
});
}
process.nextTick(() => {
this.emitAssets(compilation, err => {
this.hooks.done.callAsync(stats, err => { this.hooks.additionalPass.callAsync(err => {
this.compile(onCompiled);
})
})
});
});
});
});
})
}
// run
run();
}
// 2.compile 编译
compile(callback) {
...
// 2.1. before compile(ProgressPlugin、DllReferencePlugin)
this.hooks.beforeCompile.callAsync(params, err => {
// 2.2 compile(ExternalsPlugin -> ExternalModuleFactoryPlugin)
this.hooks.compile.call(params);
const compilation = this.newCompilation(params);
// 3. make (EntryPlugin 收集依赖)
this.hooks.make.callAsync(compilation, err => {
this.hooks.finishMake.callAsync(compilation, err => {
process.nextTick(() => {
compilation.finish(err => {
compilation.seal(err => {
this.hooks.afterCompile.callAsync(compilation, err => {
// onCompiled
return callback()
})
})
})
});
});
});
})
}
}
❤️ 加入我们
字节跳动 · 幸福里团队
Nice Leader:高级技术专家、掘金知名专栏作者、Flutter中文网社区创办者、Flutter中文社区开源项目发起人、Github社区知名开发者,是dio、fly、dsBridge等多个知名开源项目作者
期待您的加入,一起用技术改变生活!!!