웹 팩 시리즈 - 제3 편 프로 세 스 잡기

시리즈
웹 팩 시리즈 - 첫 번 째 기초 잡기 웹 팩 시리즈 - 두 번 째 플러그 인 메커니즘 잡기 웹 팩 시리즈 - 세 번 째 프로 세 스 잡기
머리말
이 글 은 개인 적 으로 웹 팩 프로 세 스 를 정리 하기 위해 내부 의 많은 세부 사항 에 관심 을 가지 지 않 았 습 니 다. 오류 가 있 으 면 가볍게 뿌 려 주세요 ~
디 버 깅
1. 다음 명령 으로 프로젝트 를 실행 합 니 다. ./scripts/build.js 디 버 깅 을 시작 하고 싶 은 곳 입 니 다.
node --inspect-brk ./scripts/build.js --inline --progress

2. 오픈 chrome://inspect/#devices 하면 디 버 깅 가능
흐름 도
입구
입구 bulid.js 에 있 습 니 다. 그 중의 코드 는 먼저 웹 팩 을 예화 한 다음 에 호출 compiler run 하 는 것 을 볼 수 있 습 니 다.
function build(previousFileSizes) {
  let compiler = webpack(config);
  return new Promise((resolve, reject) => {
    compiler.run((err, stats) => {
      ...
  });
}

entry-option(compiler)
webpack.js
웹 팩 은 nodemoduls 아래 \webpack\lib\webpack.js (이 앞 에 입구 매개 변수 가 합 쳐 져 있 음) 이 파일 을 찾 으 면 다음 과 같은 코드 를 볼 수 있 습 니 다.
const webpack = (options, callback) => {
    ......
    let compiler;
    //       
    if (Array.isArray(options)) {
        compiler = new MultiCompiler(options.map(options => webpack(options)));
    } else if (typeof options === "object") {
        // webpack     
        options = new WebpackOptionsDefaulter().process(options);
        console.log(options) //    
        //    compiler
        compiler = new Compiler(options.context);
        compiler.options = options;
        //  webpack       
        new NodeEnvironmentPlugin().apply(compiler);
        //      tabpable            
        if (options.plugins && Array.isArray(options.plugins)) {
            for (const plugin of options.plugins) {
                plugin.apply(compiler);
            }
        }
        //         environment/afterEnviroment
        compiler.hooks.environment.call();
        compiler.hooks.afterEnvironment.call();
        //   compiler             ,       entry-option
        compiler.options = new WebpackOptionsApply().process(options, compiler);
    } else {
        throw new Error("Invalid argument: options");
    }
    if (callback) {
        ......
        compiler.run(callback);
    }
    return compiler;
};

options 가 저장 한 것 은 이번 웹 팩 의 일부 설정 매개 변수 이 며, 그 중 plugins webpack 입 니 다.
new WebpackOptionsApply().process
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;
    compiler.dependencies = options.dependencies;
    if (typeof options.target === "string") {
        let JsonpTemplatePlugin;
        let FetchCompileWasmTemplatePlugin;
        let ReadFileCompileWasmTemplatePlugin;
        let NodeSourcePlugin;
        let NodeTargetPlugin;
        let NodeTemplatePlugin;
    
        switch (options.target) {
            case "web":
                JsonpTemplatePlugin = require("./web/JsonpTemplatePlugin");
                FetchCompileWasmTemplatePlugin = require("./web/FetchCompileWasmTemplatePlugin");
                NodeSourcePlugin = require("./node/NodeSourcePlugin");
                new JsonpTemplatePlugin().apply(compiler);
                new FetchCompileWasmTemplatePlugin({
                    mangleImports: options.optimization.mangleWasmImports
                }).apply(compiler);
                new FunctionModulePlugin().apply(compiler);
                new NodeSourcePlugin(options.node).apply(compiler);
                new LoaderTargetPlugin(options.target).apply(compiler);
                break;
            case "webworker":......
            ......
        }
    }
    new JavascriptModulesPlugin().apply(compiler);
    new JsonModulesPlugin().apply(compiler);
    new WebAssemblyModulesPlugin({
        mangleImports: options.optimization.mangleWasmImports
    }).apply(compiler);
    
    new EntryOptionPlugin().apply(compiler);
    //      entry-options      context entry 
    compiler.hooks.entryOption.call(options.context, options.entry);
    new CompatibilityPlugin().apply(compiler);
    ......
    new ImportPlugin(options.module).apply(compiler);
    new SystemPlugin(options.module).apply(compiler);
}

run(compiler)
run 을 호출 할 때 내부 트리거 beforeRun 를 한 다음 읽 기 recodes (records 에 대해 서 는 이 문 서 를 참고 할 수 있 습 니 다) 전에 run 이벤트 점 을 터치 합 니 다. 이 두 사건 은 모두 비동기 형식 입 니 다. 주의 run webpack .마지막 으로 호출 된 것 은 compile 방법 이 며, 동시에 들 어 오 는 것 은 onCompiled 함수 입 니 다.
run(callback) {
    if (this.running) return callback(new ConcurrentCompilationError());
    const finalCallback = (err, stats) => {
        ......
    };
    this.running = true;
    
    const onCompiled = (err, compilation) => {
        ....
    };
    
    this.hooks.beforeRun.callAsync(this, err => {
        if (err) return finalCallback(err);
    
        this.hooks.run.callAsync(this, err => {
            if (err) return finalCallback(err);
    
            this.readRecords(err => {
                if (err) return finalCallback(err);
    
                this.compile(onCompiled);
            });
        });
    });
}

compile(compiler)
copile 방법 은 주로 트리거 beforeCompile、compile、make compilation 를 볼 수 있 습 니 다. 여기 서 copile 에 전 달 된 newCompilationParams 매개 변 수 를 볼 수 있 습 니 다. 이 매개 변 수 는 뒤의 상대 적 인 절차 에서 도 비교적 중요 합 니 다. 여기 서 먼저 볼 수 있 습 니 다.
compile(callback) {
    const params = this.newCompilationParams();
    //      beforeCompile,     CompilationParams
    this.hooks.beforeCompile.callAsync(params, err => {
        if (err) return callback(err);
        //      compile,     CompilationParams
        this.hooks.compile.call(params);
        //    compilation
        const compilation = this.newCompilation(params);
        //      make
        this.hooks.make.callAsync(compilation, err => {
            ....
        });
    });
}

new Compilation Params 가 돌아 오 는 매개 변 수 는 각각 두 공장 함수 와 하나의 set 집합 이다.
newCompilationParams() {
    const params = {
        normalModuleFactory: this.createNormalModuleFactory(),
        contextModuleFactory: this.createContextModuleFactory(),
        compilationDependencies: new Set()
    };
    return params;
}

compilation(compiler)
위의 copile 방법 을 보면 compilation 는 new Compilation 방법 으로 호출 되 어 생 성 된 것 입 니 다. 그리고 이벤트 포인트 thisCompilation compilation 를 터치 하면 알 수 있 습 니 다 compilation . 플러그 인 을 작성 할 때 이 대상 을 빨리 사용 해 야 한다 면 이 두 이벤트 에서 진행 해 야 합 니 다.
createCompilation() {
    return new Compilation(this);
}
newCompilation(params) {
    const compilation = this.createCompilation();
    compilation.fileTimestamps = this.fileTimestamps;
    compilation.contextTimestamps = this.contextTimestamps;
    compilation.name = this.name;
    compilation.records = this.records;
    compilation.compilationDependencies = params.compilationDependencies;
    //      thisCompilation compilation,       compilation params
    this.hooks.thisCompilation.call(compilation, params);
    this.hooks.compilation.call(compilation, params);
    return compilation;
}

다음은 인쇄 된 compilation 속성 입 니 다.
여기 thisCompilation (childCompiler) 에 대해 이 글 을 참고 하여 요약 하면:
하위 컴 파 일 러 는 완전한 모듈 해석 과 chunk 생 성 단 계 를 가지 고 있 지만, "make", "compile", "emit", "after - emit", "invalid", "done", "this - compilation" 와 같은 이벤트 점 이 적 습 니 다.즉, 우 리 는 하위 컴 파일 러 를 이용 하여 독립 적 으로 (부모 컴 파일 러) 핵심 구축 절 차 를 마치 고 필요 한 모듈 이나 chunk 를 추가 로 생 성 할 수 있다.
make(compiler)
위의 copile 방법 을 통 해 알 수 있 듯 이 Compilation 를 예화 하면 make 됩 니 다.make 를 실행 할 때 웹 팩 이 앞 에 있 기 때문에 SingleEntryPlugin MultleEntryPlugin, SingleEntry Plugin 은 apply 방법 에 make 이 벤트 를 등록 하 였 습 니 다.
apply(compiler) {
    compiler.hooks.compilation.tap(
        "SingleEntryPlugin",
        (compilation, { normalModuleFactory }) => {
            compilation.dependencyFactories.set(
                SingleEntryDependency,
                normalModuleFactory  //     ,  compilation dependencyFactories  
            );
        }
    );
    
    compiler.hooks.make.tapAsync(
        "SingleEntryPlugin",
        (compilation, callback) => {
            const { entry, name, context } = this;
    
            const dep = SingleEntryPlugin.createDependency(entry, name);
            //    addEntry
            compilation.addEntry(context, dep, name, callback);
        }
    );
}

사실 addEntry 호출 은 Comilation._addModuleChain, acquire 함수 가 간단 합 니 다. 주로 module 을 처리 할 때 작업 이 너무 많 으 면 moduleFactory. create 를 대기 열 에 저장 합 니 다.
_addModuleChain(context, dependency, onModule, callback) {
    ......
    //      Factory
    const Dep = /** @type {DepConstructor} */ (dependency.constructor);
    const moduleFactory = this.dependencyFactories.get(Dep);
    ......
    this.semaphore.acquire(() => {
        moduleFactory.create(
            {
                contextInfo: {
                    issuer: "",
                    compiler: this.compiler.name
                },
                context: context,
                dependencies: [dependency]
            },
            (err, module) => {
                ......
            }
        );
    });
    }

moduleFactory. create 는 module buildModule(compilation)
리 셋 함수 주요 상 집행 buildModule 방법
this.buildModule(module, false, null, null, err => {
    ......
    afterBuild();
});
buildModule(module, optional, origin, dependencies, thisCallback) {
    //       
    let callbackList = this._buildingModules.get(module);
    if (callbackList) {
        callbackList.push(thisCallback);
        return;
    }
    this._buildingModules.set(module, (callbackList = [thisCallback]));
    
    const callback = err => {
        this._buildingModules.delete(module);
        for (const cb of callbackList) {
            cb(err);
        }
    };
    //   buildModule   
    this.hooks.buildModule.call(module);
    module.build(
        this.options,
        this,
        this.resolverFactory.get("normal", module.resolveOptions),
        this.inputFileSystem,
        error => {
            ......
        }
    );
    }
build doBuild,doBuild runLoaders loader webpack js , doBuild parse , Dependency
return this.doBuild(options, compilation, resolver, fs, err => {
    //  createLoaderContext       normal-module-loader
    const loaderContext = this.createLoaderContext(
        resolver,
        options,
        compilation,
        fs
    );
    .....
    const handleParseResult = result => {
        this._lastSuccessfulBuildMeta = this.buildMeta;
        this._initBuildHash(compilation);
        return callback();
    };
    
    try {
        //   parser.parse
        const result = this.parser.parse(
            this._ast || this._source.source(),
            {
                current: this,
                module: this,
                compilation: compilation,
                options: options
            },
            (err, result) => {
                if (err) {
                    handleParseError(err);
                } else {
                    handleParseResult(result);
                }
            }
        );
        if (result !== undefined) {
            // parse is sync
            handleParseResult(result);
        }
    } catch (e) {
        handleParseError(e);
    }
    });
ast
succeedModule(compilation)
마지막 으로 module. build 의 리 셋 함 수 를 실 행 했 습 니 다. 트리거 succeedModule 를 실 행 했 고 Compilation. buildModule 함수 의 리 셋 함수 로 돌 아 왔 습 니 다.
module.build(
    this.options,
    this,
    this.resolverFactory.get("normal", module.resolveOptions),
    this.inputFileSystem,
    error => {
        ......
              succeedModule
        this.hooks.succeedModule.call(module);
        return callback();
    }
);

this.buildModule(module, false, null, null, err => {
    ......
    //   afterBuild
    afterBuild();
});

현재 모듈 에 여러 개의 의존 모듈 이 존재 할 수 있 습 니 다.현재 모듈 은 모듈 에 의존 하 는 배열 을 열 고 AST , require() addDependency() 。 ,webpack processModuleDependencies module 다음 에 이전의 구축 절 차 를 반복 합 니 다.
 Compilation.prototype.addModuleDependencies = function(module, dependencies, bail, cacheGroup, recursive, callback) {
  //       (dependencies)        
  var factories = [];
  for (var i = 0; i < dependencies.length; i++) {
    var factory = _this.dependencyFactories.get(dependencies[i][0].constructor);
    factories[i] = [factory, dependencies[i]];
  }
  ...
  //            
}

마지막 으로 모든 모듈 은 Compilation modules 에 넣 습 니 다. 다음 과 같 습 니 다.
요약:
module 은 webpack 이 구축 한 핵심 실체 이자 모든 module 의 부모 클래스 입 니 다. 몇 가지 서로 다른 하위 클래스 가 있 습 니 다. Normal Module, MultiModule, ContextModule, Delegated Module 등 의존 대상 입 니 다.(Dependency, 모듈 인 스 턴 스 의 의존 대상 으로 해석 되 지 않 았 습 니 다. 예 를 들 어 웹 팩 을 실행 할 때 들 어 오 는 입구 모듈 이나 모듈 이 의존 하 는 다른 모듈 은 Dependency 대상 이 됩 니 다.) 해당 하 는 공장 대상 (Factory) 을 통 해 생 성 된 모듈 인 스 턴 스 (Module) 를 생 성 할 수 있 습 니 다.
seal(compilation) module , Compilation.seal, seal, chunk 모든 chunks 가 생 성 된 후에 webpack 은 chunks 와 modules 에 대해 최적화 와 관련 된 조작 을 할 것 이다. 예 를 들 어 분배 id, 정렬 등 이 있 고 일련의 관련 사건 점 을 촉발 할 것 이다.
seal(callback) {
    //      seal
    this.hooks.seal.call();
    //   
    ......
    this.hooks.afterOptimizeDependencies.call(this.modules);
    
    this.hooks.beforeChunks.call();
    //   chunk
    for (const preparedEntrypoint of this._preparedEntrypoints) {
        const module = preparedEntrypoint.module;
        const name = preparedEntrypoint.name;
        //     Module chunk,  chunk        。
        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.assignDepth(module);
    }
    this.processDependenciesBlocksForChunkGroups(this.chunkGroups.slice());
    this.sortModules(this.modules);
    this.hooks.afterChunks.call(this.chunks);
    
    this.hooks.optimize.call();
    
    ......
    this.hooks.afterOptimizeModules.call(this.modules);
    
    ......
    this.hooks.afterOptimizeChunks.call(this.chunks, this.chunkGroups);
    
    this.hooks.optimizeTree.callAsync(this.chunks, this.modules, err => {
        ......
        this.hooks.beforeChunkAssets.call();
        this.createChunkAssets();  //      Assets
        this.hooks.additionalAssets.callAsync(...)
    });
    }

각 chunk 의 생 성 은 포함 할 modules 를 찾 는 것 입 니 다. chunk 의 생 성 알고리즘 을 대충 설명 합 니 다.
1. webpack 은 entry 에 대응 하 는 module 을 먼저 새로운 chunk 로 생 성 합 니 다.
2. module 의 의존 목록 을 옮 겨 다 니 며 의존 하 는 module 도 chunk 에 추가
3. module 에 의존 하 는 모듈 이 동적 으로 도 입 된 모듈 이 라면 이 module 에 따라 새로운 chunk 를 만 들 고 의존 을 계속 할 것 입 니 다.
4. 위의 과정 을 반복 하여 모든 chunks 를 얻 을 때 까지
chunk 속성 도
beforeChunkAssets && additionalChunkAssets(Compilation)
이 두 사건 점 을 촉발 하 는 중간 에 Compilation.createCHunkAssets assets,
createChunkAssets() {
    ......
    //   chunk
    for (let i = 0; i < this.chunks.length; i++) {
        const chunk = this.chunks[i];
        chunk.files = [];
        let source;
        let file;
        let filenameTemplate;
        try {
            //     Template
            const template = chunk.hasRuntime()
                ? this.mainTemplate
                : this.chunkTemplate;
            const manifest = template.getRenderManifest({
                chunk,
                hash: this.hash,
                fullHash: this.fullHash,
                outputOptions,
                moduleTemplates: this.moduleTemplates,
                dependencyTemplates: this.dependencyTemplates
            }); // [{ render(), filenameTemplate, pathOptions, identifier, hash }]
            for (const fileManifest of manifest) {
                .....
                }
                .....
                //   assets  
                this.assets[file] = source;
                chunk.files.push(file);
                this.hooks.chunkAsset.call(chunk, file);
                alreadyWrittenFiles.set(file, {
                    hash: usedHash,
                    source,
                    chunk
                });
            }
        } catch (err) {
            ......
        }
    }
    }

