tags: [组件]
categories: 前端工程化


一、webpack 常用 Loader

1、加载文件

2、编译模版

3、转换脚本语言

4、转换样式文件

5、检查代码

6、其它

二、webpack 常用 Plugin

1、修改行为

define-plugin

https://webpack.js.org/plugins/define-plugin/
定义环境变量

context-replacement-plugin

https://webpack.js.org/plugins/context-replacement-plugin/
修改 require 语句在寻找文件时的默认行为

ignore-plugin

https://webpack.js.org/plugins/ignore-plugin/
用于忽略部分文件

2、优化

commons-chunk-plugin(V4.0移除,SplitChunksPlugin取代)

https://webpack.js.org/plugins/commons-chunk-plugin/
提取公共代码,避免webpack生成重复引入的模块代码,引入模块代码只生成一遍。
注意该插件和DLL的功能不一样

提取公共代码

对于多页面应用而言,提取公共代码:

  • 减少网络传输流量,降低服务器成本

  • 虽然用户第一次打开网站的速度得不到优化,但之后访问其他页面的速度将大大提高

webpack 前端工程化 - 图1

extract-text-webpack-plugin(V4.0移除,mini-css-extract-plugin取代)

https://github.com/webpack-contrib/extract-text-webpack-plugin
提取 JS 中的 CSS 代码到单独的文件中

prepack-webpack-plugin

https://github.com/gajus/prepack-webpack-plugin
通过 Facebook 的 Prepack 优化输出的 JS 代码的性能:编译代码时提前将计算结果放到编译后的代码中,而不是在代码运行时才去求值

  • 通过 Babel 将 JS 源码解析成 AST,以更细粒度分析源码

  • 实现了一个 JS 解释器,用于执行源码。借助这个解释器,prepack 才能理解源码具体是如何执行的,并将执行过程中的结果返回到输出中

  • 不能识别 DOM API 和 部分 Node.js API

  • 代码在优化后性能可能更差

  • 代码在优化后,文件的尺寸可能大大增加

  • 现在用于线上环境还为时过早

uglifyjs-webpack-plugin

https://github.com/webpack-contrib/uglifyjs-webpack-plugin
通过 UglifyJS 压缩 ES6 代码;V4.0 实例不放到 Plugin 中了,放到 optimization.minimizer 中去了
参数:

  • sourceMap: 默认不生成

  • beautify:是否输出可读性较强的代码,即保留空格和制表符。默认true

  • comments: 是否保留代码中的注释。默认 true

  • compress.warning: 是否在 uglifyJS 删除没用的代码时输出警告信息。默认 true

  • drop_console: 是否删除代码中的所有 console 语句。默认 false

  • collapse_vars: 是否内嵌已定义但是只用到一次的变量。默认true。

  • reduce_vars: 是否提取出现了多次但是没有定义成变量去引用的静态值

webpack-parallel-uglify-plugin

https://github.com/gdborton/webpack-parallel-uglify-plugin
多线程执行 UglifyJS 代码压缩,提升构建的速度,在生产环境使用
使用时即不实用 UglifyJS ,直接用 ParalleUglifyPlugin,但是变成了并行执行

参数:

  • test: 用正则匹配哪些文件需要被压缩,默认为/.js$/

  • include: 用正则命中需要被压缩的文件,默认为[]

  • exclude: 用正则命中不需要被压缩的文件,默认为[]

  • cacheDir: 用于配置缓存存放的目录路径,缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回。默认不缓存

  • workerCount: 开启几个子进程去并发执行压缩。默认为当前运行的 CPU 核数 -1

  • sourceMap: 会导致压缩过程变慢

  • uglifyJS: 传递给 uglifyJS 的参数

  • uglifyES: 传递给 uglifyES 的参数

  1. const path = require('path')
  2. const DefinePlugin = require('webpack/lib/DefinePlugin')
  3. const ParalleUglifyPlugin = require('webpack-parallel-uglify-plugin')
  4. module.exports = {
  5. plugins: [
  6. // 使用 ParalleUglifyPlugin 并行压缩输出的 JS 代码
  7. new ParalleUglifyPlugin({
  8. // 传递给 UglifyJS 的参数
  9. uglifyJS: {
  10. output: {
  11. // 最紧凑的输出
  12. beautify: false,
  13. // 删除所有注释
  14. comments: false,
  15. },
  16. compress: {
  17. // 在UglifyJS 删除没有用到的代码时不输出警告
  18. warnings: false,
  19. // 删除所有的console语句,可以兼容IE浏览器
  20. drop_console: true,
  21. }
  22. }
  23. })
  24. ]
  25. }

