webpack 的基本资源解析

Published on
2088 Words
Authors

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

  1. 先安装依赖
npm i @babel/core babel-loader -D

npm i @babel/preset-env -D
  1. 添加 babel 的配置文件 .babelrc

babel 有两个比较重要的概念,即 presets 和 plugins,可以理解为一个 plugin 对应一个功能,一个 presets 对应一系列 plugins 的集合。

{
  "presets": [
    "@babel/preset-env" // es6 相关的 env
  ],
  "plugins": [
    // "@babel/proposal-class-properties"
  ]
}
  1. 修改 webpack 的配置文件
const path = require('path')
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
      },
    ],
  },
}

React JSX

  1. 安装 React 相关的依赖
npm i @babel/preset-react -D
  1. 增加 React 的 babel presets 配置,
{
  "presets": [
    "@babel/preset-env", // es6 相关的 env
    "@babel/preset-react"
  ],
  "plugins": [
    // "@babel/proposal-class-properties"
  ]
}
  1. 修改 webpack 的配置文件,增加对 JSX 文件的解析
const path = require('path')
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader',
      },
    ],
  },
}

TypeScript

如果你用到了 typescript,那么在项目中后缀为 .ts 或 .tsx 的文件需要使用 ts-loader 处理。

  1. 安装依赖

不仅需要安装 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 文件。

  1. 添加 ts 的配置文件 tsconfig.json
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react", // 解决`无法使用 JSX,除非提供了 "--jsx" 标志问题`问题
    "allowJs": true
  },
  "include": ["src"]
}
  1. 更新 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

  1. 安装依赖
npm i css-loader style-loader -D
npm i less less-loader -D
  1. 更新 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]'
+      }
     }
    ]
  },
};