webpack是现代前端开发中最火的模块打包工具,只需要通过简单的配置,便可以完成模块的加载和打包。那它是怎么做到通过对一些插件的配置,便可以轻松实现对代码的构建呢?

webpack的配置

const path = require('path');
module.exports = {
entry: "./app/entry", // string | object | array
// Webpack打包的入口
output: { // 定义webpack如何输出的选项
path: path.resolve(__dirname, "dist"), // string
// 所有输出文件的目标路径
filename: "[chunkhash].js", // string
// 「入口(entry chunk)」文件命名模版
publicPath: "/assets/", // string
// 构建文件的输出目录
/* 其它高级配置 */
},
module: { // 模块相关配置
rules: [ // 配置模块loaders,解析规则
{
test: /\.jsx?$/, // RegExp | string
include: [ // 和test一样,必须匹配选项
path.resolve(__dirname, "app")
],
exclude: [ // 必不匹配选项(优先级高于test和include)
path.resolve(__dirname, "app/demo-files")
],
loader: "babel-loader", // 模块上下文解析
options: { // loader的可选项
presets: ["es2015"]
},
},
},
resolve: { // 解析模块的可选项
modules: [ // 模块的查找目录
"node_modules",
path.resolve(__dirname, "app")
],
extensions: [".js", ".json", ".jsx", ".css"], // 用到的文件的扩展
alias: { // 模块别名列表
"module": "new-module"
},
},
devtool: "source-map", // enum
// 为浏览器开发者工具添加元数据增强调试
plugins: [ // 附加插件列表
// ...
],
}

从上面我们可以看到,webpack配置中需要理解几个核心的概念EntryOutputLoadersPluginsChunk

  • Entry:指定webpack开始构建的入口模块,从该模块开始构建并计算出直接或间接依赖的模块或者库
  • Output:告诉webpack如何命名输出的文件以及输出的目录
  • Loaders:由于webpack只能处理javascript,所以我们需要对一些非js文件处理成webpack能够处理的模块,比如sass文件
  • Plugins:Loaders将各类型的文件处理成webpack能够处理的模块,plugins有着很强的能力。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。但也是最复杂的一个。比如对js文件进行压缩优化的UglifyJsPlugin插件
  • Chunk:coding split的产物,我们可以对一些代码打包成一个单独的chunk,比如某些公共模块,去重,更好的利用缓存。或者按需加载某些功能模块,优化加载时间。在webpack3及以前我们都利用CommonsChunkPlugin将一些公共代码分割成一个chunk,实现单独加载。在webpack4 中CommonsChunkPlugin被废弃,使用SplitChunksPlugin

webpack详解

读到这里,或许你对webpack有一个大概的了解,那webpack 是怎么运行的呢?我们都知道,webpack是高度复杂抽象的插件集合,理解webpack的运行机制,对于我们日常定位构建错误以及写一些插件处理构建任务有很大的帮助。

不得不说的tapable

webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。在Tapable1.0之前,也就是webpack3及其以前使用的Tapable,提供了包括

  • plugin(name:string, handler:function)注册插件到Tapable对象中
  • apply(…pluginInstances: (AnyPlugin|function)[])调用插件的定义,将事件监听器注册到Tapable实例注册表中
  • applyPlugins*(name:string, …)多种策略细致地控制事件的触发,包括applyPluginsAsyncapplyPluginsParallel等方法实现对事件触发的控制,实现

(1)多个事件连续顺序执行 (2)并行执行 (3)异步执行 (4)一个接一个地执行插件,前面的输出是后一个插件的输入的瀑布流执行顺序 (5)在允许时停止执行插件,即某个插件返回了一个undefined的值,即退出执行 我们可以看到,Tapable就像nodejs中EventEmitter,提供对事件的注册on和触发emit,理解它很重要,看个栗子:比如我们来写一个插件

function CustomPlugin() {}
CustomPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', pluginFunction);
}

在webpack的生命周期中会适时的执行

this.apply*("emit",options)

当然上面提到的Tapable都是1.0版本之前的,如果想深入学习,可以查看Tapable 和 事件流 那1.0的Tapable又是什么样的呢?1.0版本发生了巨大的改变,不再是此前的通过plugin注册事件,通过applyPlugins*触发事件调用,那1.0的Tapable是什么呢?

