webpack5의 핵심 소스 코드 분석

웹팩 시리즈 디렉토리

  1. webpack5의 핵심 구성
  2. webpack5의 모듈 원리
  3. webpack5의 Babel/ESlint/브라우저 호환성
  4. webpack5의 성능 최적화
  5. webpack5의 Loader 및 Plugin 구현
  6. webpack5의 핵심 소스 코드 분석

webpack 명령 시작의 원리

우리는 일반적으로 빌드 패키징 명령과 같은 npm 스크립트에서 webpack 명령을 구성하여 webpack을 시작합니다.

"scripts" {
  "build": "webpack --config ./config/webpack.common.js --env production"
}
复制代码

1. webpack/bin/webpack.js

webpack 명령을 사용하여 webpack-cli도구의 도움으로 시작합니다.webpack을 설치할 때 binwebpack의 package.json에 속성 줄이 있고 npm은 bin내부 속성 이름 webpack을 파일 이름으로 사용하고 이 파일의 새 내용을 사용합니다. bin디렉토리webpack.js 에 content.file로 복사 node_modules/.bin하여 아래에 설치

스니페이스트_2022-03-28_20-52-45.png

Snipaste_2022-03-28_20-59-24.png

따라서 webpack 명령을 실행할 때 실제로 webpack/bin/webpack.js이 파일을 실행합니다.

이 파일 을 살펴보겠습니다 webpack/bin/webpack.js. 주요 프로세스는 다음과 같습니다.

  1. 먼저 cli객체 정의
  2. cli내부 installed속성이 인지 판단할 때
  3. 구현하다runCli

Snipaste_2022-03-28_21-03-28.png

즉, 먼저 webpack-cli패키지가 설치되어 있는지 여부를 확인합니다. 그렇지 않으면 조건부 설치로 들어가고 그렇지 않으면 실행 runCli됩니다. 일반적으로 패키지 명령을 사용 하여 두 패키지를 다음 위치 에 설치 webpack및 설치합니다. webpack-cli동시에, 그래서 가장 중요한 것은 보는 것입니다runCli

1. runCli 실행

Snipaste_2022-03-28_21-14-38.png

runcli에서 가장 중요한 것은 cli의 속성 값을 경로, pkgPathwebpack-cli/package.json, 객체 로 연결 pkg하는 것이며 cli에 있는 속성 해당 속성 값은webpack-cli/package.jsonwebpack-cli/package.jsonbinwebpack-clibin/cli.js

Snipaste_2022-03-28_21-20-56.png

所以最后拼接的是webpack-cli/bin/cli.js这个路径,将调用这个文件

2. webpack-cli/bin/cli.js

1. 执行runCLI

这个文件其实主要执行的是runCLI,并且将命令后的参数一起传进去

Snipaste_2022-03-28_21-33-52.png

这里调用了runCLI的引用来自bootstrap这个文件,我们来看下这个文件

3. webpack-cli/lib/bootstrap.js

Snipaste_2022-03-28_21-36-15.png

1. 创建了WebpackCLI对象

2. 执行cli.run

而这个WebpackCLI来自webpack-cli.js

4.webpack-cli/lib/webpack-cli.js

run方法这里主要是执行了以下流程

1. 执行makeCommand

为了检查一些依赖包是否存在

Snipaste_2022-03-28_21-41-53.png

2. 执行makeOption

makeCommand方法里面执行makeOption方法,对我们传入的参数做了进一步处理

Snipaste_2022-03-28_21-46-20.png

3. 执行runWebpack

4. 执行createCompiler

而在runWebpack里面主要执行了createCompiler

Snipaste_2022-03-28_21-47-32.png

5. 执行webpack

而在createCompiler里面主要调用webpack这个方法,而这个webpack方法就是来自webpack包

Snipaste_2022-03-28_21-50-13.png

到这一步其实webpack已经打包完成了

webpack Compiler创建原理

在上述执行webpack函数创建了compiler,那这个是compiler是如何创建的呢,我们来看一下这个webpack方法。webpack来自webpack/lib/webpack.js

webpack/lib/webpack.js

Snipaste_2022-03-28_22-40-44.png

在wepack方法里面可以看到,不管是否有回调都会调用create返回compiler

1. 执行create

create方法中主要执行了createCompiler创建了compiler

