根据webpack打包规则,实现简单的打包手写实现
首先导入导出分为几类
- CommonJS导入CommonJS
- EsModule导入CommonJS
- EsModule导入EsModule
根据实现规则,简单代码样例
// index.js
// import log,{age} from "./log.js"
// // let log = require('./log.js')
// console.log('index.js内容')
// console.log("log", log,age)
// log.js
// CommonJS导出
module.exports ="logger"
// export default "jack"
// export const age = 18;
手写模拟实现代码模块中的代码经过 webpack 打包之后,就被放在了一个自执行函数中,这个自调用函数接收一个参数,它的值是一个对象,这个对象的键就是相对于当前项目来说, 被打包文件的路径(在这里被赋值给了moduleId),它的值就是我们被打包模块中的源代码,这个函数定义的时候接收了两个参数 module exports
在单模块打包中,且这个模块中没有其它的导包操作为例
- 将所有的内容都放置于一个自调用函数(IIFE)中,然后将被打包模块相关信息进行传参
- 相关信息就是一个对象,格式就是 moduleId:组装后的函数( 函数体就是打包前的源码 )
- 自调用函数体内首先定义一个空对象用于存储缓存
- 自定义一个 webpack_require 函数,它接收一个 ModuleId
- 这个 moduleId 是在自调用函数体的最后一行调用时传入的
自调用函数逻辑:
- 判断当前 ModuleID 对应的模块是否存在于缓存中
- 如果缓存中不存在的情况下,就自定义的一个 module 存放一个对象
- 同时还将这个 对象存放在了 installModules[moduleId] 里
- 这个对象有三个属性: i (存放当前被加载模块的 moduleId), l(用于标记当前模块是否已加载, true ) , exports={} (一个容器,在将来调用自己的 require 方法时去存放我们被打包模块中的内容)
- 通过 Modules[moduleId] 找到最初组装的那个函数以 call 的方式来调用
- 首先修改了this指向,然后传入了 module 和 module.exports , 最后还有 一个 webpack_require ,为了将来应对被打包模块中还有其它的模块导入
(function (modules) {
// 缓存被加载过的模块
let installedModules = {};
// 定义一个__webpak_require__方法来替换 import require的加载操作
function __webpack_require__(moduleId) {
// 缓存优先,判断当前缓存中是否存在要被加载的模块,如果存在就直接返回
if (installedModules[moduleId]) {
return installedModules[moduleId].exports
}
// 如果当前缓存中不存在,就需要自己定义,执行被导入的模块内容加载
let module = installedModules[moduleId] = {
i: moduleId, // 存放当前被加载模块的 moduleId
l: false, // 用于标记当前模块是否已加载, true
exports: {} // 一个容器,在将来调用自己的 require 方法时去存放我们被打包模块中的内容
}
// 调用当前moduleId对应的函数,完成内容的加载,__webpack_require__c参数存在是为了解决递归调用的问题
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
// 当上述方法调用结束之后,修改 l 的值,标识当前模块内容已经加载完成了
module.l = true
// 加载工作完成之后,将拿回来的内容返回至调用位置
return module.exports
}
// 定义m属性来保存modules
__webpack_require__.m = modules
// 定义c属性用于保存cache
__webpack_require__.c = installedModules
// 定义o方法就是接收一个对象,和一个属性名,然后返回一个布尔值,判断这个对象的身上是否存在这个属性
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty(object, property)
}
// 定义d方法用于在对象身上添加制定属性,并且给该属性提供一个getter
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
enumerable: true,
get: getter
});
}
}
// 定义r方法用于标识当前模块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
})
}
// 定义n方法用于设置具体的getter,依据不同的规范下的 module来返回一个相应的 getter
__webpack_require__.n = function (module) {
let getter = module && module.__esModule ? function getDefault() {
return module['default']
} : function getModuleExports() {
return module
}
__webpack_require__.d(getter, 'a', getter)
return getter
}
// 定义p属性,用于保存资源访问路径
__webpack_require__.p = ""
// 调用__webpack_require__方法,执行模块导入和加载操作
return __webpack_require__(__webpack_require__.s = "./src/index.js")
})(
// CommonJS导入CommonJS
// {
// "./src/index.js": (function (module, exports, __webpack_require__) {
// let log = __webpack_require__( /*! ./log.js */ "./src/log.js")
// console.log('index.js内容')
// console.log("log", log)
// }),
// "./src/log.js": (function (module, exports) {
// // CommonJS导出
// module.exports = "logger"
// })
// }
// EsModule导入CommonJS
{
"./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _log_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./log.js */ "./src/log.js");
var _log_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/ __webpack_require__.n(_log_js__WEBPACK_IMPORTED_MODULE_0__);
console.log('index.js内容')
console.log("log", _log_js__WEBPACK_IMPORTED_MODULE_0___default.a)
}),
"./src/log.js": (function (module, exports) {
module.exports = "logger"
})
}
// EsModule导入EsModule
// {
// "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) {
// "use strict";
// __webpack_require__.r(__webpack_exports__);
// var _log_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__( /*! ./log.js */ "./src/log.js");
// console.log('index.js内容')
// console.log("log", _log_js__WEBPACK_IMPORTED_MODULE_0__["default"], _log_js__WEBPACK_IMPORTED_MODULE_0__["age"])
// }),
// "./src/log.js": (function (module, __webpack_exports__, __webpack_require__) {
// "use strict";
// __webpack_require__.r(__webpack_exports__);
// __webpack_require__.d(__webpack_exports__, "age", function () {
// return age;
// });
// __webpack_exports__["default"] = ("jack");
// const age = 18;
// })
// }
)
通过编译生成的index.html
,引入手写built.js
文件,控制台可以正常打印
有一个特殊的t
方法,实现如下
// weback原生注释
// 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
// 定义t方法,用于加载制定value的模块内容,之后对内容进行处理再返回.
// 接收二个参数,一个是 value 一般用于表示被加载的模块id ,第二个值 mode 是一个二进制的数值
__webpack_require__.t = function (value, mode) {
// t 方法内部做的第一件事情就是调用自定义的 require 方法加载value(value一般就是模块id) 对应的模块导出,重新赋值给 value
// 当获取到了这个 value 值之后余下的 8 4 ns 2 都是对当前的内容进行加工处理,然后返回使用
if (mode & 1) {
value = __webpack_require__(value)
}
// 当mode & 8 成立是直接将 value 返回 ( 1和8同时成立,相当于是commonJS )
if (mode & 8) {
return value
}
// 当 mode & 4 成立时直接将 value 返回(esModule)
if (mode & 4 && typeof vlaue === 'object' && value && value.__esModule) {
return value
}
// 如果8和4都没有成立则需要自定义ns来通过default属性返回内容
let ns = Object.create(null)
__webpack_require__.r(ns) // ns通过r方法后会多一个Symbol.toStringTag和__esModule
// 如果拿到的 value 是一个可以直接使用的内容,例如是一个字符串,将它挂载到 ns 的 default 属性上
Object.defineProperty(ns, 'default', {
enumerable: true,
value: value
})
// 例如返回的是一个对象,进行遍历,挂载相应key的属性,还有getter
if (mode & 2 && typeof value !== 'string') {
for (var key in value) {
__webpack_require__.d(ns, key, function () {
// 提供一个getter
return value[key]
}.bind(null, key))
}
}
return ns
}
测试代码
// t方法测试
{
"./src/index.js": (function (module, exports, __webpack_require__) {
let log = __webpack_require__.t( /*! ./log.js */ "./src/log.js", 0b0111) // 通过第二个值不同设置可以测试不同情况
console.log('index.js内容')
console.log("log", log)
}),
"./src/log.js": (function (module, exports) {
// CommonJS导出
module.exports = "logger"
})
}
转载自:https://segmentfault.com/a/1190000041359198