webpack執行過程

webpack-cli

執行過程

1.webpack.config.js,shell options參數解析
2.new webpack(options)
3.run() 編譯的入口方法
4.compile() 出發make事件
5.addEntry() 找到js文件,進行下一步模塊綁定
6._addModuleChain() 解析js入口文件,建立模塊
7.buildModule() 編譯模塊,loader處理與acorn處理AST語法樹
8.seal() 每個chunk對應一個入口文件
9.createChunkAssets() 生成資源文件
10.MainTemplate.render() __webpack__require()引入
11.ModuleTemplate.render() 生成模版
12.module.source() 將生成好的js保存在compilation.assets中
13.Compiler.emitAssets()經過emitAssets將最終的js輸出到output的path中

參數解析

(function(){
    yargs.options({...})
    
    yargs.parse(process.argv.slice(2), (err, argv, output) => {...})
})()

加載webpack.config.js

(function(){
    ...
    yargs.parse(process.argv.slice(2), (err, argv, output) => {
        ...
        //解析argv,拿到配置文件參數
       let options =  require("./convert-argv")(argv);
       function processOptions(options){
           ...
       } 
        
       processOptions(options);
    })
})()

執行webpack()

(function(){
    ...
    yargs.parse(process.argv.slice(2), (err, argv, output) => {
        ...
        //解析argv,拿到配置文件參數
       let options =  require("./convert-argv")(argv);
       function processOptions(options){
           ...
           const webpack = require("webpack");
           compiler = webpack(options);   
       } 
        
       processOptions(options);
    })
})()

webpack.js

const webpack = (options, callback) => {
    
    //驗證webpack.config.js合法性
    const webpackOptionsValidationErrors = validateSchema(
        webpackOptionsSchema,
        options
    );
    
    /*
        [
          { entry: './index1.js', output: { filename: 'bundle1.js' } },
          { entry: './index2.js', output: { filename: 'bundle2.js' } }
        ]
    */
    if (Array.isArray(options)) {
        compiler = new MultiCompiler(options.map(options => webpack(options)));
    } else if(typeof options === "object"){
        ...
        //建立一個comiler對象
        compiler = new Compiler(options.context);
        
        //往comiler中註冊插件
        new NodeEnvironmentPlugin().apply(compiler);
        
        //執行config中配置的插件
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                if (typeof plugin === "function") {
                    plugin.call(compiler, compiler);
                } else {
                    plugin.apply(compiler);
                }
            }
        }
        
        //執行插件environment生命週期鉤子方法
        compiler.hooks.environment.call();
        compiler.hooks.afterEnvironment.call();
        //執行webpack內置插件
        compiler.options = new
        WebpackOptionsApply().process(options, compiler);
    }else {
        throw new Error("Invalid argument: options");
    }
    
    if (callback) {
        ...
        //調用compiler.run開始編譯
        compiler.run(callback);
    }
    //將compiler對象返回
    return compiler
}
//NodeEnvironmentPlugin.js
class NodeEnvironmentPlugin {
    apply(compiler) {
        ...
        compiler.hooks.beforeRun.tap("NodeEnvironmentPlugin", compiler => {
            if (compiler.inputFileSystem === inputFileSystem) inputFileSystem.purge();
        });
    }
}
module.exports = NodeEnvironmentPlugin;
class WebpackOptionsApply extends OptionsApply {
    constructor() {
        super();
    }
    process(options, compiler) {
        //掛載配置,執行插件
        let ExternalsPlugin;
        compiler.outputPath = options.output.path;
        compiler.recordsInputPath = options.recordsInputPath || options.recordsPath;
        compiler.recordsOutputPath =
            options.recordsOutputPath || options.recordsPath;
        compiler.name = options.name;
        
        new EntryOptionPlugin().apply(compiler);
        new HarmonyModulesPlugin(options.module).apply(compiler);
        new LoaderPlugin().apply(compiler);
        ...
    }
}

module.exports = WebpackOptionsApply;

compiler.run() 開始編譯