imagemin-webpack-plugin

https://www.npmjs.com/package/imagemin-webpack-plugin
压缩图片文件

webpack-spritesmith

https://www.npmjs.com/package/webpack-spritesmith
制作雪碧图

ModuleConcatenationPlugin

https://webpack.js.org/plugins/module-concatenation-plugin/
开启 WebpackScopeHoisting 功能

dll-plugin

https://webpack.js.org/plugins/dll-plugin/
借助 DLL 的思想大幅度提升构建速度
DLL:动态链接库,在一个动态链接库中可以包含为其他模块调用的函数和数据

  • 将网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中。在一个动态链接库中可以包含多个模块

  • 当需要导入的模块存在于某个动态链接库中时,这个模块不能被再次打包,而是去动态链接库中获取

  • 页面依赖的所有动态链接库都需要被加载

包含大量复用模块的动态链接库只需被编译一次,在之后的构建过程中被动态链接库包含的模块将不会重新编译,而是直接使用动态链接库中的代码。只要不升级这些模块的版本,动态链接库就不用重新编译

  • .dll: 包含了大量模块的代码,这些模块被存放在一个数组里,用数组的索引号作为ID,并且通过 _dll_react 变量将自己暴露在全局中,即可以通过 window._dll_react 访问到其中包含的模块

  • manifest.json: 用于描述在动态链接库文件中包含哪些模块,以及每个模块的路径和 ID

DllReferencePlugin

  • 用于在主要的配置文件中引入 DllPlugin 插件打包好的动态链接库文件
  1. module.exports = {
  2. plugins: [
  3. // 这里的路径要和 webpack.dll.config.js 里面的对应。
  4. new webpack.DllReferencePlugin({
  5. // 需要和webpack.dll.config.js中配置的context保持一致,用来指导webpack匹配manifest中库的路径
  6. context: __dirname,
  7. // 用来引入webpack.dll.config.js中输出的manifest文件
  8. manifest: path.resolve(__dirname, './dist/dll/vendors-manifest.json')
  9. }),
  10. ]
  11. }

DllPlugin

  • 用于打包出一个个单独的动态链接库文件
  1. module.exports = {
  2. plugins: [
  3. new webpack.DllPlugin({
  4. // manifest文件的输出路径,[name]的部分由entry的名字替换
  5. path: path.join(__dirname, 'dist/dll/[name]-manifest.json'),
  6. // 这里必须匹配上面的output.library中的值,dll暴露的对象名
  7. name: library,
  8. // 解析包路径的上下文,这个要跟配置的dll user一致
  9. context: __dirname
  10. }),
  11. ]
  12. }

hot-module-replacement-plugin

https://webpack.js.org/plugins/hot-module-replacement-plugin/
开启模块热替换功能

其他插件

serviceworker-webpack-plugin

https://github.com/oliviertassinari/serviceworker-webpack-plugin
为网页应用增加离线缓存功能

stylelint-webpack-plugin

https://github.com/JaKXz/stylelint-webpack-plugin
将stylelint集成到项目中

i18n-webpack-plugin

https://github.com/webpack-contrib/i18n-webpack-plugin
使网页支持国际化

provide-plugin

https://webpack.js.org/plugins/provide-plugin/
从环境中提供的全局变量中加载模块,而不用导入对应的文件

web-webpack-plugin

https://github.com/gwuhaolin/web-webpack-plugin
可方便地为单页应用输出HTML,比 html-webpack-plugin 好用

三、优化

缩小文件搜索范围

Loader 配置

  • test

  • use

  • include

  • exclude

  • cacheDirectory: babel 支持缓存转换出的结果

  1. module.exports = {
  2. //...
  3. rules: [
  4. {
  5. test: /\.(jsx|js)$/,
  6. use: 'babel-loader?cacheDirectory',
  7. exclude: path.resolve(__dirname, 'node_modules')
  8. },
  9. ]
  10. }

resolve.modules 配置

配置Webpack去哪些目录下找第三方模块,默认是[‘node_modules’], 通过配置绝对路径减少搜索步骤

  1. module.exports = {
  2. resolve: {
  3. modules: [path.resolve(__dirname, 'node_modules')]
  4. }
  5. }

