You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
(function(modules){varinstalledModules={};function__webpack_require__(moduleId){if(installedModules[moduleId]){returninstalledModules[moduleId].exports;}varmodule=installedModules[moduleId]={i: moduleId,l: false,exports: {}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);// Flag the module as loadedmodule.l=true;// Return the exports of the modulereturnmodule.exports;}// expose the modules object (__webpack_modules__)__webpack_require__.m=modules;// expose the module cache__webpack_require__.c=installedModules;// define getter function for harmony exports__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable: true,get: getter});}};// define __esModule on exports__webpack_require__.r=function(exports){if(typeofSymbol!=='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__webpack_require__.t=function(value,mode){/******/if(mode&1)value=__webpack_require__(value);if(mode&8)returnvalue;if((mode&4)&&typeofvalue==='object'&&value&&value.__esModule)returnvalue;varns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,'default',{enumerable: true,value: value});if(mode&2&&typeofvalue!='string')for(varkeyinvalue)__webpack_require__.d(ns,key,function(key){returnvalue[key];}.bind(null,key));returnns;};// getDefaultExport function for compatibility with non-harmony modules__webpack_require__.n=function(module){vargetter=module&&module.__esModule ?
functiongetDefault(){returnmodule['default'];} :
functiongetModuleExports(){returnmodule;};__webpack_require__.d(getter,'a',getter);returngetter;};// Object.prototype.hasOwnProperty.call__webpack_require__.o=function(object,property){returnObject.prototype.hasOwnProperty.call(object,property);};// __webpack_public_path____webpack_require__.p="";// Load entry module and return exportsreturn__webpack_require__(__webpack_require__.s="./src/index.js");})({"./src/index.js": (function(module,__webpack_exports__,__webpack_require__){"use strict";eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test */ \"./src/test.js\");\n\n\nconsole.log(_test__WEBPACK_IMPORTED_MODULE_0__[\"default\"])\n\n\n//# sourceURL=webpack:///./src/index.js?");}),"./src/message.js": (function(module,__webpack_exports__,__webpack_require__){// ...}),"./src/test.js": (function(module,__webpack_exports__,__webpack_require__){// ...})});
背景
随着前端复杂度的不断提升,诞生出很多打包工具,比如最先的
grunt
,gulp
。到后来的webpack
和Parcel
。但是目前很多脚手架工具,比如vue-cli
已经帮我们集成了一些构建工具的使用。有的时候我们可能并不知道其内部的实现原理。其实了解这些工具的工作方式可以帮助我们更好理解和使用这些工具,也方便我们在项目开发中应用。一些知识点
在我们开始造轮子前,我们需要对一些知识点做一些储备工作。
模块化知识
首先是模块的相关知识,主要的是
es6 modules
和commonJS
模块化的规范。更详细的介绍可以参考这里 CommonJS、AMD/CMD、ES6 Modules 以及 webpack 原理浅析。现在我们只需要了解:es6 modules
是一个编译时就会确定模块依赖关系的方式。CommonJS
的模块规范中,Node 在对 JS 文件进行编译的过程中,会对文件中的内容进行头尾包装,在头部添加
(function (export, require, modules, __filename, __dirname){\n
在尾部添加了\n};
。这样我们在单个JS文件内部可以使用这些参数。AST 基础知识
什么是抽象语法树?
大家可以通过Esprima 这个网站来将代码转化成
ast
。首先一段代码转化成的抽象语法树是一个对象,该对象会有一个顶级的type
属性Program
,第二个属性是body
是一个数组。body数组中存放的每一项都是一个对象,里面包含了所有的对于该语句的描述信息:进入正题
webpack 简易打包
有了上面这些基础的知识,我们先来看一下一个简单的
webpack
打包的过程,首先我们定义3个文件:方式很简单,定义了一个
index.js
引用test.js
;test.js
内部引用message.js
。看一下打包后的代码:看起来很乱?没关系,我们来屡一下。一眼看过去我们看到的是这样的形式:
这样好理解了吧,就是一个自执行函数,传入了一个
modules
对象,modules 对象是什么样的格式呢?上面的代码已经给了我们答案:是这样的一个
路径 --> 函数
这样的 key,value 键值对。而函数内部是我们定义的文件转移成 ES5 之后的代码,通过eval
来执行,为了方便大家理解,我对eval
内的代码做了一下格式化:到这里基本上结构是分析完了,接着我们看看他的执行,自执行函数一开始执行的代码是:
调用了
__webpack_require_
函数,并传入了一个moduleId
参数是"./src/index.js"
。再看看函数内部的主要实现:这里调用了我们
modules
中的函数,并传入了__webpack_require__
函数作为函数内部的调用。module.exports
参数作为函数内部的导出。因为index.js
里面引用了test.js
,所以又会通过__webpack_require__
来执行对test.js
的加载:test.js
内又使用了message.js
所以,test.js
内部又会执行对message.js
的加载。message.js
执行完成之后,因为没有依赖项,所以直接返回了结果:执行完成之后,再一级一级返回到根文件
index.js
。最终完成整个文件依赖的处理。整个过程中,我们像是通过一个依赖关系树的形式,不断地向数的内部进入,等返回结果,又开始回溯到根。
开发一个简单的 tinypack
通过上面的这些调研,我们先考虑一下一个基础的打包编译工具可以做什么?
第一个问题,转换语法,其实我们可以通过
babel
来做。核心步骤也就是:babylon
生成ASTbabel-core
将AST重新生成源码接着我们需要处理模块依赖的关系,那就需要得到一个依赖关系视图。好在
babel-traverse
提供了一个可以遍历AST
视图并做处理的功能,通过ImportDeclaration
可以得到依赖属性:到目前为止,我们也只是得到根文件的依赖关系和编译后的代码,比如我们的
index.js
依赖了test.js
但是我们并不知道test.js
还需要依赖message.js
,他们的源码也是没有编译过。所以此时我们还需要做深度遍历,得到完成的深度依赖关系:那么进行到这一步我们已经完成了所有文件的编译解析。最后一步,就是需要我们按照
webpack
的思想对源码进行一些包装。第一步,先是要生成一个modules
对象:得到
modules
对象后,接下来便是对整体文件的外部包装,注册require
,module.exports
:而函数内部,也只是循环执行每个依赖文件的 JS 代码而已,完成代码:
到这里基本上也就介绍完了,接下来就是输出编译好的文件了,这里我们为了可以全局使用
tinypack
包,我们还需要为其添加到全局命令(这里直接参考我的源码吧,不再赘述了)。我们来测试一下:npm i tinypack_demo@1.0.7 -g cd examples tinypack
看一下输出的文件:
再测试一下:
恩,基本上已经完成一个建议的
tinypack
。参考文章
抽象语法树 Abstract syntax tree
一看就懂的JS抽象语法树
源码
tinypack 所有的源码已经上传 github
The text was updated successfully, but these errors were encountered: