博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
minipack 源码简析
阅读量:6454 次
发布时间:2019-06-23

本文共 5599 字,大约阅读时间需要 18 分钟。

背景:其实“打包”对于前端来说再熟悉不过了,但是深入其中的原理,却不是人人都熟悉。由于webpack功能的强大和盛行,我们大部分都是所谓的“配置工程师”。借此,特地简单分析了一官方文档中提到的一个minipack项目的源码,以此深入了解下什么是打包?以及打包的原理是什么?

文章写的比较平,是按照分析代码的顺序写的,细微有些总结,有错误或不妥之处,恳请指出。

项目地址:

原始代码:

// 入口文件 entry.jsimport message from './message.js';console.log(message);// message.jsimport {name} from './name.js';export default `hello ${name}!`;// name.jsexport const name = 'world';复制代码

读取文件内容,分析依赖,第一步需要解析源码,生成抽象语法树。

第一步,读取入口文件,生成 AST,递归生成依赖关系对象 graph。 其中,createAsset 函数是解析js文本,生成每个文件对应的一个对象,其中 code 的代码是经过babel-preset-env转换后可在浏览器中执行的代码。

const {code} = transformFromAst(ast, null, {    presets: ['env'],  });复制代码

createGraph 函数生成依赖关系对象。

[   { id: 0,    filename: './example/entry.js',    dependencies: [ './message.js' ],    code: '"use strict";\n\nvar _message = require("./message.js");\n\nvar _message2 = _interopRequireDefault(_message);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nconsole.log(_message2.default);',    mapping: { './message.js': 1 } },  { id: 1,    filename: 'example/message.js',    dependencies: [ './name.js' ],    code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\n\nvar _name = require("./name.js");\n\nexports.default = "hello " + _name.name + "!";',    mapping: { './name.js': 2 } },  { id: 2,    filename: 'example/name.js',    dependencies: [],    code: '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nvar name = exports.name = \'world\';',    mapping: {} } 	]复制代码

有了依赖关系图,下一步就是将代码打包可以在浏览器中运行的包。

首先我们将依赖图解析成如下字符串(其实是对象没用{}包裹的格式):

关键代码是这句:

modules += `${mod.id}: [  function (require, module, exports) {    ${mod.code}  },  ${
JSON.stringify(mod.mapping)},],`;复制代码
0: [      function (require, module, exports) {        // -------------- mod.code --------------        "use strict";        var _message = require("./message.js");        var _message2 = _interopRequireDefault(_message);        function _interopRequireDefault(obj) {           return obj && obj.__esModule ? obj : { default: obj };         }        console.log(_message2.default);        // --------------------------------------      },      {
"./message.js":1}, ], 1: [ function (require, module, exports) { // -------------- mod.code -------------- "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _name = require("./name.js"); exports.default = "hello " + _name.name + "!"; // -------------------------------------- }, {
"./name.js":2}, ], 2: [ function (require, module, exports) { // -------------- mod.code -------------- "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var name = exports.name = 'world'; // -------------------------------------- }, {}, ],复制代码

这里,我们比较下源码:

// 入口文件 entry.jsimport message from './message.js';console.log(message);// ---"use strict";var _message = require("./message.js");var _message2 = _interopRequireDefault(_message);function _interopRequireDefault(obj) {   return obj && obj.__esModule ? obj : { default: obj }; }console.log(_message2.default);// message.jsimport {name} from './name.js';export default `hello ${name}!`;// ---"use strict";Object.defineProperty(exports, "__esModule", {  value: true});var _name = require("./name.js");exports.default = "hello " + _name.name + "!";// name.jsexport const name = 'world';// ---"use strict";Object.defineProperty(exports, "__esModule", {  value: true});var name = exports.name = 'world';复制代码

可以看出,babel在转换原始code的时候,引入了require函数来解决模块引用问题。但是其实浏览器仍然是不认识的。因此还需要额外定义一个require函数(其实这部分和requirejs原理类似的模块化解决方案,其中原理其实也很简单)

得到这个字符串后,再最后拼接起来即最终结果 => 然后,我们还需要定义一个自执行函数文本,并将上述字符串传入其中,拼接结果如下:

(function (modules) {	function require(id) {		const [fn, mapping] = modules[id];		function localRequire(name) {			return require(mapping[name]);		}		const module = { exports: {} };		fn(localRequire, module, module.exports);		return module.exports;	}	require(0);})({	0: [		function (require, module, exports) {			"use strict";			var _message = require("./message.js");			var _message2 = _interopRequireDefault(_message);			function _interopRequireDefault(obj) {				return obj && obj.__esModule ? obj : { default: obj };			}			console.log(_message2.default);		},		{ "./message.js": 1 },	],	1: [		function (require, module, exports) {			"use strict";			Object.defineProperty(exports, "__esModule", {				value: true			});			var _name = require("./name.js");			exports.default = "hello " + _name.name + "!";		},		{ "./name.js": 2 },	],	2: [		function (require, module, exports) {			"use strict";			Object.defineProperty(exports, "__esModule", {				value: true			});			var name = exports.name = 'world';		},		{},	],})复制代码

我们执行最后的结果,会输出"hello world"。

那我们仔细分析下打包后的这段代码:

首先这是一个自执行函数,传入的字符串外面包裹上{}后是一个对象,形如<moduleId>: <value>的格式。

自执行函数的主体部分定义了一个require函数:

function require(id) {	const [fn, mapping] = modules[id];	function localRequire(name) {		return require(mapping[name]);	}	const module = { exports: {} };	fn(localRequire, module, module.exports);	return module.exports;}复制代码

接收一个模块id,过程如下:

  1. 第一步:解构module(数组解构),获取fn和当前module的依赖路径
  2. 第二步:定义引入依赖函数(相对引用),函数体同样是获取到依赖module的id,localRequire 函数传入到fn中
  3. 第三步:定义module变量,保存的是依赖模块导出的对象,存储在module.exports中,module和module.exports也传入到fn中
  4. 第四步:递归执行,直到子module中不再执行传入的require函数

简单来说,模块之间通过requireexports联系,至于模块内部的实现,只在模块内可见。


由此,可以看出,其实原理并不是很复杂,但是却很巧妙,要了解“打包”的原理,也需要了解“模块化”的一些知识。前端发展虽快,但是深入到基础,会发现其实是一脉相通的。

参考中文资料,里面有代码的逐句翻译(外国人的注释写的是真详细啊):

(本文始发于知乎专栏:)

转载地址:http://qxfzo.baihongyu.com/

你可能感兴趣的文章
Struts2和Spring MVC的区别
查看>>
angular-bootstrap ui-date组件问题总结
查看>>
理解Javascript参数中的arguments对象
查看>>
p2:千行代码入门python
查看>>
bzoj1106[POI2007]立方体大作战tet*
查看>>
spring boot configuration annotation processor not found in classpath问题解决
查看>>
【转】正则基础之——神奇的转义
查看>>
团队项目测试报告与用户反馈
查看>>
MyBatis(1)——快速入门
查看>>
对软件工程课程的期望
查看>>
CPU高问题排查
查看>>
Mysql中文字符串提取datetime
查看>>
CentOS访问Windows共享文件夹的方法
查看>>
IOS 与ANDROID框架及应用开发模式对比一
查看>>
由中序遍历和后序遍历求前序遍历
查看>>
JQUERY Uploadify 3.1 C#使用案例
查看>>
coursera 北京大学 程序设计与算法 专项课程 完美覆盖
查看>>
firewall 端口转发
查看>>
wndows make images
查看>>
FS系统开发设计(思维导图)
查看>>