前端工程化项目搭建四:其他资源加载

100 阅读3分钟

这是我参与「掘金日新计划 · 8 月更文挑战」的第3天

前言

之前的文章中,已经完成了html,css,js的基本配置,接下来针对图片等资源文件的加载添加其他的配置。

图片资源加载

小tips:使用“../src/images/xxx.png”方式引入图片的时候,在img标签中可以直接使用,但是采用变量赋值的方式,这种写法在webpack中被认为是一个字符串,而不是一个图片路径,解决办法是,使用import引入图片,或者在赋值的时候使用require。
首先,我们先创建一个images文件夹,里面有两张图片,我选择的一张大小是4KB,一张是240KB,使用如下方式引入:

import zm from "../images/zmqny.png";
import Insourcing from "../images/Insourcing-business@2x.png";

const app = document.querySelector("#app");
app.innerHTML = "Hello World!!";

const img1 = new Image();
img1.src = zm;
app.appendChild(img1);

const img2 = new Image();
img2.src = Insourcing;
app.appendChild(img2);

在执行编译命令时,出现如下报错:

image.png 在执行打包命令时,出现如下报错:

image.png 根据报错提示,我们需要一个loader来处理图片。

loader加载

webpack5之前加载资源文件一般会是用 file-loaderurl-loaderraw-loader等,但是在webpack5之后可以直接使用资源模块类型(asset module type)来替代上述 loader
下面简单介绍一下常用的file-loader和url-loader

file-loader

file-loader的作用是帮助我们处理import/require()方式引入的文件资源并放到我们输出的文件夹中,上述错误,我们使用了import和require()的方式引入了图片,此时,我们下载file-loader,在webpack.config.js中进行相关的配置:

// webpack.config.js
...
module: {
    rules: [
      ...
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        use: [
          {
            loader: "file-loader"
          }
        ]
      }
    ]
  }

此时编译后,页面上能够正常展示图片了,并且执行打包命令,在dist文件夹下,也能看到打包后的图片。
打包后的图片名称是一串随机数字(这里的字符串是使用MD4的散列函数处理生成的一个hash值),接下来,我们来修改一下这个名称。
我们可以使用PlaceHolders来修改打包后的图片命名格式:
一些常用的 PlaceHolders

  • ext:处理文件扩展名
  • name:处理文件名称
  • hash:使用MD4的散列函数处理生成的一个hash值(默认情况下,它是哈希的十六进制摘要)
  • contentHash:指定生成文件内容哈希值的哈希方法。(默认 md4)
  • path:相对于 webpack/config context 的资源路径。
  • hash:<length>:截图hash的长度,默认32个字符 配置方式
hash的简单介绍
  • [hash]:整个项目共用同一个 hash 值,只要项目里有文件更改,整个项目构建的 hash 值都会更改。
  • [chunkhash]:同一个模块共用一个 hash 值,就算将 JS 和 CSS 分离,其 hash 值也是相同的,修改一处,JS 和 CSS 的 hash 值都会变。
  • [contenthash]:单个文件单独的 hash 值,只要文件内容不一样,产生的 hash 值就不一样。

webpack.config.js的配置修改:

// webpack.config.js
...
module: {
    rules: [
      ...
      {
        test: /\.(png|jpg|jpeg|gif|svg)$/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "test_[name]_[hash:6].[ext]"
            }
          }
        ]
      }
    ]
  }

修改后的名称显示:

image.png

然后,我们还可以指定打包输出的文件夹:

...
{
    loader: "file-loader",
    options: {
        name: "images/test_[name]_[hash:6].[ext]"
        // outputPath: "images" 这样也可以
    }
}

image.png

url-loader

url-loader的用法和file-loader是一样的,其作用是将文件转换成base64的url。但是吧,使用这个loader有个弊端,那就是,如果图片资源特多,或者很大的时候,整个页面的加载速度会很慢,不过,也不用担心,这个loader提供了一个limit属性,设定超过某个阈值就不转换成base64的格式就好了。
使用配置如下:

{
    test: /\.(png|jpg|jpeg|gif|svg)$/,
    use: [
          {
            loader: "url-loader",
            options: {
              limit: 15 * 1024,
              // 解决:关闭url-loader的es6模块化,使用commonjs解析
              esModule: false,
              fallback: {
                loader: "file-loader",
                options: {
                  name: "images/[name]_[hash:6].[ext]"
                }
              }
            }
          }
        ],
    type: "javascript/auto"
}

这里有个坑,file-loader和url-loader不能采用上下结构来写,如果要同时使用,则需要使用上述写法,具体为什么,这个确实找不到答案,有大佬知道的话请解惑一下,万分感谢。
此时,可以发现,小于limit设置的值的图片会变成base64的url,而大于limit值的图片还是和原来一样。

image.png

webpack5的加载方式:asset module type

简单介绍一下:

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

asset/resource

asset/resource起到两个作用:

  • 一个是用来解析文件的 URL,
  • 另外一个是将目标文件输出到打包目录。

webpack.config.js配置修改如下:

...
{
    test: /\.(jpe?g|png|svg|gif)/i,
    type: "asset/resource",
    generator: {
        filename: "images/[name]_[hash:8].[ext]"
    }
}

asset/inline

如果想要实现url-loader的效果,可以使用这个。然,这里和loader一样,使用的时候要注释掉asset/resource,防止冲突。webpack.config.js的配置修改如下:

