likes
comments
collection
share

webpack5 的使用(四):加载资源文件

作者站长头像
站长
· 阅读数 10

前言

目前项目已经可以加载了 css 文件了,这篇文章将会介绍如何加载资源文件(如:图片)。

旧版本与 webpack5+ 加载资源的区别

在 webpack5 之前,可能需要使用 raw-loader、file-loader、url-loader 来加载资源。

  1. raw-loader:将文件作为字符串导入
  2. file-loader:处理文件的路径并输出文件到输出目录
  3. url-loader:有条件将文件转化为 base64 URL,如果文件大于 limit 值,通常交给 file-loader 处理。

在 webpack5+,以上方法已经过时了,webpack5 使用了“资源模块”来代替以上 loader。 官方是这样解释“资源模块”的。

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。

而“资源模块”类型有四种。

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

asset/resource

asset/resource 起到两个作用,一个是用来解析文件的 URL,另外一个是将目标文件输出到打包目录。

我们先在 src 目录里创建一个 assets 目录,里面新建一个 img 目录,再把一张测试图片复制进来。

webpack5 的使用(四):加载资源文件

在 webpack.config.js 进行配置。

module: {
    rules: [
      ...
      {
        test: /\.(jpe?g|png|svg|gif)/i,
        type: 'asset/resource'
      }
    ]
}

在 index.js 引入图片,如果不引入的话,webpack 是不会输出这张图片,因为 webpack 认为这张图片没有被用过。

import '../assets/img/simao.jpg'

(2022.5.28,上面这种引入虽然会打包出图片,但是打包后的 js 上不会带有相关的图片内容,更正一下这里引入图片的示例,这里这样展示会好一点)

首先在 index.html 写上一个 img 标签

index.html

<img class="img" src="">

然后在 index.js 引入图片,并且插入到 img 标签里

index.js

import img from '../assets/img/simao.jpg'
document.querySelector('.img').setAttribute('src', img)

运行 npm run build。

webpack5 的使用(四):加载资源文件

图片打包到了 dist 目录了,main.js 也引入了图片,并且图片名默认是以哈希字符串命名(命名规则:[hash][ext][query])。

不过图片是打包到 dist 根目录上,我们再配置一下,让图片放到 dist/img 目录下

webpack.config.js

output: {
    ...
    assetModuleFilename: 'img/[hash][ext][query]'
}

我们打包后就发现图片放到指定目录下了。

webpack5 的使用(四):加载资源文件

这种方法看起来不错,但是资源文件不单止是图片,可能还会是字体、下载文件,这些文件的类型应该也是 asset/resource,难道我们都统一放到 img 目录下吗?

显然不合理,我们可以改用第二种方案,局部去指定目录。

module.exports = {
    ...
    output: {
        ...
        // assetModuleFilename: 'img/[hash][ext][query]' // 全局指定资源文件输出位置
    }
    ...
    module: {
        rules: [
            ...
            {
                test: /\.(jpe?g|png|svg|gif)/i,
                type: 'asset/resource',
                generator: {
                  filename: 'img/[hash][ext][query]' // 局部指定输出位置
                }
            },
        ]
    }
}

asset/inline

浏览器请求一个带有图片的网页,会发出 http 请求下载相应的图片,有多少张图片,就有多少个 http 请求,这样是很耗费图片服务器资源以及损耗速度的,想象一下需要加载的图片可能就几kb,却要发出一个 http 请求。

因此将小图片转化为 base64 字符串是一个不错的选择(雪碧图的技术也是为了解决上述问题而出现,有兴趣的可以深究一下)。asset/inline 就是起到这个作用。

记得把上面写的 asset/resource 规则注释掉,防止冲突。

webpack.config.js

module: {
     rules: [
          ...
          // {
          //   test: /\.(jpe?g|png|svg|gif)/i,
          //   type: 'asset/resource',
          //   generator: {
          //     filename: 'img/[hash][ext][query]' // 局部指定输出位置
          //   }
          // },
          {
            test: /\.(jpe?g|png|svg|gif)/i,
            type: 'asset/inline',
          },
     ]
}

index.js

import img from '../assets/img/simao.jpg'

document.body.style.background = `url(${img})`

build 一下。

webpack5 的使用(四):加载资源文件

可以发现,图片没有打包出来了,如果你在这个时候打开 main.js,会发现里面有一串很长的字符串,那个字符串就是 base64 字符串。

在浏览器打开 dist/index.html,或运行 npm run dev,网页的背景图将会预期展现出来。

webpack5 的使用(四):加载资源文件

注意了,asset/inline 会将所有符合规则的资源都变为 base64 字符串,也即是比较大的图片也会转化为 base64,base64 占用的空间可能会比原图片占用的空间还要大(可以动手试试),这种情况直接输出图片会更加好。

那么该怎么做判断,让小图片去转化为 base64,让大图片去直接输出到打包目录呢?

在 webpack5 里,asset 资源类型可以解决这个问题。

asset

asset 资源类型可以根据指定的图片大小来判断是否需要将图片转化为 base64,如果图片大于或等于限制,则使用 asset/resource 处理,如果图片小于限制 asset/inline 处理。asset 相当于一个判断工具。

项目里的图片(simao.jpg)大小为10kb,现在,我将一张 3kb 图片(名为:webpack.svg)放进 src/asset/img 里做测试。

同样地,注释之前设置的规则,然后新增新的规则。

webpack.config.js