Snipaste_2022-03-28_22-48-19.png

2. 执行createCompiler

Snipaste_2022-03-28_22-52-45.png

而在createCompiler主要做了

  1. new了一个Compiler对象
  2. plugin.apply注册了所有的插件
  3. 调用了environmentafterEnvironment环境hook
  4. 调用new WebpackOptionsApply().process将配置属性转为plugin注册
  5. 返回compiler

3. 执行compiler.run

webpack方法内首先会判断是否有callback回调,如果存在回调会执行compiler.run,如果不存在直接返回compiler,所以我们在外面在执行webpack方法获取compiler后,我们即可以传入一个回调方法,也可以调用run方法。

webpack/lib/WebpackOptionsApply.js

1. 插件注入plugin.apply()

webpack中的createCompiler里我们调用了new WebpackOptionsApply().process,我们来看看这里到底怎么实现将配置属性转为plugin注册

Snipaste_2022-03-28_23-07-26.png

其实在process方法中,我们将传入的属性转成webpackplugin注入到webpack生命周期内,如上图展示的部分属性做判断,存在就将内置的Plugin进行导入(所以plugin事实上贯穿webpack的整个构建流程),其实这个方法都是在做plugin.apply的调用注册,并将compiler对象传入进去,这些Plugin后续会通过tapable来实现钩子的监听, 并进行自己的处理逻辑

Compiler中run方法执行原理

webpack/lib/Compiler.js

在上述createCompiler中我们new了一个Compiler对象,这个构造方法主要做了什么呢,我们可以看下webpack/lib/Compiler.js这个文件

Snipaste_2022-03-28_23-16-08.png

new Compiler这个构造函数是会初始化各种各样的hooks,而之前说process里面的plugin里会注册这些hooks,这些hooks来自一个叫tapable的库来管理的,这是由webpack 官方自己来维护的一个库,对于tapable这个库的介绍使用可以看我另一篇webpack文章webpack5之Loader和Plugin的实现

现在我们来看看Compiler内的run方法,其实主要是执行之前plugin注册的hooks

Snipaste_2022-03-28_23-29-53.png

而在Compiler里面的run方法里,又定义了一个run方法,那我们看下这里做了什么

1. 执行run

  1. 首先执行了hooks.beforeRun,执行一些需要运行前操作的plugin
  2. 再执行了hooks.run,执行一些需要运行开始需要操作的plugin
  3. 执行compile方法,并传入了onCompiled编译完成的回调

2. 执行compile

当执行到this.compile就是开始准备编译了,我们来看看compile里面做了什么

Snipaste_2022-03-28_23-44-45.png

  1. 执行hooks.beforeCompile
  2. 执行hooks.compile
  3. 执行hooks.make
  4. 执行hooks.finishMake
  5. 执行hooks.afterCompile

其实hooks.make是最终的编译过程,而在hooks.compilehooks.make之间执行了const compilation = this.newCompilation(params);,并将compilation传入了hooks.make

这里的Compilation与Compiler有什么区别呢

Compiler

  • 在webpack构建的之初就会创建的一个对象, 并且在webpack的整个生命周期都会存在(before - run - beforeCompiler - compile - make - finishMake - afterCompiler - done)
  • 只要是做webpack的编译, 都会先创建一个Compiler
  • 如果修改webpack配置需要重新npm run build

Compilation

  • 存在于compile - make阶段
  • watch源代码,每次发生改变就需要重新编译模块,创建一个新的Compilation对象

Compilation对Module的处理

上述的hooks.make只是一个hook的调用,我们要去找注册在这个钩子上的回调,我们可以前往process内的new EntryOptionPlugin().apply(compiler) 这个entry插件

1. webpack/lib/EntryPlugin.js

Snipaste_2022-03-29_00-07-59.png

这个插件在apply里调用applyEntryOption,而里面又调用EntryPlugin插件

Snipaste_2022-03-29_00-11-47.png

EntryPlugin插件内可以看到注册了hooks.make

Snipaste_2022-03-29_00-12-39.png

而在注册回调中主要执行了compilation.addEntry,那我们来看看在compilation这个对象中主要做了什么

2. webpack/lib/Compilation.js

Snipaste_2022-03-29_00-24-23.png