resolve.mainFields 配置

用于配置第三方模块使用哪个入口文件;可用于不同的运行环境下使用不同的代码,例如在浏览器中通过原生的fetch 或者 XMLHttpRequest 实现,在 Node.js 中通过 http 模块实现

resolve.mainFields 的默认值和当前的 target 配置有关系

  • 当 target 为 web 或者 webworker 时,值是[“browser”, “module”, “main”]

  • 当 target 为其他情况时,值是 [“module”, “main”]

由于大多数第三方模块都采用 main 字段去描述入口文件的位置,所以可以通过配置 mainFields 字段为 main 值,减少搜索步骤,但是注意只要有一个模块出错,都可能会造成构建出的代码无法正常运行

  1. module.exports = {
  2. resolve: {
  3. mainFields: ['main'],
  4. }
  5. }

resolve.alias 配置

  • 通过配置alias 使得不同环境引用不同的代码,如react-native-web 的使用

  • 由于webpack 默认会从第三方模块中的 package.json 中的指定入口文件递归解析和处理依赖文件,一般情况下该入口文件会定义成包含检查和警告的未被压缩的代码,但是直接使用单独、完整的min.js文件可以跳过耗时的递归解析操作

  1. module.exports = {
  2. resolve: {
  3. alias: {
  4. 'react': path.resolve(__dirname, './node_modules/react/dist/react.min.js')
  5. },
  6. }
  7. }

resolve.extensions 配置

  • 后缀尝试列表要尽可能小,不要将项目中不可能存在的情况写到后缀尝试列表中

  • 频率出现最高的文件后缀要优先放在最前面,以做到尽快退出寻找过程

  • 在源码中写导入语句时,要尽可能带上后缀,从而可以避免寻找过程

  1. module.exports = {
  2. resolve: {
  3. extensions: ['js'],
  4. }
  5. }

module.noParse 配置