暴露出很多的钩子,可以使用它们为插件创建钩子函数

const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");

我们来看看 怎么使用。

class Order {
constructor() {
this.hooks = { //hooks
goods: new SyncHook(['goodsId', 'number']),
consumer: new AsyncParallelHook(['userId', 'orderId'])
}
} queryGoods(goodsId, number) {
this.hooks.goods.call(goodsId, number);
} consumerInfoPromise(userId, orderId) {
this.hooks.consumer.promise(userId, orderId).then(() => {
//TODO
})
} consumerInfoAsync(userId, orderId) {
this.hooks.consumer.callAsync(userId, orderId, (err, data) => {
//TODO
})
}
}

对于所有的hook的构造函数均接受一个可选的string类型的数组

const hook = new SyncHook(["arg1", "arg2", "arg3"]);
// 调用tap方法注册一个consument
order.hooks.goods.tap('QueryPlugin', (goodsId, number) => {
return fetchGoods(goodsId, number);
})
// 再添加一个
order.hooks.goods.tap('LoggerPlugin', (goodsId, number) => {
logger(goodsId, number);
}) // 调用
order.queryGoods('10000000', 1)

对于一个 SyncHook,我们通过tap来添加消费者,通过call来触发钩子的顺序执行。

对于一个非sync*类型的钩子,即async*类型的钩子,我们还可以通过其它方式注册消费者和调用

// 注册一个sync 钩子
order.hooks.consumer.tap('LoggerPlugin', (userId, orderId) => {
logger(userId, orderId);
}) order.hooks.consumer.tapAsync('LoginCheckPlugin', (userId, orderId, callback) => {
LoginCheck(userId, callback);
}) order.hooks.consumer.tapPromise('PayPlugin', (userId, orderId) => {
return Promise.resolve();
}) // 调用
// 返回Promise
order.consumerInfoPromise('user007', '1024'); //回调函数
order.consumerInfoAsync('user007', '1024')

通过上面的栗子,你可能已经大致了解了Tapable的用法,它的用法

  • 插件注册数量
  • 插件注册的类型(sync, async, promise)
  • 调用的方式(sync, async, promise)
  • 实例钩子的时候参数数量
  • 是否使用了interception

Tapable详解

对于Sync*类型的钩子来说。

  • 注册在该钩子下面的插件的执行顺序都是顺序执行。
  • 只能使用tap注册,不能使用tapPromisetapAsync注册
// 所有的钩子都继承于Hook
class Sync* extends Hook {
tapAsync() { // Sync*类型的钩子不支持tapAsync
throw new Error("tapAsync is not supported on a Sync*");
}
tapPromise() {// Sync*类型的钩子不支持tapPromise
throw new Error("tapPromise is not supported on a Sync*");
}
compile(options) { // 编译代码来按照一定的策略执行Plugin
factory.setup(this, options);
return factory.create(options);
}
}

对于Async*类型钩子

  • 支持taptapPromisetapAsync注册
class AsyncParallelHook extends Hook {
constructor(args) {
super(args);
this.call = this._call = undefined;
} compile(options) {
factory.setup(this, options);
return factory.create(options);
}
}
class Hook {
constructor(args) {
if(!Array.isArray(args)) args = [];
this._args = args; // 实例钩子的时候的string类型的数组
this.taps = []; // 消费者
this.interceptors = []; // interceptors
this.call = this._call = // 以sync类型方式来调用钩子
this._createCompileDelegate("call", "sync");
this.promise =
this._promise = // 以promise方式
this._createCompileDelegate("promise", "promise");
this.callAsync =
this._callAsync = // 以async类型方式来调用
this._createCompileDelegate("callAsync", "async");
this._x = undefined; //
} _createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
} _createCompileDelegate(name, type) {
const lazyCompileHook = (...args) => {
this[name] = this._createCall(type);
return this[name](...args);
};
return lazyCompileHook;
}
// 调用tap 类型注册
tap(options, fn) {
// ...
options = Object.assign({ type: "sync", fn: fn }, options);
// ...
this._insert(options); // 添加到 this.taps中
}
// 注册 async类型的钩子
tapAsync(options, fn) {
// ...
options = Object.assign({ type: "async", fn: fn }, options);
// ...
this._insert(options); // 添加到 this.taps中
}
注册 promise类型钩子
tapPromise(options, fn) {
// ...
options = Object.assign({ type: "promise", fn: fn }, options);
// ...
this._insert(options); // 添加到 this.taps中
} }

