[关闭]
@Dale-Lin 2022-08-09T21:17:54.000000Z 字数 9393 阅读 262

构建性能分析

webpack5


webpack 在大型项目中的性能表现较差,一方面是因为 JavaScript 语言的单线程架构决定了 webpack 的运行效率不会太高;另一方面是因为在大型项目中,webpack 通常需要借助许多 plugins、loader 来完成大量文件的读写、编译操作。

行之有效的性能优化方法包括:缓存、并发、优化文件处理步骤等,但优化前必须理解 webpack 打包的核心流程,借助性能分析工具找到耗时瓶颈。

核心流程

webpack 打包的核心功能有两个:

为了实现上述功能,webpack 底层的工作流程可以总结成几个阶段:

  1. 初始化:
    • 初始化参数:从配置文件、cli 参数中读取、和默认配置组合出构建参数;
    • 创建编译器对象:创建 Compiler 对象;
    • 初始化编译环境:注入内置插件、注册各种模块工厂、初始化 RuleSet、加载配置的插件等;
    • 开始编译:执行 Compiler 对象的 run 方法,创建 Compilation 对象;
    • 确定入口:根据配置中的 entry 找到所有入口,调用 compilation.addEntry 方法,将一个个文件加入到编译队列中;
  2. 构建阶段:
    • make:从 entry 开始,调用 loader 将模块转成 JS 代码;调用 parser 将 JS 转换成 AST,找到当前模块的依赖模块,再递归处理,直到 entry 依赖的所有模块都完成;
    • 完成编译:上一步完成后,得到了每个模块处理后的内容和他们的依赖关系图;
  3. 生成阶段:
    • seal:根据入口和模块之间的关系,组装 chunk;
    • optimization:对 chunk 做一系列优化操作,包括:tree-shaking、terser、scope-hoisting、compress、code split 等;
    • emitAssets:根据配置声明的输出目录输出文件。

这之中可能造成性能问题的地方:

性能分析

收集 webpack 打包过程的性能数据——webpack内置了 stats 接口,在编译过程中可以生成一个包含模块分析数据的 JSON 文件,这些数据可以用来分析应用的依赖图谱,以及优化编译速度,使用以下命令生成:

  1. npx webpack --profile --json=compilation-stats.json

参数说明:

StatsJSON结构说明

顶层结构:

  1. {
  2. 'version': '5.9.0', // Version of webpack used for the compilation
  3. 'hash': '11593e3b3ac85436984a', // Compilation specific hash
  4. 'time': 2469, // Compilation time in milliseconds
  5. 'publicPath': 'auto',
  6. 'outputPath': '/', // path to webpack output directory
  7. 'assetsByChunkName': {
  8. // Chunk name to emitted asset(s) mapping
  9. 'main': [
  10. 'web.js?h=11593e3b3ac85436984a'
  11. ],
  12. 'named-chunk': [
  13. 'named-chunk.web.js'
  14. ],
  15. 'other-chunk': [
  16. 'other-chunk.js',
  17. 'other-chunk.css'
  18. ]
  19. },
  20. 'assets': [
  21. // A list of asset objects
  22. ],
  23. 'chunks': [
  24. // A list of chunk objects
  25. ],
  26. 'modules': [
  27. // A list of module objects
  28. ],
  29. 'entryPoints': {
  30. // A list of entry objects
  31. },
  32. 'errors': [
  33. // A list of error objects
  34. ],
  35. 'errorsCount': 0, // number of errors
  36. 'warnings': [
  37. // A list of warning objects
  38. ],
  39. 'warningsCount': 0, // nummber of warnings
  40. }

Assets Objects

每个 assets object 表示一个输出的 output 文件,结构如下:

  1. {
  2. "chunkNames": [], // The chunks this asset contains
  3. "chunks": [10, 6], // The chunk IDs this asset contains
  4. "comparedForEmit": false, // Indicates whether or not the asset was compared with the same file on the output file system
  5. "emitted": true, // Indicates whether or not the asset made it to the `output` directory
  6. "name": "10.web.js", // The `output` filename
  7. "size": 1058, // The size of the file in bytes
  8. "info": {
  9. "immutable": true, // A flag telling whether the asset can be long term cached (contains a hash)
  10. "size": 1058, // The size in bytes, only becomes available after asset has been emitted
  11. "development": true, // A flag telling whether the asset is only used for development and doesn't count towards user-facing assets
  12. "hotModuleReplacement": true, // A flag telling whether the asset ships data for updating an existing application (HMR)
  13. "sourceFilename": "originalfile.js", // sourceFilename when asset was created from a source file (potentially transformed)
  14. "javascriptModule": true // true, when asset is javascript and an ESM
  15. }
  16. }

