打包配置
下面展示一个极简单(单文件模块打包)的webpack配置:
const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 打包模式
mode: 'development',
// 不需要产出source-map
devtool: 'none',
// 打包入口
entry: './src/index.js',
output: {
// 产出文件名字
filename: 'built.js',
// 产出文件路径
path: path.resolve('dist')
},
plugins: [
new htmlWebpackPlugin({
// 模板文件路径
template: 'src/index.html',
// 输出的html文件名字
filename: 'index1.html'
})
]
}
以及src/index.js内容:
console.log('index.js内容')
module.exports = {
text: '入口文件导出内容'
}
当然最后打包的结果我也写好注释啦。(此处不要关注函数内容,先看整体的代码结构,后面针对CommonJS
模块和ESM
会有区分)
(function (modules) {
// 用于缓存已经加载过得模块
var installedModules = {};
// The require function
// webpack重写的 require 方法
function __webpack_require__ (moduleId) {
// 检查缓存中是否有 或者说当前模块是否已经加载过
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 如果没有加载过,则手动创建一个新的模块
var module = installedModules[moduleId] = {
i: moduleId, // 模块id 其实就是模块的路径组成的字符串
l: false, // 该模块是否已加载 true是 false 否
exports: {} // 模块导出的内容
};
// 加载模块的方法
// 初次加载相当于 modules[moduleId].call({}, {
// i: './src/index.js',
// l: false,
// exports: {}
// }, __webpack_require__)
// 如果模块中引入了其他模块,则递归的将 __webpack_require__ 传下去,因为加载模块是 __webpack_require__ 处理的
// 对于加载入口模块来说,其实就是调用了参数中 "./src/index.js" 对应的函数罢了
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 加载完模块后将该模块标记为 已加载
module.l = true;
// 返回模块导出的内容
return module.exports;
}
// 记录所有的模块
__webpack_require__.m = modules;
// 记录模块缓存
__webpack_require__.c = installedModules;
// 给模块的某属性定义getter
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// 给模块定义 __esModule
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
// 根据mode值不同对value进行不同的处理
__webpack_require__.t = function (value, mode) {
if (mode & 1) value = __webpack_require__(value);
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key]; }.bind(null, key));
return ns;
};
// 给模块定义getter方法并返回
__webpack_require__.n = function (module) {
var getter = module && module.__esModule ?
function getDefault () { return module['default']; } :
function getModuleExports () { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// 相当于 Object.prototype.hasOwnProperty 方法
__webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// 记录 webpack.config.js 配置的 output.publicPath
__webpack_require__.p = "";
// 加载入口模块并返回入口模块的导出内容
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})({
"./src/index.js": (function (module, exports) {
console.log('index.js内容')
module.exports = {
text: '入口文件导出内容'
}
})
});
打包出来的结果其实就是一个IIFE
,参数为一个json
对象,入口文件的路径作为key,value为一个函数体为文件内容的函数。主要通过IIFE
中定义的 __webpack_require__
来执行文件的加载操作。
__webpack_require__
定义了几个功能属性像m,c,d,r,f等等每个属性有不同的作用,在单文件打包中这些属性体现不出来作用,但是在真实项目下循环引用的模块就需要这些属性去处理了。
通过require
导入的模块,在webpack
处理下会被替换为__webpack_require__
。此处也是__webpack_require__
中执行call函数的是传入__webpack_require__
函数的原因所在。另外说的webpack
会改变项目的require
方法也是说的这里。
即:
({
"./src/index.js":
(function (module, exports, __webpack_require__) {
// 此处递归调用 __webpack_require__ 加载模块
const text = __webpack_require__(/*! ./login */ "./src/login.js");
console.log('index.js内容执行了')
console.log(text)
}),
"./src/login.js":
// 此处没有__webpack_require__是因为在login中没有再引入别的模块
(function (module, exports) {
module.exports = "测试文本";
})
});
Commionjs模块打包
下面来说说CommonJs模块打包。
由CommonJs规范加载CommonJs规范导出的模块
主要区别还是 webpack_require 函数。对于CommonJs导出的模块来说和上面代码没有任何区别。
// src/index.js
const text = require("./login");
console.log(text)
console.log("index文件")
// src/login.js
module.exports = "测试文本"
结果(此处只摘抄了__webpack_require__
函数,其他代码同上):
function __webpack_require__ (moduleId) {
// 检查缓存中是否有 或者说当前模块是否已经加载过
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 如果没有加载过,则手动创建一个新的模块
var module = installedModules[moduleId] = {
i: moduleId, // 模块id 其实就是模块的路径组成的字符串
l: false, // 该模块是否已加载 true是 false 否
exports: {} // 模块导出的内容
};
// 加载模块的方法
// 初次加载相当于 modules[moduleId].call({}, {
// i: './src/index.js',
// l: false,
// exports: {}
// }, __webpack_require__)
// 如果模块中引入了其他模块,则递归的将 __webpack_require__ 传下去,因为加载模块是 __webpack_require__ 处理的
// 对于加载入口模块来说,其实就是调用了参数中 "./src/index.js" 对应的函数罢了
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 加载完模块后将该模块标记为 已加载
module.l = true;
// 返回模块导出的内容
return module.exports;
}
webpack是默认支持CommonJs模块打包的,直接把代码中的require替换为 __webpack_require__
函数,而且对于module.exports是没有做任何操作的,即webpack默认支持的就是CommonJs规范。
加载ESM
为了展示加载ESM的效果,我改了index.js和login.js的内容:
// src/index.js
const miaomiao = require("./login");
console.log(miaomiao.default, miaomiao.test)
// src/login.js
export default "我家猫真调皮";
export const test = "真想揍它一顿";
相对应的,打包的结果肯定是有变化的。 CommonJs:
({
"./src/index.js":
(function (module, exports, __webpack_require__) {
// 此处递归调用 __webpack_require__ 加载模块
const text = __webpack_require__(/*! ./login */ "./src/login.js");
console.log('index.js内容执行了')
console.log(text)
}),
});
ESM:
({
"./src/index.js":
(function (module, exports, __webpack_require__) {
const miaomiao = __webpack_require__(/*! ./login */ "./src/login.js");
console.log(miaomiao.default, miaomiao.test)
}),
"./src/login.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "test", function () { return test; });
/* harmony default export */ __webpack_exports__["default"] = ("我家猫真调皮");
const test = "真想揍它一顿";
})
});
可以看到,首先对于参数来说,加载模块时直接将require替换为了 webpack__require ,其他的没有变化,但是在src/login.js中导出ESM时,是有特殊操作的:
- 先对module.exports进行了一个r操作,主要是给module.exports定义了ES模块的属性:__esModule 和toStringTag(只有ES6有)
- 给module.exports定义test属性的getter,因为我们在login.js中定义的变量就是test。
- 给module.exports定义default属性,因为在login.js中使用了 export default
对于ESM来说,首先会对参数module.exports
(形参写成了__webpack_exports__
,其实是module.exports
)进行了r操作的处理。上面说过了,r操作其实是给参数添加Symbol(如果是ES6的话)和__esModule
属性。
之后又对module.exports
定义default
属性,因为在进行了d操作,即给module.exports
定义属性,属性名就是在login.js中定义的test。
最后一步就是给module.exports定义default属性,因为在login.js中有使用default导出的对象。而default属性对应的值也是在login.js中定义的值。
export default "我家猫真调皮";
很乱吧。其实只要记住一点,这里的module.exports和我们在文件中使用的不一样,module其实是webpack定义的对象(在__webpack_require__中定义的),exports只是他的一个属性而已,exports的初始值就是一个空对象。这里可以去翻__webpack_require__函数的内容。
ESM模块打包
上面说了CommonJs引入ESM的情况,那接下来试试使用ESM的方式引入模块。
CommonJs加载ESm
我将两个实例文件的内容改成如下:
// src/index.js
import miaomiao from "./login";
console.log(miaomiao)
// src/login.js 注意这时候是通过CommonJs导出的
module.exports = "我家猫真调皮"
当然最终结果的区别还是在参数上:
({
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _login__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./login */ "./src/login.js");
var _login__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_login__WEBPACK_IMPORTED_MODULE_0__);
console.log(_login__WEBPACK_IMPORTED_MODULE_0___default.a)
}),
"./src/login.js":
(function (module, exports) {
module.exports = "我家猫真调皮"
})
});
可以发现,通过ESM的方式引入CommonJs时,通过CommonJs导出的内容不会进行特殊操作,如参数中的src/login.js
的内容,而且也会对import
像require
一样替换为__webpack_require__
,但是也会有一些差异:
- 引入时对
module.exports
进行r
操作处理(见参数中的src/index.js
),这是因为引入时判断是通过ESM的方式,所以进行了一个针对ES6的处理。 - 不同于CommonJS,在接收模块内容的时候变量名字是经过
webpack
处理了的,如_login__WEBPACK_IMPORTED_MODULE_0__
,并且用_login__WEBPACK_IMPORTED_MODULE_0___default.a
来表示导出的内容(a属性是通过__webpack_require__.n
添加上去的),当然使用时也是_login__WEBPACK_IMPORTED_MODULE_0___default.a
ESM加载ESM
为了演示这种情况,我双叒叕改了两个文件的内容:
// src/index.js
import miaomiao from "./login";
console.log(miaomiao)
// src/login.js
export default "我家猫真调皮";
export const test = "真想揍它一顿"
于此同时,打包结果也肯定会变啦。
还是只展示参数:
({
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _login__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./login */ "./src/login.js");
console.log(_login__WEBPACK_IMPORTED_MODULE_0__["default"])
}),
"./src/login.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "test", function () { return test; });
__webpack_exports__["default"] = ("我家猫真调皮");
const test = "真想揍它一顿"
})
});
你们有没有发现区别?r操作和d操作去找上面的代码吧,记得对于ESM来说会有两个操作将其标记为ESM即可(即r)我要去撸猫啦。 最后还是附上一份结果:
(function (modules) { // webpackBootstrap
// 用于缓存已经加载过得模块
var installedModules = {};
// webpack重写的 require 方法
function __webpack_require__ (moduleId) {
// 检查缓存中是否有 或者说当前模块是否已经加载过
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
// 如果没有加载过,则手动创建一个新的模块
var module = installedModules[moduleId] = {
i: moduleId, // 模块id 其实就是模块的路径组成的字符串
l: false, // 该模块是否已加载 true是 false 否
exports: {} // 模块导出的内容
};
// 加载模块的方法
// 初次加载相当于 modules[moduleId].call({}, {
// i: './src/index.js',
// l: false,
// exports: {}
// }, __webpack_require__)
// 如果模块中引入了其他模块,则递归的将 __webpack_require__ 传下去,因为加载模块是 __webpack_require__ 处理的
// 对于加载入口模块来说,其实就是调用了参数中 "./src/index.js" 对应的函数罢了
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// 加载完模块后将该模块标记为 已加载
module.l = true;
// 返回模块导出的内容
return module.exports;
}
// 记录所有的模块
__webpack_require__.m = modules;
// 记录模块缓存
__webpack_require__.c = installedModules;
// 给模块的某属性定义getter
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
// 将模块定义为一个ES Module
__webpack_require__.r = function (exports) {
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
// create a fake namespace object
// mode & 1: value is a module id, require it
// mode & 2: merge all properties of value into the ns
// mode & 4: return value when already ns object
// mode & 8|1: behave like require
// 根据mode值不同对value进行不同的处理
__webpack_require__.t = function (value, mode) {
if (mode & 1) value = __webpack_require__(value);
if (mode & 8) return value;
if ((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
var ns = Object.create(null);
__webpack_require__.r(ns);
Object.defineProperty(ns, 'default', { enumerable: true, value: value });
if (mode & 2 && typeof value != 'string') for (var key in value) __webpack_require__.d(ns, key, function (key) { return value[key]; }.bind(null, key));
return ns;
};
// 给模块定义getter方法并返回
__webpack_require__.n = function (module) {
var getter = module && module.__esModule ?
function getDefault () { return module['default']; } :
function getModuleExports () { return module; };
__webpack_require__.d(getter, 'a', getter);
return getter;
};
// 相当于 Object.prototype.hasOwnProperty 方法
__webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
// 记录 webpack.config.js 配置的 output.publicPath
__webpack_require__.p = "";
// // 加载入口模块并返回入口模块的导出内容
return __webpack_require__(__webpack_require__.s = "./src/index.js");
})
({
"./src/index.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _login__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./login */ "./src/login.js");
console.log(_login__WEBPACK_IMPORTED_MODULE_0__["default"])
}),
"./src/login.js":
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, "test", function () { return test; });
__webpack_exports__["default"] = ("我家猫真调皮");
const test = "真想揍它一顿"
})
});