什么是Compiler
Compiler 负责编译,贯穿webpack的整个生命周期,Compiler 对象包含了当前运行Webpack的配置,包括entry、output、loaders等配置,这个对象在启动Webpack时被实例化,而且是全局唯一的。Plugin可以通过该对象获取到Webpack的配置信息进行处理。我们可以在源码中看看Compiler是怎么定义的。
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {};
// TODO webpack 5 remove this
this.hooks.infrastructurelog = this.hooks.infrastructureLog;
this._pluginCompat.tap("Compiler", options => {
});
this.name = undefined;
this.parentCompilation = undefined;
this.outputPath = "";
this.outputFileSystem = null;
this.inputFileSystem = null;
this.recordsInputPath = null;
this.recordsOutputPath = null;
this.records = {};
this.removedFiles = new Set();
this.fileTimestamps = new Map();
this.contextTimestamps = new Map();
this.resolverFactory = new ResolverFactory();
this.infrastructureLogger = undefined;
this.resolvers = {
};
this.options = /** @type {WebpackOptions} */ ({});
this.context = context;
this.requestShortener = new RequestShortener(context);
this.running = false;
this.watchMode = false;
this._assetEmittingSourceCache = new WeakMap();
this._assetEmittingWrittenFiles = new Map();
}
getInfrastructureLogger(name) {
}
watch(watchOptions, handler) {
}
run(callback) {
}
runAsChild(callback) {
}
purgeInputFileSystem() {
}
emitAssets(compilation, callback) {
}
emitRecords(callback) {
}
createChildCompiler(
compilation,
compilerName,
compilerIndex,
outputOptions,
plugins
) {
}
isChild() {
}
createCompilation() {
return new Compilation(this);
}
newCompilation(params) {
}
createNormalModuleFactory() {
}
createContextModuleFactory() {
}
newCompilationParams() {
}
compile(callback) {
}
}
把详细的内容去掉,我们可以看到,Compiler 对象中包含了 options, hoks, context, name, outPutPath等熟悉,和watch, run, createCompilation, compile 等方法。
Compiler 是在webpack 启动的时候实例化的,那我们知道了webpack存在这些属性和方法,那么我们可以知道webpack 可以读取到这些属性和调用这些方法,来完成编译工作。
Compiler.hooks
this.hooks = {
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<string, string, any[]>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])
};
通过代码可以看到 compiler.hooks 是钩子贯穿了整改webpack打包的生命周期,那么我们的插件就是注册到这些钩子(订阅钩子事件),当执行到这些钩子函数时,将会通知插件,并且通过回调返回参数给插件,就可以实现插件逻辑。
插件可以做些什么
通知学习插件的执行时间点,我们可以执行什么(改变输出),要改变输出,取决于我们可以获取到什么,以及对它做些什么修改操作,比如我们可以去除注释,去除空格,合并代码,压缩文件,提取公共代码,改变配置,修改,改变输出等。
内置插件和自定义插件
内置插件
我们可以看到项目存在很多内置插件,每个插件有自己独自的功能,比如DllPlugin插件,这个插件是在一个额外的独立的 webpack 设置中创建一个只有 dll 的 bundle(dll-only-bundle)。 这个插件会生成一个名为 manifest.json 的文件,这个文件是用来让 DLLReferencePlugin 映射到相关的依赖上去的。
class DllPlugin {
/**
* @param {DllPluginOptions} options options object
*/
constructor(options) {
validateOptions(schema, options, "Dll Plugin");
this.options = options;
}
apply(compiler) {
// 1. 在entryOption 时触发
compiler.hooks.entryOption.tap("DllPlugin", (context, entry) => {
const itemToPlugin = (item, name) => {
// 如果是数组,执行调用DllEntryPlugin插件
if (Array.isArray(item)) {
return new DllEntryPlugin(context, item, name);
}
throw new Error("DllPlugin: supply an Array as entry");
};
// 判断参数类型,如果是object 但不是 array
if (typeof entry === "object" && !Array.isArray(entry)) {
// 遍历对象的keys
Object.keys(entry).forEach(name => {
itemToPlugin(entry[name], name).apply(compiler);
});
} else {
itemToPlugin(entry, "main").apply(compiler);
}
return true;
});
new LibManifestPlugin(this.options).apply(compiler);
if (!this.options.entryOnly) {
new FlagAllModulesAsUsedPlugin("DllPlugin").apply(compiler);
}
}
}
自定义插件
在根目录下定义一个 ts, 在 vue.config.json中引入。
自定义写了一个插件,将插件注册到entryOption钩子中,npm run build 可以看到打印:
打印出了上下文和enrty 入口文件,那么我们也是可以在这里进行修改的,比如修改入口文件。
当换成 run 钩子时,
返回的参数就是一个compiler实例。也就是说每一个钩子函数都会返回不一样的参数,我们可以通过我们的需求注册到不同的钩子里面,去对不同流程做我们想要的操作。
_pluginCompat: SyncBailHook {
_args: [ 'options' ],
taps: [ [Object], [Object], [Object] ],
interceptors: [],
call: [Function: lazyCompileHook],
promise: [Function: lazyCompileHook],
callAsync: [Function: lazyCompileHook],
_x: undefined
},
hooks: {
shouldEmit: SyncBailHook {
},
done: AsyncSeriesHook {
},
additionalPass: AsyncSeriesHook {
},
beforeRun: AsyncSeriesHook {
},
run: AsyncSeriesHook {
},
emit: AsyncSeriesHook {
},
assetEmitted: AsyncSeriesHook {
},
afterEmit: AsyncSeriesHook {
},
thisCompilation: SyncHook {
},
compilation: SyncHook {
},
normalModuleFactory: SyncHook {
},
contextModuleFactory: SyncHook {
},
beforeCompile: AsyncSeriesHook {
},
compile: SyncHook {
},
make: AsyncParallelHook {
},
afterCompile: AsyncSeriesHook {
},
watchRun: AsyncSeriesHook {
},
failed: SyncHook {
},
invalid: SyncHook {
},
watchClose: SyncHook {
},
infrastructureLog: SyncBailHook {
},
environment: SyncHook {
},
afterEnvironment: SyncHook {
},
afterPlugins: SyncHook {
},
afterResolvers: SyncHook {
},
entryOption: SyncBailHook {
},
infrastructurelog: SyncBailHook {
}
},
name: undefined,
parentCompilation: undefined,
outputPath: 'F:\\company\\zhejiang-complaint\\dist',
outputFileSystem: NodeOutputFileSystem {
},
inputFileSystem: CachedInputFileSystem {
fileSystem: NodeJsInputFileSystem {},
},
recordsInputPath: undefined,
recordsOutputPath: undefined,
records: {},
removedFiles: Set(0) {},
fileTimestamps: Map(0) {},
contextTimestamps: Map(0) {},
resolverFactory: ResolverFactory {
},
infrastructureLogger: [Function: logger],
resolvers: {
normal: { plugins: [Function: deprecated], apply: [Function: deprecated] },
loader: { plugins: [Function: deprecated], apply: [Function: deprecated] },
context: { plugins: [Function: deprecated], apply: [Function: deprecated] }
},
options: {
mode: 'production',
context: 'F:\\company\\zhejiang-complaint',
devtool: false,
node: {
setImmediate: false,
process: 'mock',
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
console: false,
global: true,
Buffer: true,
__filename: 'mock',
__dirname: 'mock'
},
output: {
},
resolve: {
},
resolveLoader: {
},
module: {
},
optimization: {
},
plugins: [
VueLoaderPlugin {},
[DefinePlugin],
[CaseSensitivePathsPlugin],
[FriendlyErrorsWebpackPlugin],
[MiniCssExtractPlugin],
[OptimizeCssnanoPlugin],
[HashedModuleIdsPlugin],
[NamedChunksPlugin],
[HtmlWebpackPlugin],
[PreloadPlugin],
[PreloadPlugin],
[CopyPlugin],
[ForkTsCheckerWebpackPlugin],
[vConsolePlugin],
[HotHashWebpackPlugin],
[WebpackBarPlugin],
ConsoleLogOnBuildWebpackPlugin {}
],
entry: { main: [Array] },
cache: false,
target: 'web',
performance: {
maxAssetSize: 250000,
maxEntrypointSize: 250000,
hints: 'warning'
},
infrastructureLogging: { level: 'info', debug: false }
},
context: 'F:\\company\\zhejiang-complaint',
requestShortener: RequestShortener {
currentDirectoryRegExp: /(^|!)F:\/company\/zhejiang\-complaint/g,
parentDirectoryRegExp: /(^|!)F:\/company\//g,
buildinsAsModule: true,
buildinsRegExp: /(^|!)F:\/company\/zhejiang\-complaint\/node_modules\/webpack/g,
cache: Map(0) {}
},
running: true,
watchMode: false,
_assetEmittingSourceCache: WeakMap { <items unknown> },
_assetEmittingWrittenFiles: Map(0) {},
watchFileSystem: NodeWatchFileSystem {
},
watcherOptions: { aggregateTimeout: 200 },
watcher: EventEmitter {
}
},
webpackbar: WebpackBarPlugin {
},
dependencies: undefined
} 00000
webpack run+++++
在 run 阶段我们可以获取到很多信息,也提供了插件的很多可能。
compilation
Compilation对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 Compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息,简单来讲就是把本次打包编译的内容存到内存里。Compilation 对象也提供了插件需要自定义功能的回调,以供插件做自定义处理时选择使用拓展。
简单来说,Compilation的职责就是构建模块和Chunk,并利用插件优化构建过程。
和 Compiler 用法相同,钩子类型不同,也可以在某些钩子上访问 tapAsync 和 tapPromise。
打印 compilation
源码有点多,看不动了~
Compiler 和 Compilation 的区别
Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译,只要文件有改动,compilation就会被重新创建。