模块化的发展历史
我们首先不聊webpack, rollup之类的打包工具,先了解下这些工具没出来之前前端项目是怎么进行开发的
<script src="./src/index1.js"></script>
<script src="./src/index2.js"></script>
<script src="./src/index3.js"></script>
以前没有构建工具的时候,我们在页面上引用脚本只能这样引用,但这样引用就会出现许多问题,首先引用的顺序不能出错,因为你有些JS文件可能要依赖上一个文件中的变量,其次由于开发项目是多人开发,这样的话就可能出现全局污染的问题
比如小A在index1.js中定义了
const a = 1
小B在index2.js中定义了
const a = 2
这样首先会报语法错误,其次就算用var定义,后面的也会把前面的掩盖掉,就会出现全局污染的事情,有人说可以使用立即执行函数包裹起来,这样每个文件就是一个独立的作用域,但这样就会出现新的问题
- 我必须记得每一个
模块中返回对象的命名,才能在其他模块使用过程中正确的使用 - 代码写起来
杂乱无章,每个文件中的代码都需要包裹在一个匿名函数中来编写 - 在
没有合适的规范情况下,每个人、每个公司都可能会任意命名、甚至出现模块名称相同的情况
虽然大公司会制定一些强规范来改善这些问题,但对于大多数公司协同开发依然是一件比较麻烦的事情
CommonJS规范
最早出现的是CommonJS规范,学过Node的都有了解过,Node就是使用的CommonJS规范,使用module,exports,和require关键字进行模块化的开发
基本使用
// 在a.js中定义
const name = '张三'
const age = 23
module.exports = { name, age }
// 在b.js中使用
const { name, age } = require('./a.js')
console.log(name, age) // 张三, 23
CommonJS实现原理
首先我们看下a.js中,它把一个对象赋值给了module.exports,然后又通过require引入了这个对象,下面我们来大概模拟下这个过程
var modules = {
'./a.js': () => {
var module = {}
module.exports = { name, age }
return module.exports
}
}
function require (modulePath) {
return modules[modulePath]()
}
其实源码的大体思路也是这样的,下面我们来看下源码中具体的实现过程
// 在demo1中定义
const name = '张三'
const age = 23
module.exports = {
name,
age
}
// 在main.js中定义
const { name, age } = require('./demo1')
console.log(name, age)
再用webpack打包,看下打包后的代码(删除了注释和换行符)
(() => {
var __webpack_modules__ = {
"./src/demo1.js": (module) => {
eval(
`const name = '张三'
const age = 23
module.exports = {
name,
age
}`
);
},
"./src/main.js": (
__unused_webpack_module,
__unused_webpack_exports,
__webpack_require__
) => {
eval(
`const { name, age } = __webpack_require__("./src/demo1.js")
console.log(name, age)
`
);
},
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
var __webpack_exports__ = __webpack_require__("./src/main.js");
})();
上面就是打包后的代码,把上述文件引用到html文件中就能打印出name和age
上述代码主要分为四个步骤
- 初始化:定义
modules对象 - 定义缓存对象
cache - 定义加载模块函数
require - 执行入口函数 首先定义模块对象
(() => {
var __webpack_modules__ = {
"./src/demo1.js": (module) => {
eval(
`const name = '张三'
const age = 23
module.exports = {
name,
age
}`
);
},
"./src/main.js": (
__unused_webpack_module,
__unused_webpack_exports,
__webpack_require__
) => {
eval(
`const { name, age } = __webpack_require__("./src/demo1.js")
console.log(name, age)
`
);
},
};
webpack会从入口文件触发,去找其依赖的模块,最终合并成一个模块对象
定义缓存对象(eval会把字符串当成JS代码执行)
var __webpack_module_cache__ = {};
定义加载模块函数
function __webpack_require__(moduleId) {
// 根据模块路径在缓存中查找
var cachedModule = __webpack_module_cache__[moduleId];
// 如果存在的话就直接返回exports对象
if (cachedModule !== undefined) {
return cachedModule.exports;
}
// 不存在创建module对象
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
// 然后调用modules中相应的函数给module对象赋值
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
执行入口函数
var __webpack_exports__ = __webpack_require__("./src/main.js");
ESModule规范
基本使用
// 在demo1.js中定义
const age = 23
export const name = '张三'
export default age
// 在main.js中定义
import age, { name } from './demo1'
console.log(age, name)
下面我们看下webpack打包后的代码(去除了注释和换行符)
(() => {
"use strict";
var __webpack_modules__ = {
"./src/demo1.js": (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
eval(
`
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(
__webpack_exports__,
{
"default": () => (__WEBPACK_DEFAULT_EXPORT__),
"name": () => (name)
}
);
const age = 23
const name = '张三'
const __WEBPACK_DEFAULT_EXPORT__ = (age);
`
);
},
"./src/main.js": (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
eval(
`
__webpack_require__.r(__webpack_exports__);
var _demo1__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/demo1.js");
console.log(
_demo1__WEBPACK_IMPORTED_MODULE_0__["default"],
_demo1__WEBPACK_IMPORTED_MODULE_0__.name
)
`
);
},
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = { exports: {} });
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
(() => {
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
}
})()
(() => {
__webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop)
})()
(() => {
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
}
Object.defineProperty(exports, "__esModule", { value: true });
};
})()
var __webpack_exports__ = __webpack_require__("./src/main.js")
})();
其实流程上基本和上CommonJS基本一样,不过在定义导出对象时有些许差别,还多了些辅助函数,下面我们看下具体的代码 首先我们先看下一些辅助函数
// 检查是否是对象自身的属性
(() => {
__webpack_require__.o =
(obj,prop)=>Object.prototype.hasOwnProperty.call(obj, prop)
})()
// 给exports对象赋值格外的属性
(() => {
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
}
Object.defineProperty(exports, "__esModule", { value: true });
};
})()
// 给exports对象属性设置get函数
(() => {
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
}
})()
代码的大体思路都是一样的,都是建立exports对象
总结
写这篇文章的目的也是为了巩固自己的知识点,主要查考了不要秃头啊这位大佬的文章,我这篇文章介绍的比较简单,有兴趣的可以看下下面的文章链接