webpack4 & webpack5 对于使用 require 引入图片处理的差异

65 阅读3分钟

背景:今天使用 require 加载图片的时候,发现 require 引入的资源可以直接使用了,不需要取 default 属性了,之前 webpack4 的时候还需要取一下 default 属性,后面看了下是 webpack内部做出了改变

webpack5

// index.tsx
const HeaderImg = require("../../img/headerImg.png");
console.log(HeaderImg); 
// 结果 -> /static/media/headerImg.[hash].png -> 这里直接输出对应的图片 路径

webpack4

// index.tsx
const HeaderImg = require("../../img/headerImg.png");
console.log(HeaderImg); 
// 结果 -> { default: "/static/media/headerImg/[hash].png", __esModule: true } -> 这里直接输出对应的图片 路径
  1. webpack4、webpack5 使用 require 加载图片的差异,在于 webpack4 使用图片需要加一个 default 属性,不然没办法访问到图片的 path
  2. webpack4 打包的结果有一个 __esModule 这个可以先不管,这个属性表示 index.tsx 这个模块是一个 esm 规范的 js文件
  3. 通过打包结果差异可以看到,当我们使用 webpack5 并且使用 require 加载图片 可以直接使用,不需要在加一个 default 属性进行访问

打包结果(webpack5)

image.png

打包结果(webpack4)

image.png

  1. 可以看到 webpack4 打包之后,针对图片的导出还是使用 在__webpack_exports__["default"] 上面进行导出。 当我们需要访问该图片的时候 需要加一个 default 属性 才能访问成功
  2. webpack5 直接在 module.exports 上进行导出

两个 webpack版本 内部发生了什么差异 (下面展示 webpack 大改加载和运行原理)

// webpack 打包核心的加载逻辑
function __webpack_require__(moduleId) {
  if (installedModules[moduleId]) {
    return installedModules[moduleId].exports;
  }
  var module = (installedModules[moduleId] = {
    i: moduleId,
    l: false,
    exports: {},
    hot: hotCreateModule(moduleId),
    parents:
      ((hotCurrentParentsTemp = hotCurrentParents),
      (hotCurrentParents = []),
      hotCurrentParentsTemp),
    children: [],
  });

  modules[moduleId].call(
    module.exports,
    module,
    module.exports,
    hotCreateRequire(moduleId)
  );

  module.l = true;

  return module.exports;
}

// webpack 加载 模块
// 核心的加载 通过 取到 模块的相对位置 做为 对象的 key
// 模块的内容 做为 函数的内容
// 最终通过 module.export 暴露出去
__webpack_require__({
  "./src/index1.js": function () {
    var img = __webpack_require__("./src/img/headerImg.png");
    console.log(img);
  },
  "./src/img/headerImg.png": (module, exports, __webpack_require__) => {
    module.exports =
      __webpack_require__.p + "static/media/headerImg.a862f4874931910aad4b.png";
  },
});

分析加载过程(webpack5)

  1. webpack 打包结果就是 实现webpack自己的 commonjs规范, 并且在过程中抹平 esm & commonjs 差异性。
  2. 核心的加载函数就是 __webpack_require__, 我们写的 importrequire 最终都被编译为 __webpack_require__, 最终形成一个自执行闭包函数。
  3. 加载逻辑: 本质上就是从打包好的所有的静态资源对象里面不停的进行加载(这里静态资源对象就是 -> { './src/index1.js': function(){}, ./src/index2.js': function(){} } 类似这种对象, 本质上我们所有的模块都是这种格式 [模块相对路径]: function(){ [模块业务逻辑] }, 最终多个模块组成一个大的对象,被不停的进行加载 )
  4. 每次使用 webpack_require 加载一个模块,都会判断有没有缓存
  5. 没有缓存,最终导出需要 被 加载模块的 module.export 引用
  6. 这里在 初始化进行了图片的加载,在打包好的 [bundle].js 里面 拿到了 图片相对项目的路径 ./src/img/headerImg.png 当作 moduleId 传入 __webpack_require__ 函数, 最终导出 module.exports 的引用,在这里其实就是 __webpack_require__.p + "static/media/headerImg.[hash].png";
  7. 相当于我们直接把 图片对应的 地址暴露出去了

分析加载过程(webpack4)

  1. 核心的加载函数与 webpack5 没有本质上差异,关键的差异在于图片模块的导出, webpack5 图片的导出直接是 module.export = [图片的path], webpack4 的导出是 module.export.default = [图片的path], 两者之间差了一个 default 属性。
  2. 所以当我们使用 webpack4 并且使用 require 加载图片,需要加一个 default 属性才能访问到真实的图片 path
  3. 注意,当我们使用 import 导入图片,webpack4 与 webpack5 使用上没有任何差异。但是内部打包导出逻辑也不太一样,
  4. 当使用 import 导入图片,webpack4 会在引用的地方自动取 导出对象的 default 属性 (见下图, 代码里面写的是 console.log(HeaderImg), webpack编译会自动取 default 属性) image.png
  5. 当使用 import 导入图片,webpack5 导出对象 依然是 module.export = [图片的path], 当图片代码被加载时直接 导出了 module.export 的值