webpack 的基本资源解析
- Published on
- — 2088 Words
- Authors
- Name
- Richard Shih
- @realshiddong
webpack 原生只支持 JS 和 JSON 两种文件类型,而对于其他类型的文件,诸如 jsx/tsx 等语法糖、css (包括 less, sass, stylus 等)、图片、字体等文件,webpack 并不知道它们是什么。 而且即使支持 JS,在用户的浏览器中能够支持的 ES 特性各不相同,也需要转换代码使其更加具有兼容性。
我们使用 loaders 处理其它文件类型,把它们转换成有效的模块,可以将文件从不同的语言,比如 TypeScript 转换为 JavaScript;ES6+ 语法特性降级;或者将内联图像转换为 data URL 等。
测试环境
基于从零开始一个 webpack 新项目的基础上,逐渐添加针对不同资源解析的配置,从而建立对每种资源处理所需要的完整配置过程有个清晰的认识。
资源解析
webpack 中解析不同类型的资源主要用到 loader,它的配置层级是在 module.rules 数组中,为处理一类或几类资源可以为其添加一条规则 Rule。
每一条 Rule 规则的常用属性如下,其中 + 表示 webpack 5 更新的部分。
test: 匹配资源文件的正则表达式
- type: 设置资源模块类型 (支持 javascript* | json | webassembly/experimental)
+ type: 设置资源模块类型 (新增 asset* 资源模块类型)
use/loader: 设置加载器,类型可以是 String | Object | Array
+ generator: 设置资源生成信息
+ - filename: 设置资源文件输出名称和保存的路径(只适用于 type 属性为 asset 和 asset/resource 的场景)
include: 手动添加必须处理的文件(文件夹)
exclude: 手动屏蔽不需要处理的文件(文件夹)
ES6+
我们在前端项目中使用的一般是 ES6+ 语法,而在浏览器中不一定能够支持这些语法特性,比如 Class, Promise 等,可以通过 caniuse 查看语法特性的兼容性。
webpack 原生支持 JS 的解析,但对于 ES6+ 的语法特性需要降级,比如要输出为 ES5 的代码。则需要依赖 babel-loader。
babel-loader 是依赖 babel 的,所以需要安装 babel 与 babel-loader,并且需要为 babel 添加配置文件,其配置文件是 .babelrc
。
- 先安装依赖
npm i @babel/core babel-loader -D
npm i @babel/preset-env -D
- 添加 babel 的配置文件 .babelrc
babel 有两个比较重要的概念,即 presets 和 plugins,可以理解为一个 plugin 对应一个功能,一个 presets 对应一系列 plugins 的集合。
{
"presets": [
"@babel/preset-env" // es6 相关的 env
],
"plugins": [
// "@babel/proposal-class-properties"
]
}
- 修改 webpack 的配置文件
const path = require('path')
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
},
],
},
}
React JSX
- 安装 React 相关的依赖
npm i @babel/preset-react -D
- 增加 React 的 babel presets 配置,
{
"presets": [
"@babel/preset-env", // es6 相关的 env
"@babel/preset-react"
],
"plugins": [
// "@babel/proposal-class-properties"
]
}
- 修改 webpack 的配置文件,增加对 JSX 文件的解析
const path = require('path')
module.exports = {
// ...
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
},
],
},
}
TypeScript
如果你用到了 typescript,那么在项目中后缀为 .ts 或 .tsx 的文件需要使用 ts-loader 处理。
- 安装依赖
不仅需要安装 typescript 和 ts-loader,还需要安装 react 和 react-dom 的类型文件。
npm i typescript ts-loader -D
npm i @types/react @types/react-dom -D
ts-loader
中安装了 ES6+ 的 preset 与 react 的 preset,所以在项目中可以不再需要单独添加.babelrc
文件。
- 添加 ts 的配置文件 tsconfig.json
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react", // 解决`无法使用 JSX,除非提供了 "--jsx" 标志问题`问题
"allowJs": true
},
"include": ["src"]
}
- 更新 webpack 的配置文件
const path = require('path')
module.exports = {
// ...
module: {
rules: [
// ...
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
},
],
},
}
如果使用了 TypeScrit,还需要为图片、字体等资源模块添加描述文件。
// index.d.ts
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
CSS (less/sass/stylus)
解析 CSS 文件需要使用 css-loader,它会加载 .css 文件,并转换成 commonjs 对象,插入到 JS 中。 使用 style-loader 将样式通过 <style>
标签插入到 head 中。
其中,要注意 loader 是链式调用的,执行顺序是从右到左。
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/,
use: [
// loader 是链式调用的,执行顺序是从右到左 (从下到上)
'style-loader',
'css-loader',
],
},
],
},
}
实际开发中,我们会用到 less 或 sass 等 CSS 预处理器,其解析同样需要用到对应的 loader。按照 loader 的执行顺序,其 loader 应该放在 css-loader 之后,
- less-loader
- sass-loader
- stylus- loader
例如,less-loader 用于将 less 文件转换成 css
- 安装依赖
npm i css-loader style-loader -D
npm i less less-loader -D
- 更新 webpack 配置文件
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.less$/,
use: [
'style-loader', // 将 JS 字符串生成为 style 节点
'css-loader', // 将 CSS 转化成 CommonJS 模块
'less-loader', // 将 less 编译成 CSS
],
},
],
},
}
图片与字体等资源文件
webpack 5 对图片、字体等资源的解析方式发生了变化,可以查看 Asset Modules.
如果是使用 webpack 4,则可以参考下面的解析方式,虽然现在 webpack 5 中也支持该解析方式,不过在将来会下线掉这种方式。具体的说明可以查看 To v5 from v4
"If you have rules defined for loading assets using raw-loader, url-loader, or file-loader, please use Asset Modules instead as they're going to be deprecated in near future."
使用 loaders
处理资源文件主要用到下面这 3 个 loaders,
- raw-loader 导入资源的源代码,如将 txt 文件导入为字符串
- file-loader 将文件输出为一个单独的文件,并导出可访问的 URL
- url-loader 将文件作为 data URI 内联到 bundle 中
通过 file-loader 可以处理文件,只需要匹配图片资源的后缀即可。
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: ['file-loader'],
},
],
},
}
file-loader 也可以处理字体,
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ['file-loader'],
},
]
}
另外,使用 url-loader 也可以处理图片和字体文件,并且可以设置较小的资源自动 base64 转换。
url-loader 内部也是基于 file-loader 的。
module: {
rules: [
{
test: /\.(png|svg|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240, // 10k,单位:字节
},
},
],
},
]
}
使用 asset 字段配置
资源模块是 webpack 5 新增的一种概念,具体表现是对图片、字体等资源的解析方式发生了变化,可以不再使用 loader 处理这些资源模块,取而代之的是使用一个 type 字段配置即可。
webpack 5 实现了 4 种新的模块类型,通过 Rule 的 type 属性设置:
- asset/resource: 输出一个单独的文件并导出 URL —— 取代 file-loader
- asset/inline: 导出一个资源的 data URI —— 取代 url-loader
- asset/source: 导出资源的源代码 —— 取代 raw-loader
- asset: 在导出资源的 data URI 和输出一个单独的文件之间自动选择 —— 取代 url-loader,之前是在 url-loader 中通过配置资源体积限制实现
1. 自动处理文件
比如处理图片时,配置如下,
module.exports = {
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
},
],
},
}
当图片较小的时候,我们更希望导出一个资源的 data URI 直接嵌入在代码中,而不是输出一个单独的文件。 此时我们希望 webpack 能够根据图片大小自动适配,这种情况下就可以使用 type:"asset"
配置了。
如果设置成 asset,则会根据图片的大小在 asset/inline 和 asset/resource 中自动选择。其默认的临界大小是 8kb
,即
- 大于 8kb 导出单独文件
- 小于等于 8kb 则导出 dataURI
当然,我们也可以通过 Rule 的 parser.dataUrlCondition.maxSize 进行设置,其单位是 Byte 字节。
2. 修改资源模块的输出路径与文件名
在 output.assetModuleFilename
中可以设置统一的输出文件名,但也可以在 Rule 的配置中通过 generator.filename
自定义,其优先级更高。 定义方式与 output.filename
类似,可以通过 [name] 原文件名、[hash] 哈希字符串、[ext] 扩展名等变量控制文件名。
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, './src/index.js'),
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
+ assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.png/,
type: 'asset/resource'
},
{
test: /\.html/,
type: 'asset/resource',
+ generator: {
+ filename: 'static/[name]-[hash][ext][query]'
+ }
}
]
},
};