...
{
    test: /\.(jpe?g|png|svg|gif)/i,
    type: "asset/inline"
}

不过,这里出现了一个问题,没办法限制limit,这就意味着,很大的图也会被转换成base64,这体积,搞不好比原来的还要大得多。那么,这就需要使用下面这种方式了。

asset

asset资源类型可以根据指定的图片大小来判断是否需要将图片转化为base64,如果图片大于或等于限制,则使用asset/resource 处理,如果图片小于限制asset/inline 处理。webpack.config.js配置修改如下

...
{
    test: /\.(jpe?g|png|svg|gif)/i,
    type: "asset",
    generator: {
        filename: "images/[name]_[hash:8].[ext]" // 局部指定输出位置
    },
    parser: {
        dataUrlCondition: {
            maxSize: 15 * 1024 // 限制于 8kb
        }
    }
}

以上,就完成了webpack5的稳健资源加载的配置啦。

额外补充内容---别名设置

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

完整代码

package.js

{
  "name": "webpack-project",
  "version": "1.0.0",
  "description": "前端项目搭建练习",
  "main": "index.js",
  "scripts": {
    "test": "npm run test",
    "build:prod": "webpack --config build/webpack.config.prod.js",
    "dev": "webpack server --config build/webpack.config.dev.js --open"
  },
  "author": "leo",
  "license": "ISC",
  "dependencies": {
    "html-webpack-plugin": "^5.5.0",
    "webpack": "^5.73.0"
  },
  "devDependencies": {
    "clean-webpack-plugin": "^4.0.0",
    "css-loader": "^6.7.1",
    "css-minimizer-webpack-plugin": "^4.0.0",
    "file-loader": "^6.2.0",
    "mini-css-extract-plugin": "^2.6.1",
    "sass": "^1.54.4",
    "sass-loader": "^13.0.2",
    "style-loader": "^3.3.1",
    "url-loader": "^4.1.1",
    "webpack-cli": "^4.10.0",
    "webpack-dev-server": "^4.9.3",
    "webpack-merge": "^5.8.0"
  }
}

webpack.config.js

// webpack.config.js
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  entry: path.resolve(__dirname, "../src/main.js"), // path.resolve 方法用于生成绝对路径
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "../src"),
      // 下面可以继续新增别名
      images: path.resolve(__dirname, "../images")
    }
  },
  plugins: [
    new webpack.LoaderOptionsPlugin({
      // test: /\.xxx$/, // may apply this only for some modules
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, "../public/index.html"), // 指定要编译的文件,不指定的话会按照默认的模板创建一个html
      title: "webpack练习",
      filename: "index.html" // 编译完成输出的文件名
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].[fullhash].css"
    })
  ],
  module: {
    rules: [
      // {
      //   test: /\.css$/,
      //   // use: ["style-loader", "css-loader"]
      //   use: [
      //     { loader: "style-loader" },
      //     {
      //       loader: "css-loader",
      //       options: {
      //         modules: true // 启用/禁用 CSS 模块及其配置
      //       }
      //     }
      //   ]
      // }
      // loader,源码预处理器
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"]
      },
      {
        test: /\.s[ac]ss$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"]
      },
      // {
      //   test: /\.(png|jpg|jpeg|gif|svg)$/,
      //   use: [
      //     {
      //       loader: "file-loader",
      //       options: {
      //         name: "images/[name]_[hash:6].[ext]"
      //       }
      //     }
      //   ]
      // },
      // {
      //   test: /\.(png|jpg|jpeg|gif|svg)$/,
      //   use: [
      //     {
      //       loader: "url-loader",
      //       options: {
      //         limit: 15 * 1024,
      //         // 解决:关闭url-loader的es6模块化,使用commonjs解析
      //         esModule: false,
      //         fallback: {
      //           loader: "file-loader",
      //           options: {
      //             name: "images/[name]_[hash:6].[ext]"
      //           }
      //         }
      //       }
      //     }
      //   ],
      //   type: "javascript/auto"
      // }
      // asset/resource
      // {
      //   test: /\.(jpe?g|png|svg|gif)/i,
      //   type: "asset/resource",
      //   generator: {
      //     filename: "images/[name]_[hash:8].[ext]"
      //   }
      // }
      // asset/inline
      // {
      //   test: /\.(jpe?g|png|svg|gif)/i,
      //   type: "asset/inline"
      // }
      // asset
      {
        test: /\.(jpe?g|png|svg|gif)/i,
        type: "asset",
        generator: {
          filename: "images/[name]_[hash:8].[ext]" // 局部指定输出位置
        },
        parser: {
          dataUrlCondition: {
            maxSize: 15 * 1024 // 限制于 8kb
          }
        }
      }
    ]
  }
};

main.js

import "./index.js";
import "./css/index.css";
import "./scss/index.scss";
console.log(process.env.NODE_ENV);

index.js

import zm from "images/zmqny.png";
import Insourcing from "images/Insourcing-business@2x.png";

const app = document.querySelector("#app");
app.innerHTML = "Hello World!!";

const img1 = new Image();
img1.src = zm;
app.appendChild(img1);

const img2 = new Image();
img2.src = Insourcing;
app.appendChild(img2);

参考资料

webpack5的使用
webpack官网