Chunk Objects

每个 chunk object 表示一组模块组成的 chunk(没有代码分离的情况下,一个 entry 构成的模块一般对应一个 chunk),结构如下:

  1. {
  2. "entry": true, // Indicates whether or not the chunk contains the webpack runtime
  3. "files": [
  4. // An array of filename strings that contain this chunk
  5. ],
  6. "filteredModules": 0, // See the description in the top-level structure above
  7. "id": 0, // The ID of this chunk
  8. "initial": true, // Indicates whether this chunk is loaded on initial page load or on demand(/guides/lazy-loading)
  9. "modules": [
  10. // A list of [module objects](#module-objects)
  11. "web.js?h=11593e3b3ac85436984a"
  12. ],
  13. "names": [
  14. // An list of chunk names contained within this chunk
  15. ],
  16. "origins": [
  17. // ...
  18. ],
  19. "parents": [], // Parent chunk IDs
  20. "rendered": true, // Indicates whether or not the chunk went through Code Generation
  21. "size": 188057 // Chunk size in bytes
  22. }

每个 origins 描述当前 chunk 是怎么生成的:

  1. {
  2. "loc": "", // Lines of code that generated this chunk
  3. "module": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
  4. "moduleId": 0, // The ID of the module
  5. "moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
  6. "moduleName": "./lib/index.web.js", // Relative path to the module
  7. "name": "main", // The name of the chunk
  8. "reasons": [
  9. // A list of the same `reasons` found in module objects
  10. ]
  11. }

Module Objects

编译应用的每个实际 module(存在于依赖图谱中)数据;结构如下:

  1. "assets": [
  2. // A list of [asset objects](#asset-objects)
  3. ],
  4. "built": true, // Indicates that the module went through [Loaders](/concepts/loaders), Parsing, and Code Generation
  5. "cacheable": true, // Whether or not this module is cacheable
  6. "chunks": [
  7. // IDs of chunks that contain this module
  8. ],
  9. "errors": 0, // Number of errors when resolving or processing the module
  10. "failed": false, // Whether or not compilation failed on this module
  11. "id": 0, // The ID of the module (analogous to [`module.id`](/api/module-variables/#moduleid-commonjs))
  12. "identifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // A unique ID used internally
  13. "name": "./lib/index.web.js", // Path to the actual file
  14. "optional": false, // All requests to this module are with `try... catch` blocks (irrelevant with ESM)
  15. "prefetched": false, // Indicates whether or not the module was [prefetched](/plugins/prefetch-plugin)
  16. "profile": {
  17. // Module specific compilation stats corresponding to the [`--profile` flag](/api/cli/#profiling) (in milliseconds)
  18. "building": 73, // Loading and parsing
  19. "dependencies": 242, // Building dependencies
  20. "factory": 11 // Resolving dependencies
  21. },
  22. "reasons": [
  23. // ...
  24. ],
  25. "size": 3593, // Estimated size of the module in bytes
  26. "source": "// Should not break it...\r\nif(typeof...", // The stringified raw source
  27. "warnings": 0 // Number of warnings when resolving or processing the module

reasons 数组描述为什么该模块存在于依赖图谱中:

  1. {
  2. "loc": "33:24-93", // Lines of code that caused the module to be included
  3. "module": "./lib/index.web.js", // Relative path to the module based on [context](/configuration/entry-context/#context)
  4. "moduleId": 0, // The ID of the module
  5. "moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
  6. "moduleName": "./lib/index.web.js", // A more readable name for the module (used for "pretty-printing")
  7. "type": "require.context", // The [type of request](/api/module-methods) used
  8. "userRequest": "../../cases" // Raw string used for the `import` or `require` request
  9. }