每次都是调用taptapSynctapPromise注册不同类型的插件钩子,通过调用callcallAsyncpromise方式调用。其实调用的时候为了按照一定的执行策略执行,调用compile方法快速编译出一个方法来执行这些插件。

const factory = new Sync*CodeFactory();
class Sync* extends Hook {
// ...
compile(options) { // 编译代码来按照一定的策略执行Plugin
factory.setup(this, options);
return factory.create(options);
}
} class Sync*CodeFactory extends HookCodeFactory {
content({ onError, onResult, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}

compile中调用HookCodeFactory#create方法编译生成执行代码。


class HookCodeFactory {
constructor(config) {
this.config = config;
this.options = undefined;
} create(options) {
this.init(options);
switch(this.options.type) {
case "sync": // 编译生成sync, 结果直接返回
return new Function(this.args(),
"\"use strict\";\n" + this.header() + this.content({
// ...
onResult: result => `return ${result};\n`,
// ...
}));
case "async": // async类型, 异步执行,最后将调用插件执行结果来调用callback,
return new Function(this.args({
after: "_callback"
}), "\"use strict\";\n" + this.header() + this.content({
// ...
onResult: result => `_callback(null, ${result});\n`,
onDone: () => "_callback();\n"
}));
case "promise": // 返回promise类型,将结果放在resolve中
// ...
code += "return new Promise((_resolve, _reject) => {\n";
code += "var _sync = true;\n";
code += this.header();
code += this.content({
// ...
onResult: result => `_resolve(${result});\n`,
onDone: () => "_resolve();\n"
});
// ...
return new Function(this.args(), code);
}
}
// callTap 就是执行一些插件,并将结果返回
callTap(tapIndex, { onError, onResult, onDone, rethrowIfPossible }) {
let code = "";
let hasTapCached = false;
// ...
code += `var _fn${tapIndex} = ${this.getTapFn(tapIndex)};\n`;
const tap = this.options.taps[tapIndex];
switch(tap.type) {
case "sync":
// ...
if(onResult) {
code += `var _result${tapIndex} = _fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} else {
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})});\n`;
} if(onResult) { // 结果透传
code += onResult(`_result${tapIndex}`);
}
if(onDone) { // 通知插件执行完毕,可以执行下一个插件
code += onDone();
}
break;
case "async": //异步执行,插件运行完后再将结果通过执行callback透传
let cbCode = "";
if(onResult)
cbCode += `(_err${tapIndex}, _result${tapIndex}) => {\n`;
else
cbCode += `_err${tapIndex} => {\n`;
cbCode += `if(_err${tapIndex}) {\n`;
cbCode += onError(`_err${tapIndex}`);
cbCode += "} else {\n";
if(onResult) {
cbCode += onResult(`_result${tapIndex}`);
} cbCode += "}\n";
cbCode += "}";
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined,
after: cbCode //cbCode将结果透传
})});\n`;
break;
case "promise": // _fn${tapIndex} 就是第tapIndex 个插件,它必须是个Promise类型的插件
code += `var _hasResult${tapIndex} = false;\n`;
code += `_fn${tapIndex}(${this.args({
before: tap.context ? "_context" : undefined
})}).then(_result${tapIndex} => {\n`;
code += `_hasResult${tapIndex} = true;\n`;
if(onResult) {
code += onResult(`_result${tapIndex}`);
}
// ...
break;
}
return code;
}
// 按照插件的注册顺序,按照顺序递归调用执行插件
callTapsSeries({ onError, onResult, onDone, rethrowIfPossible }) {
// ...
const firstAsync = this.options.taps.findIndex(t => t.type !== "sync");
const next = i => {
// ...
const done = () => next(i + 1);
// ...
return this.callTap(i, {
// ...
onResult: onResult && ((result) => {
return onResult(i, result, done, doneBreak);
}),
// ...
});
};
return next(0);
} callTapsLooping({ onError, onDone, rethrowIfPossible }) { const syncOnly = this.options.taps.every(t => t.type === "sync");
let code = "";
if(!syncOnly) {
code += "var _looper = () => {\n";
code += "var _loopAsync = false;\n";
}
code += "var _loop;\n";
code += "do {\n";
code += "_loop = false;\n";
// ...
code += this.callTapsSeries({
// ...
onResult: (i, result, next, doneBreak) => { // 一旦某个插件返回不为undefined, 即一只调用某个插件执行,如果为undefined,开始调用下一个
let code = "";
code += `if(${result} !== undefined) {\n`;
code += "_loop = true;\n";
if(!syncOnly)
code += "if(_loopAsync) _looper();\n";
code += doneBreak(true);
code += `} else {\n`;
code += next();
code += `}\n`;
return code;
},
// ...
})
code += "} while(_loop);\n";
// ...
return code;
}
// 并行调用插件执行
callTapsParallel({ onError, onResult, onDone, rethrowIfPossible, onTap = (i, run) => run() }) {
// ...
// 遍历注册都所有插件,并调用
for(let i = 0; i < this.options.taps.length; i++) {
// ...
code += "if(_counter <= 0) break;\n";
code += onTap(i, () => this.callTap(i, {
// ...
onResult: onResult && ((result) => {
let code = "";
code += "if(_counter > 0) {\n";
code += onResult(i, result, done, doneBreak);
code += "}\n";
return code;
}),
// ...
}), done, doneBreak);
}
// ...
return code;
}
}

HookCodeFactory#create中调用到content方法,此方法将按照此钩子的执行策略,调用不同的方法来执行编译 生成最终的代码。

  • SyncHook中调用`callTapsSeries`编译生成最终执行插件的函数,`callTapsSeries`做的就是将插件列表中插件按照注册顺序遍历执行。
class SyncHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
onError: (i, err) => onError(err),
onDone,
rethrowIfPossible
});
}
}
  • SyncBailHook中当一旦某个返回值结果不为undefined便结束执行列表中的插件
 class SyncBailHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
// ...
onResult: (i, result, next) => `if(${result} !== undefined) {\n${onResult(result)};\n} else {\n${next()}}\n`,
// ...
});
}
}
  • SyncWaterfallHook中上一个插件执行结果当作下一个插件的入参
class SyncWaterfallHookCodeFactory extends HookCodeFactory {
content({ onError, onResult, onDone, rethrowIfPossible }) {
return this.callTapsSeries({
// ...
onResult: (i, result, next) => {
let code = "";
code += `if(${result} !== undefined) {\n`;
code += `${this._args[0]} = ${result};\n`;
code += `}\n`;
code += next();
return code;
},
onDone: () => onResult(this._args[0]),
});
}
}
  • AsyncParallelHook调用callTapsParallel并行执行插件
class AsyncParallelHookCodeFactory extends HookCodeFactory {
content({ onError, onDone }) {
return this.callTapsParallel({
onError: (i, err, done, doneBreak) => onError(err) + doneBreak(true),
onDone
});
}
}

webpack流程篇

本文关于webpack 的流程讲解是基于webpack4的。

webpack 入口文件

从webpack项目的package.json文件中我们找到了入口执行函数,在函数中引入webpack,那么入口将会是lib/webpack.js,而如果在shell中执行,那么将会走到./bin/webpack.js,我们就以lib/webpack.js为入口开始吧!

{
"name": "webpack",
"version": "4.1.1",
...
"main": "lib/webpack.js",
"web": "lib/webpack.web.js",
"bin": "./bin/webpack.js",
...
}

webpack入口

const webpack = (options, callback) => {
// ...
// 验证options正确性
// 预处理options
options = new WebpackOptionsDefaulter().process(options); // webpack4的默认配置
compiler = new Compiler(options.context); // 实例Compiler
// ...
// 若options.watch === true && callback 则开启watch线程
compiler.watch(watchOptions, callback);
compiler.run(callback);
return compiler;
};

webpack 的入口文件其实就实例了Compiler并调用了run方法开启了编译,webpack的编译都按照下面的钩子调用顺序执行。

  • before-run 清除缓存
  • run 注册缓存数据钩子
  • before-compile
  • compile 开始编译
  • make 从入口分析依赖以及间接依赖模块,创建模块对象
  • build-module 模块构建
  • seal 构建结果封装, 不可再更改
  • after-compile 完成构建,缓存数据
  • emit 输出到dist目录

编译&构建流程

webpack中负责构建和编译都是Compilation

class Compilation extends Tapable {
constructor(compiler) {
super();
this.hooks = {
// hooks
};
// ...
this.compiler = compiler;
// ...
// template
this.mainTemplate = new MainTemplate(this.outputOptions);
this.chunkTemplate = new ChunkTemplate(this.outputOptions);
this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
this.outputOptions
);
this.runtimeTemplate = new RuntimeTemplate(
this.outputOptions,
this.requestShortener
);
this.moduleTemplates = {
javascript: new ModuleTemplate(this.runtimeTemplate),
webassembly: new ModuleTemplate(this.runtimeTemplate)
}; // 构建生成的资源
this.chunks = [];
this.chunkGroups = [];
this.modules = [];
this.additionalChunkAssets = [];
this.assets = {};
this.children = [];
// ...
}
//
buildModule(module, optional, origin, dependencies, thisCallback) {
// ...
// 调用module.build方法进行编译代码,build中 其实是利用acorn编译生成AST
this.hooks.buildModule.call(module);
module.build(/**param*/);
}
// 将模块添加到列表中,并编译模块
_addModuleChain(context, dependency, onModule, callback) {
// ...
// moduleFactory.create创建模块,这里会先利用loader处理文件,然后生成模块对象
moduleFactory.create(
{
contextInfo: {
issuer: "",
compiler: this.compiler.name
},
context: context,
dependencies: [dependency]
},
(err, module) => {
const addModuleResult = this.addModule(module);
module = addModuleResult.module;
onModule(module);
dependency.module = module; // ...
// 调用buildModule编译模块
this.buildModule(module, false, null, null, err => {});
}
});
}
// 添加入口模块,开始编译&构建
addEntry(context, entry, name, callback) {
// ...
this._addModuleChain( // 调用_addModuleChain添加模块
context,
entry,
module => {
this.entries.push(module);
},
// ...
);
} seal(callback) {
this.hooks.seal.call(); // ...
const chunk = this.addChunk(name);
const entrypoint = new Entrypoint(name);
entrypoint.setRuntimeChunk(chunk);
entrypoint.addOrigin(null, name, preparedEntrypoint.request);
this.namedChunkGroups.set(name, entrypoint);
this.entrypoints.set(name, entrypoint);
this.chunkGroups.push(entrypoint); GraphHelpers.connectChunkGroupAndChunk(entrypoint, chunk);
GraphHelpers.connectChunkAndModule(chunk, module); chunk.entryModule = module;
chunk.name = name; // ...
this.hooks.beforeHash.call();
this.createHash();
this.hooks.afterHash.call();
this.hooks.beforeModuleAssets.call();
this.createModuleAssets();
if (this.hooks.shouldGenerateChunkAssets.call() !== false) {
this.hooks.beforeChunkAssets.call();
this.createChunkAssets();
}
// ...
} createHash() {
// ...
} // 生成 assets 资源并 保存到 Compilation.assets 中 给webpack写插件的时候会用到
createModuleAssets() {
for (let i = 0; i < this.modules.length; i++) {
const module = this.modules[i];
if (module.buildInfo.assets) {
for (const assetName of Object.keys(module.buildInfo.assets)) {
const fileName = this.getPath(assetName);
this.assets[fileName] = module.buildInfo.assets[assetName];
this.hooks.moduleAsset.call(module, fileName);
}
}
}
} createChunkAssets() {
// ...
}
}

在webpack make钩子中, tapAsync注册了一个DllEntryPlugin, 就是将入口模块通过调用compilation.addEntry方法将所有的入口模块添加到编译构建队列中,开启编译流程。

compiler.hooks.make.tapAsync("DllEntryPlugin", (compilation, callback) => {
compilation.addEntry(
this.context,
new DllEntryDependency(
this.entries.map((e, idx) => {
const dep = new SingleEntryDependency(e);
dep.loc = `${this.name}:${idx}`;
return dep;
}),
this.name
),
// ...
);
});

随后在addEntry 中调用_addModuleChain开始编译。在_addModuleChain首先会生成模块,最后构建。

class NormalModuleFactory extends Tapable {
// ...
create(data, callback) {
// ...
this.hooks.beforeResolve.callAsync(
{
contextInfo,
resolveOptions,
context,
request,
dependencies
},
(err, result) => {
if (err) return callback(err); // Ignored
if (!result) return callback();
// factory 钩子会触发 resolver 钩子执行,而resolver钩子中会利用acorn 处理js生成AST,再利用acorn处理前,会使用loader加载文件
const factory = this.hooks.factory.call(null); factory(result, (err, module) => {
if (err) return callback(err); if (module && this.cachePredicate(module)) {
for (const d of dependencies) {
d.__NormalModuleFactoryCache = module;
}
} callback(null, module);
});
}
);
}
}

在编译完成后,调用compilation.seal方法封闭,生成资源,这些资源保存在compilation.assets, compilation.chunk, 在给webpack写插件的时候会用到

class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
beforeRun: new AsyncSeriesHook(["compilation"]),
run: new AsyncSeriesHook(["compilation"]),
emit: new AsyncSeriesHook(["compilation"]),
afterEmit: new AsyncSeriesHook(["compilation"]),
compilation: new SyncHook(["compilation", "params"]),
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
make: new AsyncParallelHook(["compilation"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
// other hooks
};
// ...
} run(callback) {
const startTime = Date.now(); const onCompiled = (err, compilation) => {
// ... this.emitAssets(compilation, err => {
if (err) return callback(err); if (compilation.hooks.needAdditionalPass.call()) {
compilation.needAdditionalPass = true; const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
this.hooks.done.callAsync(stats, err => {
if (err) return callback(err); this.hooks.additionalPass.callAsync(err => {
if (err) return callback(err);
this.compile(onCompiled);
});
});
return;
}
// ...
});
}; this.hooks.beforeRun.callAsync(this, err => {
if (err) return callback(err);
this.hooks.run.callAsync(this, err => {
if (err) return callback(err); this.readRecords(err => {
if (err) return callback(err); this.compile(onCompiled);
});
});
});
}
// 输出文件到构建目录
emitAssets(compilation, callback) {
// ...
this.hooks.emit.callAsync(compilation, err => {
if (err) return callback(err);
outputPath = compilation.getPath(this.outputPath);
this.outputFileSystem.mkdirp(outputPath, emitFiles);
});
} newCompilationParams() {
const params = {
normalModuleFactory: this.createNormalModuleFactory(),
contextModuleFactory: this.createContextModuleFactory(),
compilationDependencies: new Set()
};
return params;
} compile(callback) {
const params = this.newCompilationParams();
this.hooks.beforeCompile.callAsync(params, err => {
if (err) return callback(err);
this.hooks.compile.call(params);
const compilation = this.newCompilation(params); this.hooks.make.callAsync(compilation, err => {
if (err) return callback(err);
compilation.finish();
// make 钩子执行后,调用seal生成资源
compilation.seal(err => {
if (err) return callback(err);
this.hooks.afterCompile.callAsync(compilation, err => {
if (err) return callback(err);
// emit, 生成最终文件
return callback(null, compilation);
});
});
});
});
}
}

最后输出

seal执行后,便会调用emit钩子,根据webpack config文件的output配置的path属性,将文件输出到指定的path.

最后

webpack详解的更多相关文章

  1. webpack --- 详解

    官网: http://webpack.github.io/docs/using-loaders.html 简书: http://www.jianshu.com/p/42e11515c10f

  2. 五. Webpack详解

    1. 什么是Webpack 1.1 引入 什么是webpack?这个webpack还真不是一两句话可以说清楚的. 我们先看看官方的解释:At its core, webpack is a static ...

  3. 详解前端模块化工具-webpack

    webpack是一个module bundler,抛开博大精深的汉字问题,我们暂且管他叫'模块管理工具'.随着js能做的事情越来越多,浏览器.服务器,js似乎无处不在,这时,使日渐增多的js代码变得合 ...

  4. 六. Vue CLI详解

    1. Vue CLI理解 1.1 什么是Vue CLI 如果你只是简单写几个Vue的Demo程序, 那么你不需要Vue CLI,如果你在开发大型项目那么你需要它, 并且必然需要使用Vue CLI. 使 ...

  5. webpack配置详解

    webpack配置详解 先点个赞吧,再挨个点下面的连接,觉得不值这个赞的回来骂我啊. Webpack傻瓜式指南(一) Webpack傻瓜指南(二)开发和部署技巧 Webpack傻瓜式指南 原生的官网详 ...

  6. webpack安装配置使用教程详解

    webpack安装配置使用教程详解 www.111cn.net 更新:2015-09-01 编辑:swteen 来源:转载 本文章来为各位详细的介绍一下关于webpack安装配置使用教程吧,这篇文章对 ...

  7. [js高手之路]深入浅出webpack系列2-配置文件webpack.config.js详解

    接着上文,重新在webpack文件夹下面新建一个项目文件夹demo2,然后用npm init --yes初始化项目的package.json配置文件,然后安装webpack( npm install ...

  8. [js高手之路]深入浅出webpack教程系列3-配置文件webpack.config.js详解(下)

    本文继续接着上文,继续写下webpack.config.js的其他配置用法. 一.把两个文件打包成一个,entry怎么配置? 在上文中的webpack.dev.config.js中,用数组配置entr ...

  9. Webpack执行命令参数详解

    一.概述前面的章节我们讲解了webpack的安装.webpack.config.js的 基本配置.webpack执行命名以及require方法的使用,不 知道大家有没有发现,当我们每次修改或者新增一个 ...

随机推荐

  1. ACM数论之旅8---组合数(组合大法好(,,• ₃ •,,) )

    组合数并不陌生(´・ω・`) 我们都学过组合数 会求组合数吗 一般我们用杨辉三角性质 杨辉三角上的每一个数字都等于它的左上方和右上方的和(除了边界) 第n行,第m个就是,就是C(n, m) (从0开始 ...

  2. response和request的setCharacterEncoding区别

    一.request.setCharacterEncoding():是设置从request中取得的值或从数据库中取出的值. 指定后可以通过getParameter()则直接获得正确的字符串,如果不指定, ...

  3. ng-include 上ng-controller 无法获取控件

    A.Html内容如下 <div> <div kendo-grid="testGrid" k-options="testOptions"> ...

  4. 简明Python教程自学笔记——命令行通讯录

    [前言]学习Python已经有一段时间了,相关的书籍资料也下载了不少,但是没有一本完整的看完,也没有编出一个完整的程序.今天下午比较清闲就把<简明Python教程>看了一遍,然后根据书里面 ...

  5. Theme Section HDU - 4763(些许暴力)

    题意: 求出最长公共前后缀 不能重叠  而且 这个前后缀 在串的中间也要出现一次 解析: 再明确一次next数组的意思:完全匹配的最长前后缀长度 求一遍next 然后暴力枚举就好了 #include ...

  6. LINQ 模糊搜索

    IList<entity> ls = new List<entity>(); ls = (from k in ls where k.Name.Contains("sa ...

  7. 命令行Scp的使用----远程拷贝文件

    1.用CRT分别连上两台需要传输文件的linux系统服务器,并检查防火墙是否关闭. 查看防火墙状态: /etc/init.d/iptables status 若防火墙启用,暂时关闭防火墙: /etc/ ...

  8. 【BZOJ3105】【CQOI2013】新Nim游戏

    Description 传统的Nim游戏是这样的:有一些火柴堆,每堆都有若干根火柴(不同堆的火柴数量可以不同).两个游戏者轮流操作,每次可以选一个火柴堆拿走若干根火柴.可以只拿一根,也可以拿走整堆火柴 ...

  9. VS2010 重命名文件:源文件名和目标文件名相同 的解决方案

    想要在“”解决方案资源管理器“”中修改一个已经写好的文件的文件名,如图: 在改了几次后就出现了如图的问题: 然而在“解决方案资源管理器”中并没有看到,于是我打开了工程在磁盘中的位置文件夹: 意外发现了 ...

  10. View 的setTag() 和 getTag()

    View 的setTag() 和 getTag()ImageView.setTag(""); 就是当多个ImageView用到一个监听器时,能触发不同的情况,是View的方法 Ad ...