class Compiler extends Tapable{
    constructor(context){
        ...
    }
    watch(){...}
    run(callback){
        ...
        const onCompiled = (err, compilation){
            ...
        } 
        //執行生命週期鉤子
        this.hooks.beforeRun.callAsync(this, err => {
              ...
            this.hooks.run.callAsync(this, err =>{
                this.readRecords(err =>{
                    ...
                    //開始編譯
                    this.compile(onCompiled);
                })
            }
        }
    }
    compile(callback) {
        //拿到參數
        const params = this.newCompilationParams();
        //執行編譯前鉤子
        this.hooks.beforeCompile.callAsync(params, err => {
            ...
            
            //建立compilation對象
            const compilation = this.newCompilation(params);
            
            //開始構建模塊對象
            this.hooks.make.callAsync(compilation, err =>{
                
            })
        }
    }
    createCompilation() {
        //建立comilation對象
        return new Compilation(this);
    }
    newCompilation(params) {
        //調用建立compilation對象方法
        const compilation = this.createCompilation();
    }
}

module.exports = Compiler;

建立 Compilation()

class Compilation extends Tapable {
    constructor(compiler) {
        super();
        ...
        //初始化配置
        this.compiler = compiler;
        this.resolverFactory = compiler.resolverFactory;
        this.inputFileSystem = compiler.inputFileSystem;
        this.requestShortener = compiler.requestShortener;
        
        //初始化模版
        this.mainTemplate = new MainTemplate(this.outputOptions);
        this.chunkTemplate = new ChunkTemplate(this.outputOptions);
        this.hotUpdateChunkTemplate = new HotUpdateChunkTemplate(
            this.outputOptions
        );
    }
}
class MainTemplate extends Tapable {
    this.hooks.requireExtensions.tap("MainTemplate", (source, chunk, hash) => {
            const buf = [];
            const chunkMaps = chunk.getChunkMaps();
            // Check if there are non initial chunks which need to be imported using require-ensure
            if (Object.keys(chunkMaps.hash).length) {
                buf.push("// This file contains only the entry chunk.");
                buf.push("// The chunk loading function for additional chunks");
                buf.push(`${this.requireFn}.e = function requireEnsure(chunkId) {`);
                buf.push(Template.indent("var promises = [];"));
                buf.push(
                    Template.indent(
                        this.hooks.requireEnsure.call("", chunk, hash, "chunkId")
                    )
                );
                buf.push(Template.indent("return Promise.all(promises);"));
                buf.push("};");
            } else if (
                chunk.hasModuleInGraph(m =>
                    m.blocks.some(b => b.chunkGroup && b.chunkGroup.chunks.length > 0)
                )
            ) {
                // There async blocks in the graph, so we need to add an empty requireEnsure
                // function anyway. This can happen with multiple entrypoints.
                buf.push("// The chunk loading function for additional chunks");
                buf.push("// Since all referenced chunks are already included");
                buf.push("// in this file, this function is empty here.");
                buf.push(`${this.requireFn}.e = function requireEnsure() {`);
                buf.push(Template.indent("return Promise.resolve();"));
                buf.push("};");
            }
            buf.push("");
            buf.push("// expose the modules object (__webpack_modules__)");
            buf.push(`${this.requireFn}.m = modules;`);

            buf.push("");
            buf.push("// expose the module cache");
            buf.push(`${this.requireFn}.c = installedModules;`);

            buf.push("");
            buf.push("// define getter function for harmony exports");
            buf.push(`${this.requireFn}.d = function(exports, name, getter) {`);
            buf.push(
                Template.indent([
                    `if(!${this.requireFn}.o(exports, name)) {`,
                    Template.indent([
                        "Object.defineProperty(exports, name, { enumerable: true, get: getter });"
                    ]),
                    "}"
                ])
            );
            buf.push("};");

            buf.push("");
            buf.push("// define __esModule on exports");
            buf.push(`${this.requireFn}.r = function(exports) {`);
            buf.push(
                Template.indent([
                    "if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {",
                    Template.indent([
                        "Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });"
                    ]),
                    "}",
                    "Object.defineProperty(exports, '__esModule', { value: true });"
                ])
            );
            buf.push("};");

            buf.push("");
            buf.push("// create a fake namespace object");
            buf.push("// mode & 1: value is a module id, require it");
            buf.push("// mode & 2: merge all properties of value into the ns");
            buf.push("// mode & 4: return value when already ns object");
            buf.push("// mode & 8|1: behave like require");
            buf.push(`${this.requireFn}.t = function(value, mode) {`);
            buf.push(
                Template.indent([
                    `if(mode & 1) value = ${this.requireFn}(value);`,
                    `if(mode & 8) return value;`,
                    "if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;",
                    "var ns = Object.create(null);",
                    `${this.requireFn}.r(ns);`,
                    "Object.defineProperty(ns, 'default', { enumerable: true, value: value });",
                    "if(mode & 2 && typeof value != 'string') for(var key in value) " +
                        `${this.requireFn}.d(ns, key, function(key) { ` +
                        "return value[key]; " +
                        "}.bind(null, key));",
                    "return ns;"
                ])
            );
            buf.push("};");

            buf.push("");
            buf.push(
                "// getDefaultExport function for compatibility with non-harmony modules"
            );
            buf.push(this.requireFn + ".n = function(module) {");
            buf.push(
                Template.indent([
                    "var getter = module && module.__esModule ?",
                    Template.indent([
                        "function getDefault() { return module['default']; } :",
                        "function getModuleExports() { return module; };"
                    ]),
                    `${this.requireFn}.d(getter, 'a', getter);`,
                    "return getter;"
                ])
            );
            buf.push("};");

            buf.push("");
            buf.push("// Object.prototype.hasOwnProperty.call");
            buf.push(
                `${
                    this.requireFn
                }.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };`
            );

            const publicPath = this.getPublicPath({
                hash: hash
            });
            buf.push("");
            buf.push("// __webpack_public_path__");
            buf.push(`${this.requireFn}.p = ${JSON.stringify(publicPath)};`);
            return Template.asString(buf);
        });
}

make開始構建

//開始構建模塊對象
this.hooks.make.callAsync(compilation, err =>{
                
})
//SingleEntryPlugin 監聽make
class SingleEntryPlugin {
    apply(compiler) {
        compiler.hooks.make.tapAsync(
            "SingleEntryPlugin",
            (compilation, callback) => {
                const { entry, name, context } = this;
                
                //建立依賴
                const dep = SingleEntryPlugin.createDependency(entry, name);
                //添加入口文件
                compilation.addEntry(context, dep, name, callback);
            }
        );
    }
}
//Compilation.js
class Compilation extends Tapable {
    addEntry(context, entry, name, callback) {
        ...
            this._addModuleChain(
            context,
            entry,
            module => {
                this.entries.push(module);
            },
            (err, module) => {
            ...
            }
        );
    }
    _addModuleChain(context, dependency, onModule, callback) {
        ...
        //獲取模塊工廠
        const moduleFactory = this.dependencyFactories.get(Dep);
        
        this.semaphore.acquire(() => {
            ...
            //建立模塊
            moduleFactory.create(
                {
                    contextInfo: {
                        issuer: "",
                        compiler: this.compiler.name
                    },
                    context: context,
                    dependencies: [dependency]
                },...)
        }
    }
}
class NormalModuleFactory extends Tapable {
    ...
    create(data, callback) {
        ...
        this.buildModule(module, false, null, null, err => {
        }
    }
    buildModule(module, optional, origin, dependencies, thisCallback) {
        ...
        //開始編譯
        module.build(
            this.options,
            this,
            this.resolverFactory.get("normal", module.resolveOptions),
            this.inputFileSystem,...)
    }
}
//NodmalModule
doBuild(options, compilation, resolver, fs, callback) {
        const loaderContext = this.createLoaderContext(
            resolver,
            options,
            compilation,
            fs
        );
        ...
        //開始運行loader
        runLoaders(
            {
                resource: this.resource,
                loaders: this.loaders,
                context: loaderContext,
                readResource: fs.readFile.bind(fs)
            },
            (err, result) => {
            
            );
         )  }

總結

初始化階段

事件名 解釋 代碼位置
讀取命令行參數 從命令行中讀取用戶輸入的參數 require('./convert-argv')(argv)
實例化Compiler 1.用上一步獲得的參數初始化Compiler實例2.Compiler負責文件監聽和啓動編譯3.Compiler實例中包含了完整的Webpack配置,全局只有一個Compiler實例 compiler = webpack(options)
加載插件 1.依次調用插件的apply方法,讓插件能夠監聽後續的全部事件節點,同時給插件傳入Compiler實例的引用,以方便插件經過compiler調用webpack提供的API plugin.apply(compiler)
處理入口 讀取配置的Entry,爲每一個Entry實例化一個對應的EntryPlugin,爲後面該Entry的遞歸解析工做作準備 new EntryOptionsPlugin() new SingleEntryPlugin(context,item,name) compiler.hooks.make.tapAsync

編譯階段

事件名 解釋 代碼位置
run 啓動一次新的編譯 this.hooks.run.callAsync
compile 該事件是爲了告訴插件一次新的編譯將要啓動,同時會給插件傳入compiler對象 compiler(callback)
compilation 當webpack以開發模式運行時,每當監測到文件變化,一次新的,Compilation將被建立一個Compilation對象包含了當前的模塊資源,編譯生成資源,變化的文件,Compilation對象也提供了不少事件回調供插件擴展 newCompilation(params)
make 一個新的Compilation建立完畢開始編譯 this.hooks.make.callAsync
addEntry 即將從Entry開始讀取文件 compilation.addEntry this._addModuleChain
moduleFactory 建立模塊工廠 const moduleFactory = this.dependencyFactories.get(Dep)
create 開始建立模塊 factory(result,(err,module) this.hooks.resolver.tap("NormalModule")
resolveRequestArray 解析loader路徑 resolveRequestArray
resolve 解析資源文件路徑 resolve
userRequest 獲得包括loader在內的資源文件的絕對路徑用!拼起來的字符串 userRequest
ruleSet.exec 它能夠根據模塊路徑名,匹配出模塊所需的loader this.ruleSet.exec
_run 它能夠根據模塊路徑名,匹配出模塊所需的loader _run
loaders 獲得全部的loader數組 results[0].concat(loaders,results[1],results[2])
getParser 獲取AST解析器 this.getParser(type,setting.parser)
buildModule 開始編譯模塊 thislbuildModule(module) buildModule(module,optional,origin,dependencies,thisCallback)
build 開始編譯 build
doBuild 開始編譯 doBuild
loader 使用loader進行轉換 runLoaders
iteratePitchingLoaders 開始遞歸執行pitchloader iteratePitchingLoaders
loadLoader 加載loader loadLoader
runSyncOrAsync 執行loader runSyncOrAsync
processResource 開始處理資源 processResource options.readResource iterateNormalLoaders
createSource 建立源碼對象 this.createSource
parse 使用parser轉換抽象語法樹 this.parser.parse
parse 繼續抽象語法樹 parse(source,initialState)
acorn.parse 繼續語法樹 acorn.parse(code,parserOptions)
ImportDependency 遍歷添加依賴 parser.state.module.addDependency
succeedModule 生成語法樹後就表示一個模塊編譯完成 this.hooks.successdModule.call(module)
processModuleDependencies 遞歸編譯依賴模塊 this.processModuleDependencies
make後 結束make this.hooks.make.callAsync
finish 編譯完成 compilation.finishi()

結束階段

事件名 解釋 代碼位置
seal 封裝 compilation.seal
addChunk 生成資源 addChunk(name)
createChunkAssets 建立資源 this.createChunkAssets()
getRenderManifest 獲取要渲染的描述文件 getRenderManifest(options)
render 渲染源碼 source = fileManifest.render()
afterCompile 編譯結束 this.hooks.afterCompile
shouldemit 全部屬性輸出的文件已經生成好,詢問插件哪些文件須要輸出,哪些不須要 this.hooks.shouldEmit
emit 肯定後要輸出哪些文件後,執行文件輸出,能夠在這裏獲取和修改輸出內容 this.emitAssets(compilation,this.hooks.emit.callAsync) const emitFiles = err this.outputFileSystem.writeFile
this.emitRecords 寫入記錄 this.emitRecords
done 所有完成 this.hooks.done.callAsync
相關文章
相關標籤/搜索