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 有两个属性:
test属性,识别出哪些文件会被转换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)
通过选择 development, production 或 none 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production。
module.exports = {
mode: 'production'
}
参考链接
模块(module)
webpack 中 模块 的概念应用到项目中的任何文件。
webpack 能以各种方式表达模块的依赖关系。例如:
- ES2015 的
import语句 - CommonJS 的
require() - AMD 的
define和require() - 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,其中包含了 react、react-dom、./src/index.jsx
和一个 app.jsx 的 non-initial chunk,因为 app.jsx 是 动态导入 的。
资源模块
资源模块 (assets module) 是一种模块类型,它允许 加载 资源文件(字体、图片等)无需配置额外的 loader。其实就是 内置了 url-loader 、file-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]'
}
}
]
},
// ...
}
提升开发体验
本章内容主要介绍了 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- 完整的模块代码参考链接
模块热替换
模块热替换(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-loader、css-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 代码。比如
less、sass、stylus。那么可能会使用到以下 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 检测有错误,会输出到控制台。
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"],
},
],
},
};
压缩 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 兼容处理
下载包
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
- 在
webpack.config.js给optimization添加 如下配置
module.exports = {
...
optimization: {
sideEffects: true, // 告知 webpack 去 package.json 找 sideEffects 标记,这就需要 咱们在 package.json 添加 sideEffects: true
usedExports: true,
minimize: true
}
}
- 在
package.json中添加sideEffects
{
"sideEffects": true
}