createChunkAssets 는 파일 이름과 해당 하 는 파일 내용 을 생 성하 여 넣 습 니 다 Compilation.assets . 여기 에는 네 개의 Template 하위 클래스 가 있 습 니 다. 각각 MainTemplate.js , ChunkTemplate.js ,ModuleTemplate.js , HotUpdateChunkTemplate.js
  • MainTemplate. js: entry 에서 설정 한 입구 chunk 의 렌 더 링 템 플 릿
  • 에 대응 합 니 다.
  • ChunkTemplate: 동적 으로 도 입 된 비 입구 chunk 의 렌 더 링 템 플 릿
  • ModuleTemplate. js: chunk 의 module 렌 더 링 템 플 릿
  • HotUpdateChunkTemplate. js: 열 교체 모듈 에 대한 처리.
  • 모듈 패키지http://taobaofed.org/blog/201...) 모듈 은 패 키 징 할 때 구축 할 때 와 마찬가지 로 각 모듈 류 를 호출 하 는 방법 입 니 다. 패 키 징 은 module. source () 를 호출 하여 각 작업 을 진행 합 니 다. 예 를 들 어 require () 의 교체 등 입 니 다.
    MainTemplate.prototype.requireFn = "__webpack_require__";
    MainTemplate.prototype.render = function(hash, chunk, moduleTemplate, dependencyTemplates) {
        var buf = [];
        //    module    moduleId,      。
        buf.push("function " + this.requireFn + "(moduleId) {");
        buf.push(this.indent(this.applyPluginsWaterfall("require", "", chunk, hash)));
        buf.push("}");
        buf.push("");
        ... //       
    };

    마지막 으로 Compilation. assets 대상 보기
    done(Compiler)
    마지막 으로 웹 팩 은 Compiler 의 emitAssets () 를 호출 하여 output 의 설정 항목 에 따라 파일 을 해당 하 는 path 에 출력 하여 웹 팩 의 전체 포장 과정 을 끝 냅 니 다. 결 과 를 처리 하려 면 emit 트리거 후 사용자 정의 플러그 인 을 확장 해 야 합 니 다.
    총결산
    웹 팩 의 내부 핵심 은 compilation copilemodulechunk 등 대상 이나 인 스 턴 스 입 니 다. 이 글 을 쓰 는 것 도 자신의 생각 을 정리 하 는 데 도움 이 되 고 배 움 의 바다 가 끝 이 없습니다 ~ ~
    인용 하 다.
    웹 팩 돌리 기 (1): 웹 팩 의 기본 구조 와 구축 프로 세 스 돌리 기 웹 팩 (2): 웹 팩 의 핵심 대상 웹 팩 의 프로 세 스 편

    좋은 웹페이지 즐겨찾기