module: {
     rules: [
          ...
          // {
          //   test: /\.(jpe?g|png|svg|gif)/i,
          //   type: 'asset/resource',
          //   generator: {
          //     filename: 'img/[hash][ext][query]' // 局部指定输出位置
          //   }
          // },
          // {
          //  test: /\.(jpe?g|png|svg|gif)/i,
          //  type: 'asset/inline',
          //},
          {
            test: /\.(jpe?g|png|svg|gif)/i,
            type: 'asset',
            generator: {
              filename: 'img/[hash][ext][query]' // 局部指定输出位置
            },
            parser: {
              dataUrlCondition: {
                maxSize: 8 * 1024 // 限制于 8kb
              }
            }
          }
     ]
}

index.js 引入 webpack.svg,并设置 body 的背景图为 webpack.svg。

index.js

import img from '../assets/img/simao.jpg'
import img2 from '../assets/img/webpack.svg'

// 更换为 img2 背景图
document.body.style.background = `url(${img2})`

npm run build,发现 dist/img 有图片文件(原 simao.jpg),simao.jpg 并没有并转化为 base64。

webpack5 的使用(四):加载资源文件

我们再在浏览器浏览 index.html 页面,发现 webpack.svg 被转化为 base64 并写入到 body 的背景图里。

webpack5 的使用(四):加载资源文件

由于 simao.jpg 大于 8kb,asset 选择用 asset/resource 处理它。 由于 webpack.svg 小于 8kb,asset 选择用 asset/inline 处理它。

asset/source

这个在实际开发中用得比较少,所以就简单说一下。

asset/source 的作用可以理解为“把目标文件的内容输出到 js 变量中”。

写一个处理 txt 文件的规则,资源类型为 asset/source。

webpack.config.js

module: {
     rules: [
          ...
          {
            test: /\.txt/,
            type: 'asset/source'
          }
     ]
}

在 src/assets 创建一个 txt 目录,里面新建一个 test.txt,test.txt 内容如下:

Hello World

index.js

import txt from '../assets/txt/test.txt'

console.log(txt)

npm run dev,可以发现 “Hello World” 打印出来了

webpack5 的使用(四):加载资源文件

补充:webpack 别名设置

好像每次 import 一个资源都要写上相对路径,如果文件换目录,那不是每个 import 都要改一遍,别担心,webpack 有一个别名设置,可以设定路径别名。

设置 @ 为 src 的别名。

webpack.config.js

module.exports = {
    resolve: {
        alias: {
          '@': path.resolve(__dirname, '../src'),
          // 下面可以继续新增别名
        }
    }
}

index.js 里这样导入

import '@/css/index.css'
import '@/scss/index.scss'
import img from '@/assets/img/simao.jpg'
import img2 from '@/assets/img/webpack.svg'
import txt from '@/assets/txt/test.txt'

完整代码

目录

webpack5 的使用(四):加载资源文件

webpack.config.js

const path = require('path')

const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

console.log('环境变量:', process.env.NODE_ENV)

module.exports = {
  // entry: path.resolve(__dirname, '../src/js/index.js'),
  entry: {
    main: path.resolve(__dirname, '../src/js/index.js'),
    header: path.resolve(__dirname, '../src/js/header.js'),
    footer: path.resolve(__dirname, '../src/js/footer.js'),
  },
  output: {
    // filename: 'main.js',
    filename: 'js/[name].[fullhash].js',
    path: path.resolve(__dirname, '../dist'),
    // assetModuleFilename: 'img/[hash][ext][query]' // 全局指定资源文件输出位置和文件名
  },
  // devServer: {
  //   port: 3000,
  //   hot: true,
  //   contentBase: '../dist'
  // },
  plugins: [
    // new HtmlWebpackPlugin({
    //   title: '首页'
    // }),
    // 配置多个 HtmlWebpackPlugin,有多少个页面就配置多少个
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/index.html'),
      filename: 'index.html',
      chunks: ['main'] // 与入口文件对应的模块名(entry 配置),这里可以理解为引入 main.js
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/header.html'),
      filename: 'header.html',
      chunks: ['header']
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../src/html/footer.html'),
      filename: 'footer.html',
      chunks: ['footer']
    }),
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[fullhash].css'
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [
          //'style-loader', 'css-loader'
          MiniCssExtractPlugin.loader, 'css-loader'
        ]
      },
      {
        test: /\.s[ac]ss$/i,
        use: [
          MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'
        ]
      },
      // {
      //   test: /\.(jpe?g|png|svg|gif)/i,
      //   type: 'asset/resource',
      //   generator: {
      //     filename: 'img/[hash][ext][query]' // 局部指定输出位置
      //   }
      // },
      // {
      //   test: /\.(jpe?g|png|svg|gif)/i,
      //   type: 'asset/inline',
      // },
      {
        test: /\.(jpe?g|png|svg|gif)/i,
        type: 'asset',
        generator: {
          filename: 'img/[hash][ext][query]' // 局部指定输出位置
        },
        parser: {
          dataUrlCondition: {
            maxSize: 8 * 1024 // 限制于 8kb
          }
        }
      },
      {
        test: /\.txt/,
        type: 'asset/source'
      }
    ]
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, '../src'),
      // 下面可以继续新增别名
    }
  }
}

index.js

import '@/css/index.css'
import '@/scss/index.scss'
import img from '@/assets/img/simao.jpg'
import img2 from '@/assets/img/webpack.svg'
import txt from '@/assets/txt/test.txt'

document.body.style.background = `url(${img2})`

console.log('这是一个入口文件')
console.log('环境变量:', process.env.NODE_ENV)

console.log(txt)

系列文章

转载自:https://juejin.cn/post/6970333716040122381
评论
请登录