Webpack 详解

作者: ixlei https://segmentfault.com/a/1190000013657042

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和事件流(https://segmentfault.com/a/1190000008060440)。

那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方法注册一个consumentorder.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();})// 调用// 返回Promiseorder.consumerInfoPromise('user007', '1024');//回调函数order.consumerInfoAsync('user007', '1024')

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

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

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

  • 注册在该钩子下面的插件的执行顺序都是顺序执行。
  • 只能使用 tap注册,不能使用 tapPromisetapAsync注册
// 所有的钩子都继承于Hookclass 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.

原文发布于微信公众号 - 程序员宝库(chengxuyuanbaoku)

原文发表时间:2018-07-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏腾讯云API

腾讯云API:用Python使用腾讯云API(cvm实例)

腾讯云API地址:https://cloud.tencent.com/document/api

7784
来自专栏finleyMa

Laravel 用户权限管理

实现基于user,role,permission三表的权限管理 因为一个用户可能拥有多种role,而一种role能同时被多个用户拥有。所以要建立多对多关系。 ...

7445
来自专栏柠檬先生

RHS 和 LHS

773
来自专栏向治洪

JSBridge深度剖析

概述 做过混合开发的人都知道Ionic和PhoneGap之类的框架,这些框架在web基础上包装一层Native,然后通过Bridge技术的js调用本地的库。 在...

3155
来自专栏熊二哥

快速入门系列--WebAPI--04在老版本MVC4下的调整

WebAPI是建立在MVC和WCF的基础上的,原来微软老是喜欢封装的很多,这次终于愿意将http编程模型的相关细节暴露给我们了。在之前的介绍中,基本上都基于.N...

2396
来自专栏有趣的django

37.Django1.11.6文档

第一步 入门 检查版本 python -m django --version 创建第一个项目 django-admin startproject mysite ...

4848
来自专栏Rindew的iOS技术分享

苏宁一面

1834
来自专栏腾讯云API

腾讯云API:无服务器函数

无服务器函数是一个很好玩的东西,可以通过这个程序跑一些脚本,在一定程度上,是很方便的。但是作为新鲜事物,一般很难被大家接受,所以,我今天在这里,就做一个小例子,...

8895
来自专栏容器云生态

Docker-client for python使用指南

Docker-client for python使用指南: 客户端初始化的三种方法 import docker docker.api() docker.APIC...

1.1K10
来自专栏酷玩时刻

支付宝支付-扫码支付

扫码支付,指用户打开支付宝钱包中的“扫一扫”功能,扫描商户针对每个订单实时生成的订单二维码,并在手机端确认支付。

1923

扫码关注云+社区

领取腾讯云代金券