在执行compilation.addEntry这里主要做了

  1. 执行_addEntryItem,用于添加入口的Item
  2. 执行addModuleTree
  3. addModuleTree中执行handleModuleCreation
  4. handleModuleCreation中执行factorizeModule,添加hooksfactorizeQueue队列中
  5. handleModuleCreation中执行addModule,添加module模块到addModuleQueue队列中
  6. addModule中执行buildModule,将需要构建的module模块添加到buildQueue队列中
  7. buildQueue队列中有一个processor属性,执行_buildModule
  8. _buildModule中执行module.needBuild判断模块是否需要构建
  9. 执行module.build
  10. 最后会在wepack/lib/NormalModule.js中执行build方法,开始构建模块

Snipaste_2022-03-29_00-44-51.png

module的build阶段

上面在处理module的最后在wepack/lib/NormalModule.js中执行build方法,开始构建模块,那现在我们来看看build做了哪些内容

wepack/lib/NormalModule.js

1. 执行doBuild

Snipaste_2022-03-29_11-21-45.png

2. 执行_doBuild

执行doBuild内的_doBuild方法

Snipaste_2022-03-29_11-18-33.png

3. 执行runLoaders

执行_doBuildrunLoaders,这个runLoaders来自独立的loader-runner库,我们之前配置的各种Loaders就是在这里处理的

4. 执行processResult

runLoaders执行结束后回执行processResult这个回调

Snipaste_2022-03-29_11-21-45.png

5. 执行parse

之后会调用parse解析AST

Snipaste_2022-03-29_11-33-12.png

而这个parse来自webpack/lib/javascript/JavascriptParser.js内的parse

Snipaste_2022-03-29_11-40-25.png

这个parse其实是用到了acorn这个库来解析javascript

Snipaste_2022-03-29_11-42-02.png

6. 执行handleBuildDone

解析完后会调用handleParseResult回调,里面执行handleBuildDone

Snipaste_2022-03-29_11-44-42.png

handleBuildDone里又执行了build里面传进来的回调

Snipaste_2022-03-29_11-44-42.png

Snipaste_2022-03-29_11-54-59.png

最终执行的webpack/lib/Compilation.js下的module.build传进来的回调

Snipaste_2022-03-29_11-55-52.png

7. _buildModule执行完成

_buildModule执行完成后,最终hooks.make执行完成,于是接下来会执行webpack/lib/Compiler.jscompilation.finishcompilation.seal方法

Snipaste_2022-03-29_14-18-06.png

seal这一步,就是开始将静态资源输出到构建目录了

输出静态资源到构建目录

1. 执行hooks.optimizeChunkModules

먼저 실행 hooks.optimizeChunkModules하고 그 전에 모듈 코드를 최적화하십시오.

Snipaste_2022-03-29_15-33-03.png

2. codeGeneration 실행

실행 codeGeneration, 코드 생성

Snipaste_2022-03-29_15-34-24.png

3. createChunkAssets 실행

실행 createChunkAssets, chunkAssets리소스 생성

Snipaste_2022-03-29_15-38-32.png

4. getRenderManifest 실행

실행 createChunkAssets에서 getRenderManifest모든 데이터를 매니페스트 개체에 넣습니다.

Snipaste_2022-03-29_15-38-59.png

5. 이미터 자산 실행

Execute emitAsset, 출력 자원, 이때 자원은 메모리에 저장되었습니다.

Snipaste_2022-03-29_15-41-50.png

6. onCompiled 실행

최종 완료 webpack/lib/Compiler후 콜백 실행compileonCompiled

Snipaste_2022-03-29_15-43-54.png

7. 방출 자산 실행

onCompiled콜백에서 실행 emitAssets,

Snipaste_2022-03-29_15-45-53.png

8. hooks.emit 실행

emitAssets마지막으로 hooks.emit내부의 빌드 디렉토리로 리소스 내보내기를 실행합니다.

Snipaste_2022-03-29_15-47-28.png

소스 코드의 도입이 아직 불완전할 수 있습니다. 소스 코드를 볼 때 vscode의 디버거 도구를 사용하여 중단점을 통해 코드 방향을 볼 수 있습니다. 위의 소개는 웹팩 실행 프로세스를 대략적으로 설명할 수 있을 것입니다. 자세한 내용은 디버거를 사용하여 더 많이 탐색할 수 있기를 바랍니다.

추천

출처juejin.im/post/7080447622300827655