webpack - 入门

120 阅读4分钟

webpack 简介

webpack是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

webpack 核心概念

入口(entry)

entry 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。

默认以 ./src/index.js 为入口。

 module.exports = {
   entry: './src/main.js'
 }

参考链接

entry 还有 更多的配置

输出(output)

output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。

主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。

 module.exports = {
   entry: './src/main.js',
   output: {
     path: path.resolve(__dirname, dist),
     filename: 'build.js'
   }
 }

参考链接

output 还有 更多的配置

loader

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

loader 有两个属性:

  1. test 属性,识别出哪些文件会被转换
  2. use 属性,定义转换时,应该使用哪些 loader

例如:css 模块

 module.exports = {
   module: {
     rules: [
       {
         test: /.css$/,
         use: ['style-loader', 'css-loader']
       }
     ]
   }
 }

参考链接

loader 还有 更多的自定义配置

插件(plugin)

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

想要使用一个插件,你只需要 require() 它,并把它添加到 plugins 数组中。

例如:使用 html-webpack-plugin 生成 导入 bundle 文件 的html 文件

 const HtmlWebpackPlugin = require('html-webpack-plugin');
 ​
 module.exports = {
   plugins: [
     new HtmlWebpackPlugin({
       template: './src/index.html'
     })
   ],
 }

webpack 提供了许多插件,插件列表

模式(mode)

通过选择 developmentproductionnone 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production

 module.exports = {
   mode: 'production'
 }

参考链接

mode 配置

模块(module)

webpack 中 模块 的概念应用到项目中的任何文件。

webpack 能以各种方式表达模块的依赖关系。例如:

  • ES2015 的 import 语句
  • CommonJS 的 require()
  • AMD 的 definerequire()
  • css/sass/less 中的 @import
  • stylesheet url(...) 或者 HTML <img src="...">

manifest

你可能会疑惑,webpack 是如何管理我们所编写的模块的关系的呢?

答案是: manifest 与 runtime。主要是指:浏览器运行过程中,webpack 用来连接模块化应用程序所需要的代码。

manifest 可以追踪所有模块到输出 bundle 之间的映射关系,主要是数据。而 runtime 就是处理模块加载和解析的代码。

chunk

chunk 有两种形式:

  • initial(初始化) 是入口起点的 main chunk。 此 chunk 包含为入口起点指定的所有模块及其依赖项。
  • non-initial 是可以延迟加载的 chunk。在 动态导入splitChunks 时,会出现这种 chunk。

例如:在 以 index.jsx 为 入口 的项目

 import React from 'react'
 import ReactDOM from 'react-dom'
 ​
 import('./app.jsx').then(App => {
   ReactDOM.render(<App />, root);
 })

会创建一个 名为 main 的 initial chunk,其中包含了 reactreact-dom./src/index.jsx

和一个 app.jsx 的 non-initial chunk,因为 app.jsx动态导入 的。

资源模块

资源模块 (assets module) 是一种模块类型,它允许 加载 资源文件(字体、图片等)无需配置额外的 loader。其实就是 内置了 url-loaderfile-loader 的功能,在webpack5之前,这些资源文件通常使用

  • raw-loader 将文件导入为字符串
  • url-loader 将文件作为 Data URL 内联到 bundle 中
  • file-loader 将文件输出到目录

通过 以下 4 中模块类型,来替换上面的 loader:

  • asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
  • asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
  • asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
  • asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

例如:加载 图片

 module.exports = {
   module: {
     rules: [
       {
         test: /.(png|jpe?g|gif|webp|avif)(?.*)?$/,
         type: 'asset',
         generator: {
           filename: 'img/[name].[hash:8][ext]'
         }
       }
     ]
   },
   // ...
 }
  • 资源模块:
  • raw-loader
  • url-loader
  • file-loader

提升开发体验

本章内容主要介绍了 DevServer、SourceMap、HMR

DevServer

webpack-dev-server 可用于快速开发应用程序,实现代码更新浏览器自动刷新。

安装:

 npm install --save-dev webpack-dev-server

修改配置文件:

 module.exports = {
   // ...
   devServer: {
     static: './dist'
   }
 }

参考链接:

DevServer 的 更多配置

SourceMap

当 webpack 打包源代码时,可能会很难追踪到 error(错误) 和 warning(警告) 在源代码中的原始位置。

