Webpack原理

Webpack 原理浅析

今天写一个项目的时候要用到 exceljs 这个包,但是看了文档之后发现exceljs只提供commonjs形式的导出,我用的是es module规范(因为是在浏览器上的项目)。一时陷入沉思,我该怎么引用这个包?

不管三七二十一,先 import 再说。结果发现,直接 import 居然也行???于是研究了一下为什么不同的模块化标准也能互相通用???结果证明是 webpack 搞的鬼。

Webpack 的作用

虽然 Webpack 功能众多,但是最重要的功能显然是把多个 js 文件打包到一个 js 文件当中。那么问题就来了,他是怎么把多个 js 文件打包成一个的呢???

一些猜测

不管是 commonjs 也好,还是 esmodule 也好,总的来说,一个模块就是执行一些代码,然后导出一个包含一些变量与方法的对象。我们的目的,就是把这些分散在多个文件中的代码以及对象,合并到一个文件当中。

首先来说导出的对象。由于不同的模块可能会有相同的变量或方法名称,显然我们不能直接把所有模块导出的对象直接合并到一个对象当中去。基本立即就能想到的方法,可以把模块名称作为 key,把模块导出的对象作为 value,形成一颗大的树,防止不同的模块之间名称发生冲突。

然后是各个模块中的代码,暂时没有想到什么特殊情况,个人认为直接把所有代码复制到目标文件中就行。

那么为什么不同的模块标准可以混用呢,这是怎么实现的呢?

所谓混用,无非就是两种情况,commonjs 导出的模块使用 es module 导入,es module 导出的模块使用 commonjs 导入(一般应该是前者,我们就拿前者来说)。要实现混用,那就把其中一个转化为另外一个,或者直接自己再定一个新的全都转换成这个新的标准就行。

Webpack 的做法

先来看看一个非常简单的例子。以下代码为了可读性有所删减修改。

// index.js
import { test } from "./another";

test();
// another.js
module.exports = {
test() {
console.log("test");
}
};
// 打包生成的bundle.js
(function(modules) {
// webpackBootstrap
// 已引入的模块的缓存
var installedModules = {};

// Webpack的require函数
// 核心思想是每个模块都被转换为一个函数
// Webpack为每个模块生成一个对象
// 使用函数对对象进行操作
function __webpack_require__(moduleId) {
// 检查缓存中是否已经有该模块
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 创建一个新模块并放到缓存中
var module = (installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
});
// 执行模块函数
modules[moduleId].call(
module.exports,
module,
module.exports,
__webpack_require__
);
// 标记该模块为已加载
module.l = true;
// 返回模块的导出结果
return module.exports;
}

// 加载entry模块,并返回导出结果
// (entry模块可能也会有导出)
return __webpack_require__("./src/index.js");
})(
// 下面是传给最外层函数的参数
// (最外层函数是一个IIFE)
{
"./src/another.js": function(module, exports) {
// 以下代码会在__webpack_require__中调用
// module是在__webpack_require__中创建的
// 不是commonjs中的module
module.exports = {
test() {
console.log("test");
}
};
},

"./src/index.js": function(
module,
__webpack_exports__,
__webpack_require__
) {
"use strict";
var _another__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
"./src/another.js"
);
Object(_another__WEBPACK_IMPORTED_MODULE_0__["test"])();
}
}
);

仔细看完上面的代码就会发现,Webpack为每个模块都创建一个函数,这样做的好处是各个模块之间不会相互影响,实现了模块作用域。这个创建的函数会把Webpack创建的模块对象作为参数传入,且参数名称为module,这样子如果在模块中使用commonjs导出模块,就会对module参数进行赋值,改变了Webpack创建的module对象。后面不管是require还是import,都是直接访问Webpack自己创建的module对象,实现了commonjs和es module的混用。

Author: LeoB_O
Link: https://leob-o.github.io/2020/02/18/Webpack原理/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.