Entry Objects

  1. "main": {
  2. "name": "main",
  3. "chunks": [
  4. 179
  5. ],
  6. "assets": [
  7. {
  8. "name": "main.js",
  9. "size": 22
  10. }
  11. ],
  12. "filteredAssets": 0,
  13. "assetsSize": 22,
  14. "auxiliaryAssets": [],
  15. "filteredAuxiliaryAssets": 0,
  16. "auxiliaryAssetsSize": 0,
  17. "children": {},
  18. "childAssets": {},
  19. "isOverSizeLimit": false
  20. }

Errors and Warnings

errors and warnings 属性包含一组对象,每个对象包含一个栈追踪和其他属性:

  1. {
  2. "moduleIdentifier": "C:\\Repos\\webpack\\test\\cases\\context\\issue-5750\\index.js",
  3. "moduleName": "(webpack)/test/cases/context/issue-5750/index.js",
  4. "loc": "3:8-47",
  5. "message": "Critical dependency: Contexts can't use RegExps with the 'g' or 'y' flags.",
  6. "moduleId": 29595,
  7. "moduleTrace": [
  8. {
  9. "originIdentifier": "C:\\Repos\\webpack\\test\\cases|sync|/^\\.\\/[^/]+\\/[^/]+\\/index\\.js$/",
  10. "originName": "(webpack)/test/cases sync ^\\.\\/[^/]+\\/[^/]+\\/index\\.js$",
  11. "moduleIdentifier": "C:\\Repos\\webpack\\test\\cases\\context\\issue-5750\\index.js",
  12. "moduleName": "(webpack)/test/cases/context/issue-5750/index.js",
  13. "dependencies": [
  14. {
  15. "loc": "./context/issue-5750/index.js"
  16. }
  17. ],
  18. "originId": 32582,
  19. "moduleId": 29595
  20. },
  21. {
  22. "originIdentifier": "C:\\Repos\\webpack\\testCases.js",
  23. "originName": "(webpack)/testCases.js",
  24. "moduleIdentifier": "C:\\Repos\\webpack\\test\\cases|sync|/^\\.\\/[^/]+\\/[^/]+\\/index\\.js$/",
  25. "moduleName": "(webpack)/test/cases sync ^\\.\\/[^/]+\\/[^/]+\\/index\\.js$",
  26. "dependencies": [
  27. {
  28. "loc": "1:0-70"
  29. }
  30. ],
  31. "originId": 8198,
  32. "moduleId": 32582
  33. }
  34. ],
  35. "details": "at RequireContextDependency.getWarnings (C:\\Repos\\webpack\\lib\\dependencies\\ContextDependency.js:79:5)\n at Compilation.reportDependencyErrorsAndWarnings (C:\\Repos\\webpack\\lib\\Compilation.js:1727:24)\n at C:\\Repos\\webpack\\lib\\Compilation.js:1467:10\n at _next2 (<anonymous>:16:1)\n at eval (<anonymous>:42:1)\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:219:18\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:40:16\n at Hook.eval [as callAsync] (<anonymous>:38:1)\n at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (C:\\Repos\\tapable\\lib\\Hook.js:18:14)\n at Compilation.finish (C:\\Repos\\webpack\\lib\\Compilation.js:1462:28)\n at C:\\Repos\\webpack\\lib\\Compiler.js:909:18\n at processTicksAndRejections (internal/process/task_queues.js:75:11)\n",
  36. "stack": "ModuleDependencyWarning: Critical dependency: Contexts can't use RegExps with the 'g' or 'y' flags.\n at Compilation.reportDependencyErrorsAndWarnings (C:\\Repos\\webpack\\lib\\Compilation.js:1732:23)\n at C:\\Repos\\webpack\\lib\\Compilation.js:1467:10\n at _next2 (<anonymous>:16:1)\n at eval (<anonymous>:42:1)\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:219:18\n at C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2830:7\n at Object.each (C:\\Repos\\webpack\\node_modules\\neo-async\\async.js:2850:39)\n at C:\\Repos\\webpack\\lib\\FlagDependencyExportsPlugin.js:40:16\n at Hook.eval [as callAsync] (<anonymous>:38:1)\n at Hook.CALL_ASYNC_DELEGATE [as _callAsync] (C:\\Repos\\tapable\\lib\\Hook.js:18:14)\n at Compilation.finish (C:\\Repos\\webpack\\lib\\Compilation.js:1462:28)\n at C:\\Repos\\webpack\\lib\\Compiler.js:909:18\n at processTicksAndRejections (internal/process/task_queues.js:75:11)\n"
  37. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注