让 webpack 忽略对部分没采用模块化的文件的递归处理,如jQuery| react.min.js
注意被忽略掉的文件里不应该包含 import | require | define 等模块化的语句,不然会导致在构建出的代码包含无法在浏览器环境下执行的模块化语句

  1. const path = require('path)
  2. module.exports = {
  3. module: {
  4. noParse: [/react\.min\.js$/],
  5. }
  6. }

HappyPack

将任务分解给多个子进程去并发执行,子进程处理完后再将结果发送给主进程。由于 JS 是单线程模型,所以要发挥多核 CPU 的功能,就只能通过多进程实现,而无法通过多线程实现。

接入 webpack

  • Loader 配置中,对所有文件的处理都交给 happypack/loader, 使用紧跟其后的 querystring ?id=babel 去告诉 happypack/loader 选择哪个 HappyPack 实例处理文件

  • Plugin 中新增了 HappyPack 实例,用于告诉 happypack/loader 如何处理 .js 和 .css 文件。选项中的 id 属性的值和上面 querystring 中的 ?id=babel 对应

    • id:

    • loaders: 和原本Loader 配置中的一样

    • threads: 代表开启几个子进程去处理这一类型的文件,默认是 3 个,必须是整数

    • verbose: 是否允许 HappyPack 输出日志,默认是 true

    • threadPool: 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多。

  1. npm i -D happypack
  1. const path = require('path');
  2. const HappyPack = require('happypack');
  3. module.exports = {
  4. module: {
  5. rules: [
  6. {
  7. test: /\.(jsx|js)$/,
  8. use: ['happypack/loader?id=babel'], // 将对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
  9. exclude: path.resolve(__dirname, 'node_modules')
  10. },
  11. {
  12. test: /\.(css|less)$/,
  13. use: ['happypack/loader?id=css'],
  14. exclude: path.resolve(__dirname, 'node_modules')
  15. },
  16. {
  17. test: /\.(png|svg|jpg|gif|webp|ico)$/,
  18. use: ['happypack/loader?id=file'],
  19. exclude: path.resolve(__dirname, 'node_modules')
  20. },
  21. {
  22. test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
  23. use: ['happypack/loader?id=url'],
  24. include: path.resolve(__dirname, 'assets/fonts')
  25. }
  26. ],
  27. plugins: [
  28. new HappyPack({
  29. id: 'babel',
  30. loaders: ['babel-loader?cacheDirectory'],
  31. threadPool: happyThreadPool,
  32. }),
  33. new HappyPack({
  34. id: 'css',
  35. loaders: ['style-loader', 'css-loader', 'less-loader'],
  36. threadPool: happyThreadPool,
  37. }),
  38. new HappyPack({
  39. id: 'file',
  40. loaders: ['file-loader'],
  41. threadPool: happyThreadPool,
  42. }),
  43. new HappyPack({
  44. id: 'url',
  45. loaders: ['url-loader'],
  46. threadPool: happyThreadPool, // 使用共享进程池中的子进程去处理任务
  47. }),
  48. ]
  49. }
  50. };

优化文件监听

  1. webpack --watch

webpack-dev-server 默认开启文件监听
在开启监听模式时,默认会监听配置的 Entry 文件和所有 Entry 递归依赖的文件,包括 node_modules 下的第三方模块。

  1. module.exports = {
  2. watchOptions: {
  3. ignored: /node_modules/,
  4. }
  5. }

采用这种方法优化后,消耗的内存和 CPU 将会大大减少

  • watchOptions.aggregateTimeout 越大性能越好,能降低重新构建的频率

  • watchOptions.poll 越小越好,降低检查的频率

同时注意这两个优化会导致监听模式反应和灵敏度降低

优化自动刷新

自动刷新:

  • 借助浏览器扩展去通过浏览器提供的接口刷新,如 WebStorm IDE 的 LiveEdit 功能
  • 向要开发的网页中注入代理客户端代码,通过代理客户端去刷新整个页面
  • 将要开发的网页装进一个 iframe 中,通过 刷新 iframe 去看到最新效果

devServer.inline : 用来控制是否向 Chunk 中注入代理客户端,默认注入。优化即关闭,从而只注入一个代理客户端,而非粗暴地为每个 chunk 都注入。

模块热替换

devServer 默认不开启

  1. webpack-dev-server --hot

热替换性能问题和自动刷新一样主要是因为需要监听文件的变化和注入客户端:忽略 node_modules

Update module 优化

  1. const NamedModulesPlugin = require('webpack/lib/NamedModulesPlugin')
  2. module.exports = {
  3. plugins: [
  4. // 显示出被替换模块的名称
  5. new NamedModulesPlugin(),
  6. ]
  7. }

压缩 CSS

css-loader 内置了 cssnano,只需开启 css-loader 的 minimize 选项

  1. use: ['css-loader?minimize']

CDN 加速

内容分发网络:通过将资源部署到世界各地,使用户在访问时按照仅仅原则从离其最近的服务器获取资源,来加快资源的获取速度。通过优化物理链路层传输过程中的光速有限、丢包等问题来提升网速。

  • 针对 HTML 文件:不开启缓存,将 HTML 放到自己的服务器上,而不是 CDN 服务器上,同时关闭自己服务器上的缓存。自己的服务器只提供 HTML 文件和数据接口

  • 针对静态的 JS 、CSS、图片等文件:开启 CDN 和缓存,上传到 CDN 服务上,同时为每个文件名带上由文件内容算出的 Hash 值。

  • 更改 HTML 中引入静态文件的资源引入地址为CDN 服务的 URL 地址

  • “//cdn.com/id/app_a6976b6d.css” 这样的 URL 省略了 http:或者 http:前缀,这样做在访问这些资源时会自动根据当前 HTML 的 URL 采用了什么模式去决定是采用 HTTP 还是 HTTPS 模式

  • 浏览器规则:在同一时刻针对同一个域名资源的并行请求有限制(大概4个左右),则会导致资源的加载被阻塞。可以将这些静态资源分散到不同的 CDN 服务上

  • 用多个域名后会带来一个新问题:增加域名解析时间,对于是否采用多域名分散资源,需要根据自己的需求去衡量得失

webpack 实现 CDN 的接入

  • 静态资源导入 URL 需要变成指向 CDN 服务的绝对路径的 URL,而不是相对于 HTML 文件的 URL

  • 静态资源的文件名需要带上由文件内容算出来的 Hash 值,以防止被缓存

  • 将不同类型的资源放到不同域名的 CDN 服务上,以防止资源的并行加载被阻塞

  • 在 output.publicPath 中设置 JS 的地址

  • 在 css-loader.publicPath 中设置被 CSS 导入的资源的地址

  • 在 WebPlugin.stylePublicPath 中设置 CSS 文件的地址

  1. const path = require('path');
  2. const HappyPack = require('happypack');
  3. const ExtractTextPlugin = require('extract-text-webpack-plugin');
  4. module.exports = {
  5. // ...
  6. output: {
  7. filename: '[name]_[chunkhash:8].js', // 为输出的 JS 加上 Hash 值
  8. path: path.resolve(__dirname, './dist'),
  9. publicPath: '//js.cdn.com/id/', // 指定存放 JS 文件的 CDN 目录 URL
  10. },
  11. module: {
  12. rules: [
  13. {
  14. test: /\.(jsx|js)$/,
  15. use: ['happypack/loader?id=babel'], // 将对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
  16. exclude: path.resolve(__dirname, 'node_modules')
  17. },
  18. {
  19. test: /\.(css|less)$/,
  20. use: ['happypack/loader?id=css'],
  21. exclude: path.resolve(__dirname, 'node_modules')
  22. },
  23. {
  24. test: /\.(png|svg|jpg|gif|webp|ico)$/,
  25. use: ['happypack/loader?id=file'],
  26. exclude: path.resolve(__dirname, 'node_modules')
  27. },
  28. {
  29. test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
  30. use: ['happypack/loader?id=url'],
  31. include: path.resolve(__dirname, 'assets/fonts')
  32. }
  33. ],
  34. plugins: [
  35. new HappyPack({
  36. id: 'babel',
  37. loaders: ['babel-loader?cacheDirectory']
  38. }),
  39. new HappyPack({
  40. id: 'css',
  41. loaders: ['style-loader', 'css-loader?minimizer', 'less-loader']
  42. }),
  43. new HappyPack({
  44. id: 'file',
  45. loaders: ['file-loader?name=[name]_[hash:8].[ext]'] // 为输出的图片名加上 Hash 值
  46. }),
  47. new HappyPack({
  48. id: 'url',
  49. loaders: ['url-loader']
  50. }),
  51. new ExtractTextPlugin({
  52. filename: '[name]_[contenthash:8].css', // 为输出的 CSS 文件名加上 Hash 值
  53. publicPath: '//img.cdn.com/id/', // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL
  54. })
  55. ]
  56. }
  57. };

Tree Shaking

剔除 JS 中用不上的死代码,依赖静态的 ES6 模块化语法,如果采用了 ES5 则 webpack 无法分析出可以剔除哪些代码

接入 Tree Shaking

  • 需要配置 Babel 以让其保留 ES6 模块化语句

.babelrc

  1. {
  2. "presets": ["env", {
  3. "modules": false // 关闭 Babel 的模块转换功能,保留原本的ES6模块化语法
  4. }],
  5. }

大部分 NPM 库都会提供两份代码,一份用 CommonJS 模块化语法,一份用 ES6 模块化语法,并在 package.json 中分别指出这两份代码的入口:

  1. {
  2. "main": "lib/index.js", // 指明采用 CommonJS 模块化的代码入口
  3. "jsnext:main": "es/index.js" // 指明采用 ES6 模块化的代码入口,社区约定;也有一些模块叫“module
  4. }

配置 webpack 的文件寻找规则

  1. module.exports = {
  2. resolve: {
  3. // 针对 NPM 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
  4. mainFields: ['jsnext:main', 'browser', 'main']
  5. }
  6. }

Scope Hoisting

分析模块之间的依赖关系,尽可能将被打散的模块合并到一个函数中,但前提是不能造成代码冗余。因此只有那些被引用了一次的模块才能被合并

  • 代码体积更小,因为函数申明语句会产生大量的代码

  • 代码在运行时因为创建的函数作用域变少了,所以内存开销也变小了

  • 对于非 ES6 模块化语法的代码,会自动降级处理,可以在启动时加上 —display-optimization-bailout 在控制台输出降级日志

  1. const ModuleConcatnationPlugin = require('webpack/lib/optimize/ModuleConcatnationPlugin');
  2. module.exports = {
  3. resolve: {
  4. mainFields: ['jsnext:main', 'browser', 'main'] // Scope Hoisting 依赖 ES6 模块化语法
  5. plugins: [
  6. new ModuleConcatnationPlugin(), // 开启 Scope Hoisting
  7. ]
  8. }

分割代码以按需加载

按需加载:用户当前需要用什么功能就只加载这个功能对应的代码,而不是加载所有功能对应的代码

为 SPA 做按需优化时:

  • 将整个网站划分成一个个小功能,再按照每个功能的相关程度分成几类
  • 将每类合并为一个 Chunk,按需加载对应的 Chunk
  • 不要按需加载首页所对应的功能,将其放到执行入口所在的 Chunk 中,以减少用户能感知的网页加载时间
  • 对于不依赖大量代码的功能点,例如依赖 Chart.js 去画图表、依赖 flv.js 去播放视频的功能点,可再对其进行按需加载
  • 被分割出去的代码的加载需要一定的时机去触发,即当用户操作到了或者即将操作到对应功能时再去加载对应的代码。被分割出去的代码的加载时机需要开发者根据网页的需求去衡量和确定

分割代码按需加载ReactRouter的案例

.babelrc

  1. {
  2. "presets": [
  3. "env",
  4. "react"
  5. ],
  6. "plugins": [
  7. "syntax-dynamic-import"
  8. ]
  9. }
  1. // main.js
  2. import React, {PureComponent, createElement} from 'react';
  3. import {render} from 'react-dom';
  4. import {HashRouter, Route, Link} from 'react-router-dom';
  5. import PageHome from './pages/home';
  6. /**
  7. * 异步加载组件
  8. * @param load 组件加载函数,load 函数会返回一个 Promise,在文件加载完成时 resolve
  9. * @returns {AsyncComponent} 返回一个高阶组件用于封装需要异步加载的组件
  10. */
  11. function getAsyncComponent(load) {
  12. return class AsyncComponent extends PureComponent {
  13. componentDidMount() {
  14. // 在高阶组件 DidMount 时才去执行网络加载步骤
  15. load().then(({default: component}) => {
  16. // 代码加载成功,获取到了代码导出的值,调用 setState 通知高阶组件重新渲染子组件
  17. this.setState({
  18. component,
  19. })
  20. });
  21. }
  22. render() {
  23. const {component} = this.state || {};
  24. // component 是 React.Component 类型,需要通过 React.createElement 生产一个组件实例
  25. return component ? createElement(component) : null;
  26. }
  27. }
  28. }
  29. // 根组件
  30. function App() {
  31. return (
  32. <HashRouter>
  33. <div>
  34. <nav>
  35. <Link to='/'>Home</Link> | <Link to='/about'>About</Link> | <Link to='/login'>Login</Link>
  36. </nav>
  37. <hr/>
  38. <Route exact path='/' component={PageHome}/>
  39. <Route path='/about' component={getAsyncComponent(
  40. // 异步加载函数,异步地加载 PageAbout 组件
  41. () => import(/* webpackChunkName: 'page-about' */'./pages/about')
  42. )}
  43. />
  44. <Route path='/login' component={getAsyncComponent(
  45. // 异步加载函数,异步地加载 PageAbout 组件
  46. () => import(/* webpackChunkName: 'page-login' */'./pages/login')
  47. )}
  48. />
  49. </div>
  50. </HashRouter>
  51. )
  52. }
  53. // 渲染根组件
  54. render(<App/>, window.document.getElementById('app'));
  1. // pages/home/index.js
  2. import React from 'react';
  3. export default function PageHome() {
  4. return <div>Page Home</div>
  5. }
  1. // pages/about/index.js
  2. import React from 'react';
  3. export default function PageAbout() {
  4. return <div>Page About</div>
  5. }
  1. // pages/login/index.js
  2. import React from 'react';
  3. export default function PageLogin() {
  4. return <div>Page Login</div>
  5. }

可视化分析工具

  1. webpack --profile --json

webpack analyse(官方)

  • Modules: 展示所有模块,每个模块对应一个文件,且包含所有模块之间的依赖关系图、模块路径、模块ID、模块所属的 Chunk、模块的大小

  • Chunks: 展示所有代码块,在一个代码块中包含多个模块,并且包含代码块的 ID、名称、大小,每个代码块包含的模块数量,以及代码块之间的依赖关系图

  • Assets: 展示所有输出的文件资源,包括JS、CSS、图片等,并且包括文件名称、大小及该文件来自哪个代码块

  • Warning: 展示构建过程中出现的所有警告信息

  • Errors: 展示构建过程中出现的所有错误信息

  • Hints:展示处理每个模块所耗费的时间

webpack-bundle-analyzer

  • 没有官方的那么多功能,但更直观

  • 打包出的文件中都包含什么

  • 每个文件的尺寸在总体中的占比,一眼看出哪些文件的尺寸大

  • 模块之间的包含关系

  • 每个文件的 Gzip 后的大小

推荐配置

最优构建速度

  1. const path = require('path');
  2. const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
  3. const {AutoWebPlugin} = require('web-webpack-plugin');
  4. const HappyPack = require('happypack');
  5. // 自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
  6. const autoWebPlugin = new AutoWebPlugin('./src/pages', {
  7. // HTML 模版文件所在的文件路径
  8. template: './template.html',
  9. // 提取出所有页面公共的代码
  10. commonsChunk: {
  11. // 提取出公共代码 Chunk 的名称
  12. name: 'common',
  13. },
  14. });
  15. module.exports = {
  16. // AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,
  17. // autoWebPlugin.entry 方法可以获取到生成入口配置
  18. entry: autoWebPlugin.entry({
  19. // 这里可以加入你额外需要的 Chunk 入口
  20. base: './src/base.js',
  21. }),
  22. output: {
  23. filename: '[name].js',
  24. },
  25. resolve: {
  26. // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
  27. // 其中 __dirname 表示当前工作目录,也就是项目根目录
  28. modules: [path.resolve(__dirname, 'node_modules')],
  29. // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
  30. mainFields: ['jsnext:main', 'main'],
  31. },
  32. module: {
  33. rules: [
  34. {
  35. // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
  36. test: /\.js$/,
  37. use: ['happypack/loader?id=babel'],
  38. // 只对项目根目录下的 src 目录中的文件采用 babel-loader
  39. include: path.resolve(__dirname, 'src'),
  40. },
  41. {
  42. test: /\.js$/,
  43. use: ['happypack/loader?id=ui-component'],
  44. include: path.resolve(__dirname, 'src'),
  45. },
  46. {
  47. // 增加对 CSS 文件的支持
  48. test: /\.css/,
  49. // 提取出 Chunk 中的 CSS 代码到单独的文件中
  50. use: ['happypack/loader?id=css'],
  51. },
  52. ]
  53. },
  54. plugins: [
  55. autoWebPlugin,
  56. // 使用HappyPack
  57. new HappyPack({
  58. id: 'babel',
  59. // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
  60. loaders: ['babel-loader?cacheDirectory'],
  61. }),
  62. new HappyPack({
  63. // UI 组件加载拆分
  64. id: 'ui-component',
  65. loaders: [{
  66. loader: 'ui-component-loader',
  67. options: {
  68. lib: 'antd',
  69. style: 'style/index.css',
  70. camel2: '-'
  71. }
  72. }],
  73. }),
  74. new HappyPack({
  75. id: 'css',
  76. // 如何处理 .css 文件,用法和 Loader 配置中一样
  77. loaders: ['style-loader', 'css-loader'],
  78. }),
  79. // 4-11提取公共代码
  80. new CommonsChunkPlugin({
  81. // 从 common 和 base 两个现成的 Chunk 中提取公共的部分
  82. chunks: ['common', 'base'],
  83. // 把公共的部分放到 base 中
  84. name: 'base'
  85. }),
  86. ],
  87. watchOptions: {
  88. // 4-5使用自动刷新:不监听的 node_modules 目录下的文件
  89. ignored: /node_modules/,
  90. }
  91. };

最佳质量

  1. const path = require('path');
  2. const DefinePlugin = require('webpack/lib/DefinePlugin');
  3. const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
  4. const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
  5. const ExtractTextPlugin = require('extract-text-webpack-plugin');
  6. const {AutoWebPlugin} = require('web-webpack-plugin');
  7. const HappyPack = require('happypack');
  8. const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
  9. // 自动寻找 pages 目录下的所有目录,把每一个目录看成一个单页应用
  10. const autoWebPlugin = new AutoWebPlugin('./src/pages', {
  11. // HTML 模版文件所在的文件路径
  12. template: './template.html',
  13. // 提取出所有页面公共的代码
  14. commonsChunk: {
  15. // 提取出公共代码 Chunk 的名称
  16. name: 'common',
  17. },
  18. // 指定存放 CSS 文件的 CDN 目录 URL
  19. stylePublicPath: '//css.cdn.com/id/',
  20. });
  21. module.exports = {
  22. // AutoWebPlugin 会找为寻找到的所有单页应用,生成对应的入口配置,
  23. // autoWebPlugin.entry 方法可以获取到生成入口配置
  24. entry: autoWebPlugin.entry({
  25. // 这里可以加入你额外需要的 Chunk 入口
  26. base: './src/base.js',
  27. }),
  28. output: {
  29. // 给输出的文件名称加上 hash 值
  30. filename: '[name]_[chunkhash:8].js',
  31. path: path.resolve(__dirname, './dist'),
  32. // 指定存放 JavaScript 文件的 CDN 目录 URL
  33. publicPath: '//js.cdn.com/id/',
  34. },
  35. resolve: {
  36. // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
  37. // 其中 __dirname 表示当前工作目录,也就是项目根目录
  38. modules: [path.resolve(__dirname, 'node_modules')],
  39. // 只采用 main 字段作为入口文件描述字段,以减少搜索步骤
  40. mainFields: ['jsnext:main', 'main'],
  41. },
  42. module: {
  43. rules: [
  44. {
  45. // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
  46. test: /\.js$/,
  47. use: ['happypack/loader?id=babel'],
  48. // 只对项目根目录下的 src 目录中的文件采用 babel-loader
  49. include: path.resolve(__dirname, 'src'),
  50. },
  51. {
  52. test: /\.js$/,
  53. use: ['happypack/loader?id=ui-component'],
  54. include: path.resolve(__dirname, 'src'),
  55. },
  56. {
  57. // 增加对 CSS 文件的支持
  58. test: /\.css/,
  59. // 提取出 Chunk 中的 CSS 代码到单独的文件中
  60. use: ExtractTextPlugin.extract({
  61. use: ['happypack/loader?id=css'],
  62. // 指定存放 CSS 中导入的资源(例如图片)的 CDN 目录 URL
  63. publicPath: '//img.cdn.com/id/'
  64. }),
  65. },
  66. ]
  67. },
  68. plugins: [
  69. autoWebPlugin,
  70. // 4-14开启ScopeHoisting
  71. new ModuleConcatenationPlugin(),
  72. // 4-3使用HappyPack
  73. new HappyPack({
  74. // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
  75. id: 'babel',
  76. // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
  77. loaders: ['babel-loader?cacheDirectory'],
  78. }),
  79. new HappyPack({
  80. // UI 组件加载拆分
  81. id: 'ui-component',
  82. loaders: [{
  83. loader: 'ui-component-loader',
  84. options: {
  85. lib: 'antd',
  86. style: 'style/index.css',
  87. camel2: '-'
  88. }
  89. }],
  90. }),
  91. new HappyPack({
  92. id: 'css',
  93. // 如何处理 .css 文件,用法和 Loader 配置中一样
  94. // 通过 minimize 选项压缩 CSS 代码
  95. loaders: ['css-loader?minimize'],
  96. }),
  97. new ExtractTextPlugin({
  98. // 给输出的 CSS 文件名称加上 hash 值
  99. filename: `[name]_[contenthash:8].css`,
  100. }),
  101. // 4-11提取公共代码
  102. new CommonsChunkPlugin({
  103. // 从 common 和 base 两个现成的 Chunk 中提取公共的部分
  104. chunks: ['common', 'base'],
  105. // 把公共的部分放到 base 中
  106. name: 'base'
  107. }),
  108. new DefinePlugin({
  109. // 定义 NODE_ENV 环境变量为 production 去除 react 代码中的开发时才需要的部分
  110. 'process.env': {
  111. NODE_ENV: JSON.stringify('production')
  112. }
  113. }),
  114. // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
  115. new ParallelUglifyPlugin({
  116. // 传递给 UglifyJS 的参数
  117. uglifyJS: {
  118. output: {
  119. // 最紧凑的输出
  120. beautify: false,
  121. // 删除所有的注释
  122. comments: false,
  123. },
  124. compress: {
  125. // 在UglifyJs删除没有用到的代码时不输出警告
  126. warnings: false,
  127. // 删除所有的 `console` 语句,可以兼容ie浏览器
  128. drop_console: true,
  129. // 内嵌定义了但是只用到一次的变量
  130. collapse_vars: true,
  131. // 提取出出现多次但是没有定义成变量去引用的静态值
  132. reduce_vars: true,
  133. }
  134. },
  135. }),
  136. ]
  137. };

参考资源

《深入浅出 webpack》https://github.com/gwuhaolin/dive-into-webpack
webpack-demos/webpack.config.js at master · dongyuanxin/webpack-demos
脑壳疼的Webpack-tapable - 掘金