为了更容易地追踪 error 和 warning,JavaScript 提供了 source maps 功能,可以将编译后的代码映射回原始源代码。

webpack 的 source map 有许多 可用选项,这里我们使用 inline-source-map 选项,如下:

 module.exports = {
   // ...
   devtool: 'inline-source-map'
 }

Webpack 中 devtool 所有配置项很多,总共有 26 种,总结下来也就是 ^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$ + none + eval

我们只需要了解,这几种就能了解所有的 26 种

  • inline - 模块代码末尾引用 通过 base64 编码后的代码
  • hidden - 模块代码末尾不引用
  • eval - 模块代码使用 eval 函数执行
  • nosources - 不生成源代码
  • cheap - 只能定位到行
  • module - 完整的模块代码

参考链接

Webpack Devtool 配置项

模块热替换

模块热替换(hot module replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新所有类型的模块,而无需完全刷新。

开启 HMR,修改 webpack-dev-server 的配置:

 module.exports = {
   // ...
   devServer: {
     hot: true, // 开启模块热更新
   }
 }

注意:

当我们开启 HMR 时,我们尝试 修改 css 内容,会有相应的效果,那是因为 style-loader 完善了用于模块热更新的代码。而我们尝试更新我们的 js,发现并没有模块热更新效果。那是因为 我们需要使用 module.hot.accept() 注册我们模块更新时的代码。

如以下内容

 if (module.hot) {
 ​
  let lastEditor = editor
 ​
  // 注册 `./editor.js` 模块更新,处理事件
 ​
  module.hot.accept('./editor.js', () => {
 ​
   console.log(`editor is updating`)
 ​
   const value = lastEditor.innerHTML
 ​
   document.body.removeChild(lastEditor)
 ​
   lastEditor = createEditor()
 ​
   lastEditor.innerHTML = value
 ​
   document.body.appendChild(lastEditor)
 ​
  })
 ​
 ​
  // 注册 `./editor.jpg` 模块更新,处理事件
 ​
  module.hot.accept('./editor.jpg', () => {
 ​
   img.src = backgroundImg
 ​
  })
 ​
 }

详细代码:hmr

处理资源(css、图片、js等)

本章主要介绍 webpack 处理 日常开发中 常用的资源,如 css、图片、js

处理 css

webpack 中 加载 css 代码,需要使用 style-loadercss-loader

但是 style-loader 不是处理 css 资源的唯一解。如果你想分离 css 代码为单独的文件,你可能会用到 MInCssExtractPlugin.loader

其中 css-loader 将 css 代码以模块形式导出。style-loader 将 css 模块代码以 style 标签形式加入到 html 中。

例如: 如下main.js 导入了 css 模块,import './main.css'

那么在 webpack.config.js 中,我们需要 在 module.rules 添加 css 模块对应的规则。

 npm install --save-dev style-loader css-loader

webpack.config.js

 module.exports = {
   // ...
   module: {
    rules: [
     {
         test: /.css$/,
         use: [
       'style-loader',
       'css-loader'
      ]
       },
    ]
  }
 }

查看 css-loader 更多配置

查看 style-loader 更多配置

项目中我们可能会用过 css预处理器,更好地编写 css 代码。比如 lesssassstylus。那么可能会使用到以下 loader:

处理图片

处理 图片、字体 资源,可以使用 webpack5 内置的 Assets Modules。 例如:我们在 main.js 中 引入 import mainImage from './main.png'

webpack.config.js

   module.exports = {
     // ...
     module: {
       rules: [
         {
           test: /.(png|jpe?g|gif)$/,
           type: 'asset',
         }
       ]
     }
   }

asset 类型,会根据文件大小选择 是 DataURL 还是 文件的方式输出。小于 8kb 的文件会输出为 DataURL;否则会被视为 文件

处理 js

webpack 可以处理 js,但是有时候我们需要 eslint、babel 处理我们的 js 文件。

eslint

安装:

 npm install --save-dev eslint eslint-webpack-plugin

配置 webpack

 const EslintWebpackPlugin = require('eslint-webpack-plugin')
 ​
 module.exports = {
   // ...
   
   plugins: [
     // 这里需要设置 .eslintrc.js 等
     new EslintWebpackPlugin({
       context: path.resolve(__dirname, './src')
     }),
     // ...
   ]
 }

当 执行 打包命令时,如果 eslint 检测有错误,会输出到控制台。

eslint 官网

eslint-webpack-plugin

babel-loader

转译 JavaScript 文件

安装:

 npm install -D babel-loader @babel/core @babel/preset-env webpack

配置

 module: {
   rules: [
     {
       test: /.js$/,
       exclude: /node_modules/,
       use: {
         loader: 'babel-loader',
         options: {
           presets: ['@babel/preset-env']
         }
       }
     }
   ]
 }

提取、压缩、兼容css

提取 css

这里我们使用 MiniCssExtractPlugin 会将 CSS 提取到单独的文件中。

首先安装 mini-css-extract-plugin

 npm install --save-dev mini-css-extract-plugin

使用 webpack.config.js

 const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 ​
 module.exports = {
   plugins: [new MiniCssExtractPlugin()],
   module: {
     rules: [
       {
         test: /.css$/i,
         use: [MiniCssExtractPlugin.loader, "css-loader"],
       },
     ],
   },
 };

mini-css-extract-plugin

压缩 css

使用 CssMinimizerWebpackPlugin 优化和压缩 CSS

安装 css-minimizer-webpack-plugin

 npm install css-minimizer-webpack-plugin --save-dev

使用 webpack.config.js

 const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
 ​
 module.exports = {
   plugins: [
     new MiniCssExtractPlugin(),
     new CssMinimizerPlugin()
   ],
   module: {
     rules: [
       {
         test: /.css$/i,
         use: [MiniCssExtractPlugin.loader, "css-loader"],
       },
     ],
   },
 };

css-minimizer-webpack-plugin

css 兼容处理

下载包

 npm install postcss-loader postcss postcss-preset-env --save-dev

配置 webpack.config.js

 module.exports = {
   module: {
     rules: [
       {
         test: /.css$/,
         use: [
           'style-loader',
           'css-loader',
           {
             loader: 'postcss-loader',
             options: {
               postcssOptions: {
                 plugins: [
                   'postcss-preset-env'
                 ]
               }
             }
           }
         ]
       }
     ]
   }
 }

package.json 中 添加 browserslist

 ​
   "browserslist": [
     "last 2 version",
     "> 1%",
     "not dead"
   ]

代码分离和缓存

动态导入

动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案import() 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure

例如:动态导入 lodash

 function getComponent() {
   return import('lodash').then(({ default: _ }) => {
     const element = document.createElement('div')
     element.innerHTML = _.join(['Hello', 'Webpack'], ',')
     return element
   })
 }

当我们执行 webpack 打包时,lodash 就会分离到 一个 bundle:

 ...
 [webpack-cli] Compilation finished
 asset vendors-node_modules_lodash_lodash_js.bundle.js 549 KiB [compared for emit] (id hint: vendors)
 asset index.bundle.js 13.5 KiB [compared for emit] (name: index)
 runtime modules 7.37 KiB 11 modules
 cacheable modules 530 KiB
   ./src/index.js 434 bytes [built] [code generated]
   ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
 webpack 5.4.0 compiled successfully in 268 ms

多入口的代码分离

import 内联注释

通过在 import 中添加注释,我们可以进行诸如给 chunk 命名或选择不同模式的操作。

webpackChunkName:chunk 的名称。例如 import(/* webpackChunkName: "about" */ './about.js')

webpackPrefetch: true:将来某些导航下可能需要的资源,prefetch chunk 会在父 chunk 加载结束后开始加载。

webpackPreload: true:当前导航可能需要的资源,会在父 chunk 加载时,并行开始加载。

不同之处:

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。

更多内联注释

Tree Shaking

Tree Shaking,通常用于移除 JavaScript 上下文中未引用的代码(dead-code)。

webpack 中 该功能特点:

  • 该功能会在 production 模式时,自动开启。
  • development 时,不会开启

如何在 development 时,开启 Tree Shaking

  1. webpack.config.jsoptimization 添加 如下配置
 module.exports = {
   ...
   optimization: {
     sideEffects: true, // 告知 webpack  package.json  sideEffects 标记,这就需要 咱们在 package.json 添加 sideEffects: true
     usedExports: true,
     minimize: true
   }
 }
  1. package.json 中添加 sideEffects
 {
   "sideEffects": true
 }

参考链接

Tree Shaking