本文开始我会围绕webpackbabel写一系列的工程化文章,这两个工具我尽管天天用,然而对他们的原理了解的其实不是很深刻,写这些文章的过程其实也是我深刻学习的过程。因为webpackbabel的体系太大,知识点泛滥,不可能一篇文章囊括所有知识点,目前我的打算是从简略动手,先实现一个最简略的能够运行的webpack,而后再看看plugin, loadertree shaking等性能。目前我打算会有这些文章:

  1. 手写最简webpack,也就是本文
  2. webpackplugin实现原理
  3. webpackloader实现原理
  4. webpacktree shaking实现原理
  5. webpackHMR实现原理
  6. babelast原理

所有文章都是原理或者源码解析,欢送关注~

本文可运行代码曾经上传GitHub,大家能够拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/mini-webpack

留神:本文次要讲webpack原理,在实现时并不谨严,而且只解决了importexportdefault状况,如果你想在生产环境应用,请本人增加其余状况的解决和边界判断

为什么要用webpack

笔者刚开始做前端时,其实不晓得什么webpack,也不懂模块化,都是html外面间接写script,引入jquery间接干。所以如果一个页面的JS须要依赖jquerylodash,那html可能就长这样:

<!DOCTYPE html><html>  <head>    <meta charset="utf-8" />    <script src="https://unpkg.com/jquery@3.5.1"></script>    <script src="https://unpkg.com/lodash@4.17.20"></script>    <script src="./src/index.js"></script>  </head>  <body>  </body></html>

这样写会导致几个问题:

  1. 独自看index.js不能清晰的找到他到底依赖哪些内部库
  2. script的程序必须写正确,如果错了就会导致找不到依赖,间接报错
  3. 模块间通信艰难,根本都靠往window上注入变量来裸露给内部
  4. 浏览器严格依照script标签来下载代码,有些没用到的代码也会下载下来
  5. 以后端规模变大,JS脚本会显得很芜杂,项目管理凌乱

webpack的一个最根本的性能就是来解决上述的状况,容许在JS外面通过import或者require等关键字来显式申明依赖,能够援用第三方库,本人的JS代码间也能够互相援用,这样在本质上就实现了前端代码的模块化。因为历史问题,老版的JS并没有本人模块治理计划,所以社区提出了很多模块治理计划,比方ES2015importCommonJSrequire,另外还有AMDCMD等等。就目前我见到的状况来说,import因为曾经成为ES2015规范,所以在客户端宽泛应用,而requireNode.js的自带模块管理机制,也有很宽泛的用处,而AMDCMD的应用曾经很少见了。

然而webpack作为一个凋谢的模块化工具,他是反对ES6CommonJSAMD等多种规范的,不同的模块化规范有不同的解析办法,本文只会讲ES6规范的import计划,这也是客户端JS应用最多的计划。

简略例子

依照业界常规,我也用hello world作为一个简略的例子,然而我将这句话拆成了几局部,放到了不同的文件外面。

先来建一个hello.js,只导出一个简略的字符串:

const hello = 'hello';export default hello;

而后再来一个helloWorld.js,将helloworld拼成一句话,并导出拼接的这个办法:

import hello from './hello';const world = 'world';const helloWorld = () => `${hello} ${world}`;export default helloWorld;

最初再来个index.js,将拼好的hello world插入到页面下来:

import helloWorld from "./helloWorld";const helloWorldStr = helloWorld();function component() {  const element = document.createElement("div");  element.innerHTML = helloWorldStr;  return element;}document.body.appendChild(component());

当初如果你间接在html外面援用index.js是不能运行胜利的,因为大部分浏览器都不反对import这种模块导入。而webpack就是来解决这个问题的,它会将咱们模块化的代码转换成浏览器意识的一般JS来执行。

引入webpack

咱们印象中webpack的配置很多,很麻烦,但那是因为咱们须要开启的性能很多,如果只是解析转换import,配置起来非常简单。

  1. 先把依赖装上吧,这没什么好说的:

    // package.json{  "devDependencies": {    "webpack": "^5.4.0",    "webpack-cli": "^4.2.0"  },}
  2. 为了使用方便,再加个build脚本吧:

    // package.json{  "scripts": {    "build": "webpack"  },}
  3. 最初再简略写下webpack的配置文件就好了:

    // webpack.config.jsconst path = require("path");module.exports = {  mode: "development",  devtool: 'source-map',  entry: "./src/index.js",  output: {    filename: "main.js",    path: path.resolve(__dirname, "dist"),  },};

    这个配置文件外面其实只有指定了入口文件entry和编译后的输入文件目录output就能够失常工作了,这里这个配置的意思是让webpack./src/index.js开始编译,编译后的文件输入到dist/main.js这个文件外面。

    这个配置文件上还有两个配置modedevtool只是我用来不便调试编译后的代码的,mode指定用哪种模式编译,默认是production,会对代码进行压缩和混同,不好读,所以我设置为development;而devtool是用来管制生成哪种粒度的source map,简略来说,想要更好调试,就要更好的,更清晰的source map,然而编译速度变慢;反之,想要编译速度快,就要抉择粒度更粗,更不好读的source mapwebpack提供了很多可供选择的source map,具体的能够看他的文档。

  4. 而后就能够在dist上面建个index.html来援用编译后的代码了:

    // index.html<!DOCTYPE html><html>  <head>    <meta charset="utf-8" />  </head>  <body>    <script src="main.js"></script>  </body></html>
  5. 运行下yarn build就会编译咱们的代码,而后关上index.html就能够看到成果了。

深刻原理

后面讲的这个例子很简略,个别也满足不了咱们理论工程中的需要,然而对于咱们了解原理却是一个很好的突破口,毕竟webpack这么宏大的一个体系,咱们也不能一口吃个瘦子,得一点一点来。

webpack把代码编译成了啥?

为了弄懂他的原理,咱们能够间接从编译后的代码动手,先看看他长啥样子,有的敌人可能一提到去看源码,心理就没底,其实我以前也是这样的。然而齐全没有必要害怕,他编译后的代码浏览器可能执行,那必定就是一般的JS代码,不会藏着这么黑科技。

上面是编译完的代码截图:

尽管咱们只有三个简略的JS文件,然而加上webpack本人的逻辑,编译后的文件还是有一百多行代码,所以即便我把具体逻辑折叠起来了,这个截图还是有点长,为了可能看清楚他的构造,我将它分成了4个局部,标记在了截图上,上面咱们别离来看看这几个局部吧。

  1. 第一局部其实就是一个对象__webpack_modules__,这个对象外面有三个属性,属性名字是咱们三个模块的文件门路,属性的值是一个函数,咱们轻易开展一个./src/helloWorld.js看下:

    咱们发现这个代码内容跟咱们本人写的helloWorld.js十分像:

    他只是在咱们的代码前先调用了__webpack_require__.r__webpack_require__.d,这两个辅助函数咱们在前面会看到。

    而后对咱们的代码进行了一点批改,将咱们的import关键字改成了__webpack_require__函数,并用一个变量_hello__WEBPACK_IMPORTED_MODULE_0__来接管了import进来的内容,前面援用的中央也改成了这个,其余跟这个无关的代码,比方const world = 'world';还是放弃原样的。

    这个__webpack_modules__对象存了所有的模块代码,其实对于模块代码的保留,在不同版本的webpack外面实现的形式并不一样,我这个版本是5.4.0,在4.x的版本外面如同是作为数组存下来,而后在最外层的立刻执行函数外面以参数的模式传进来的。然而不论是哪种形式,都只是转换而后保留一下模块代码而已。

  2. 第二块代码的外围是__webpack_require__,这个代码开展,霎时给了我一种相熟感:

    来看一下这个流程吧:

    1. 先定义一个变量__webpack_module_cache__作为加载了的模块的缓存
    2. __webpack_require__其实就是用来加载模块的
    3. 加载模块时,先查看缓存中有没有,如果有,就间接返回缓存
    4. 如果缓存没有,就从__webpack_modules__将对应的模块取出来执行
    5. __webpack_modules__就是下面第一块代码里的那个对象,取出的模块其实就是咱们本人写的代码,取出执行的也是咱们每个模块的代码
    6. 每个模块执行除了执行咱们的逻辑外,还会将export的内容增加到module.exports上,这就是后面说的__webpack_require__.d辅助办法的作用。增加到module.exports上其实就是增加到了__webpack_module_cache__缓存上,前面再援用这个模块就间接从缓存拿了。

    这个流程我太相熟了,因为他几乎跟Node.jsCommonJS实现思路截然不同,具体的能够看我之前写的这篇文章:深刻Node.js的模块加载机制,手写require函数。

  3. 第三块代码其实就是咱们后面看到过的几个辅助函数的定义,具体干啥的,其实他的正文曾经写了:

    1. __webpack_require__.d:外围其实是Object.defineProperty,次要是用来将咱们模块导出的内容增加到全局的__webpack_module_cache__缓存上。

    2. __webpack_require__.o:其实就是Object.prototype.hasOwnProperty的一个简写而已。

    3. __webpack_require__.r:这个办法就是给每个模块增加一个属性__esModule,来表明他是一个ES6的模块。

    4. 第四块就一行代码,调用__webpack_require__加载入口模块,启动执行。

这样咱们将代码分成了4块,每块的作用都搞清楚,其实webpack干的事件就清晰了:

  1. import这种浏览器不意识的关键字替换成了__webpack_require__函数调用。
  2. __webpack_require__在实现时采纳了相似CommonJS的模块思维。
  3. 一个文件就是一个模块,对应模块缓存上的一个对象。
  4. 当模块代码执行时,会将export的内容增加到这个模块对象上。
  5. 当再次援用一个以前援用过的模块时,会间接从缓存上读取模块。

本人实现一个webpack

当初webpack到底干了什么事件咱们曾经分明了,接下来咱们就能够本人入手实现一个了。依据后面最终生成的代码后果,咱们要实现的代码其实次要分两块:

  1. 遍历所有模块,将每个模块代码读取进去,替换掉importexport关键字,放到__webpack_modules__对象上。
  2. 整个代码外面除了__webpack_modules__和最初启动的入口是变动的,其余代码,像__webpack_require____webpack_require__.r这些办法其实都是固定的,整个代码构造也是固定的,所以齐全能够先定义好一个模板。

应用AST解析代码

因为咱们须要将import这种代码转换成浏览器能辨认的一般JS代码,所以咱们首先要可能将代码解析进去。在解析代码的时候,能够将它读出来当成字符串替换,也能够应用更业余的AST来解析。AST全称叫Abstract Syntax Trees,也就是形象语法树,是一个将代码用树来示意的数据结构,一个代码能够转换成ASTAST又能够转换成代码,而咱们熟知的babel其实就能够做这个工作。要生成AST很简单,波及到编译原理,然而如果仅仅拿来用就比较简单了,本文就先不波及简单的编译原理,而是间接将babel生成好的AST拿来应用。

留神: webpack源码解析AST并不是应用的babel,而是应用的acorn,webpack继承acornParser,本人实现了一个JavascriptParser,本文写作时采纳了babel,这也是一个大家更相熟的工具

比方我先将入口文件读出来,而后用babel转换成AST能够间接这样写:

const fs = require("fs");const parser = require("@babel/parser");const config = require("../webpack.config"); // 引入配置文件// 读取入口文件const fileContent = fs.readFileSync(config.entry, "utf-8");// 应用babel parser解析ASTconst ast = parser.parse(fileContent, { sourceType: "module" });console.log(ast);   // 把ast打印进去看看

下面代码能够将生成好的ast打印在控制台:

这尽管是一个残缺的AST,然而看起来并不清晰,要害数据其实是body字段,这里的body也只是展现了类型名字。所以照着这个写代码其实不好写,这里举荐一个在线工具https://astexplorer.net/,能够很分明的看到每个节点的内容:

从这个解析进去的AST咱们能够看到,body次要有4块代码:

  1. ImportDeclaration:就是第一行的import定义
  2. VariableDeclaration:第三行的一个变量申明
  3. FunctionDeclaration:第五行的一个函数定义
  4. ExpressionStatement:第十三行的一个一般语句

你如果把每个节点开展,会发现他们上面又嵌套了很多其余节点,比方第三行的VariableDeclaration开展后,其实还有个函数调用helloWorld()

应用traverse遍历AST

对于这样一个生成好的AST,咱们能够应用@babel/traverse来对他进行遍历和操作,比方我想拿到ImportDeclaration进行操作,就间接这样写:

// 应用babel traverse来遍历ast上的节点traverse(ast, {  ImportDeclaration(path) {    console.log(path.node);  },});

下面代码能够拿到所有的import语句:

import转换为函数调用

后面咱们说了,咱们的指标是将ES6的import

import helloWorld from "./helloWorld";

转换成一般浏览器能辨认的函数调用:

var _helloWorld__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/helloWorld.js");

为了实现这个性能,咱们还须要引入@babel/types,这个库能够帮咱们创立新的AST节点,所以这个转换代码写进去就是这样:

const t = require("@babel/types");// 应用babel traverse来遍历ast上的节点traverse(ast, {  ImportDeclaration(p) {    // 获取被import的文件    const importFile = p.node.source.value;    // 获取文件门路    let importFilePath = path.join(path.dirname(config.entry), importFile);    importFilePath = `./${importFilePath}.js`;    // 构建一个变量定义的AST节点    const variableDeclaration = t.variableDeclaration("var", [      t.variableDeclarator(        t.identifier(          `__${path.basename(importFile)}__WEBPACK_IMPORTED_MODULE_0__`        ),        t.callExpression(t.identifier("__webpack_require__"), [          t.stringLiteral(importFilePath),        ])      ),    ]);    // 将以后节点替换为变量定义节点    p.replaceWith(variableDeclaration);  },});

下面这段代码咱们用了很多@babel/types上面的API,比方t.variableDeclarationt.variableDeclarator,这些都是用来创立对应的节点的,具体的API能够看这里。留神这个代码外面我有很多写死的中央,比方importFilePath生成逻辑,还应该解决多种后缀名的,还有最终生成的变量名_${path.basename(importFile)}__WEBPACK_IMPORTED_MODULE_0__,最初的数字我也是间接写了0,按理来说应该是依据不同的import程序来生成的,然而本文次要讲webpack的原理,这些细节上我就没花过多工夫了。

下面的代码其实是批改了咱们的AST,批改后的AST能够用@babel/generator又转换为代码:

const generate  = require('@babel/generator').default;const newCode = generate(ast).code;console.log(newCode);

这个打印后果是:

能够看到这个后果外面import helloWorld from "./helloWorld";曾经被转换为var __helloWorld__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/helloWorld.js");

替换import进来的变量

后面咱们将import语句替换成了一个变量定义,变量名字也改为了__helloWorld__WEBPACK_IMPORTED_MODULE_0__,天然要将调用的中央也改了。为了更好的治理,咱们将AST遍历,操作以及最初的生成新代码都封装成一个函数吧。

function parseFile(file) {  // 读取入口文件  const fileContent = fs.readFileSync(file, "utf-8");  // 应用babel parser解析AST  const ast = parser.parse(fileContent, { sourceType: "module" });  let importFilePath = "";  // 应用babel traverse来遍历ast上的节点  traverse(ast, {    ImportDeclaration(p) {      // 跟之前一样的    },  });  const newCode = generate(ast).code;  // 返回一个蕴含必要信息的新对象  return {    file,    dependcies: [importFilePath],    code: newCode,  };}

而后启动执行的时候就能够调这个函数了

parseFile(config.entry);

拿到的后果跟之前的差不多:

好了,当初须要将应用import的中央也替换了,因为咱们曾经晓得了这个中央是将它作为函数调用的,也就是要将

const helloWorldStr = helloWorld();

转为这个样子:

const helloWorldStr = (0,_helloWorld__WEBPACK_IMPORTED_MODULE_0__.default)();

这行代码的成果其实跟_helloWorld__WEBPACK_IMPORTED_MODULE_0__.default()是一样的,为啥在前面包个(0, ),我也不晓得,有晓得的大佬通知下我呗。

所以咱们在traverse外面加一个CallExpression

  traverse(ast, {    ImportDeclaration(p) {      // 跟后面的差不多,省略了    },    CallExpression(p) {      // 如果调用的是import进来的函数      if (p.node.callee.name === importVarName) {        // 就将它替换为转换后的函数名字        p.node.callee.name = `${importCovertVarName}.default`;      }    },  });

这样转换后,咱们再从新生成一下代码,曾经像那么个样子了:

递归解析多个文件

当初咱们有了一个parseFile办法来解析解决入口文件,然而咱们的文件其实不止一个,咱们应该根据模块的依赖关系,递归的将所有的模块都解析了。要实现递归解析也不简单,因为后面的parseFile的依赖dependcies曾经返回了:

  1. 咱们创立一个数组寄存文件的解析后果,初始状态下他只有入口文件的解析后果
  2. 依据入口文件的解析后果,能够拿到入口文件的依赖
  3. 解析所有的依赖,将后果持续加到解析后果数组外面
  4. 始终循环这个解析后果数组,将外面的依赖文件解析完
  5. 最初将解析后果数组返回就行

写成代码就是这样:

function parseFiles(entryFile) {  const entryRes = parseFile(entryFile); // 解析入口文件  const results = [entryRes]; // 将解析后果放入一个数组  // 循环后果数组,将它的依赖全副拿进去解析  for (const res of results) {    const dependencies = res.dependencies;    dependencies.map((dependency) => {      if (dependency) {        const ast = parseFile(dependency);        results.push(ast);      }    });  }  return results;}

而后就能够调用这个办法解析所有文件了:

const allAst = parseFiles(config.entry);console.log(allAst);

看看解析后果吧:

这个后果其实跟咱们最终须要生成的__webpack_modules__曾经很像了,然而还有两块没有解决:

  1. 一个是import进来的内容作为变量应用,比方

    import hello from './hello';const world = 'world';const helloWorld = () => `${hello} ${world}`;
  2. 另一个就是export语句还没解决

替换import进来的变量(作为变量调用)

后面咱们曾经用CallExpression解决过作为函数应用的import变量了,当初要解决作为变量应用的其实用Identifier解决下就行了,解决逻辑跟之前的CallExpression差不多:

  traverse(ast, {    ImportDeclaration(p) {      // 跟以前一样的    },    CallExpression(p) {            // 跟以前一样的    },    Identifier(p) {      // 如果调用的是import进来的变量      if (p.node.name === importVarName) {        // 就将它替换为转换后的变量名字        p.node.name = `${importCovertVarName}.default`;      }    },  });

当初再运行下,import进来的变量名字曾经变掉了:

替换export语句

从咱们须要生成的后果来看,export须要进行两个解决:

  1. 如果一个文件有export default,须要增加一个__webpack_require__.d的辅助办法调用,内容都是固定的,加上就行。
  2. export语句转换为一般的变量定义。

对应生成后果上的这两个:

要解决export语句,在遍历ast的时候增加ExportDefaultDeclaration就行了:

  traverse(ast, {    ImportDeclaration(p) {      // 跟以前一样的    },    CallExpression(p) {            // 跟以前一样的    },    Identifier(p) {      // 跟以前一样的    },    ExportDefaultDeclaration(p) {      hasExport = true; // 先标记是否有export      // 跟后面import相似的,创立一个变量定义节点      const variableDeclaration = t.variableDeclaration("const", [        t.variableDeclarator(          t.identifier("__WEBPACK_DEFAULT_EXPORT__"),          t.identifier(p.node.declaration.name)        ),      ]);      // 将以后节点替换为变量定义节点      p.replaceWith(variableDeclaration);    },  });

而后再运行下就能够看到export语句被替换了:

而后就是依据hasExport变量判断在AST转换为代码的时候要不要加__webpack_require__.d辅助函数:

const EXPORT_DEFAULT_FUN = `__webpack_require__.d(__webpack_exports__, {   "default": () => (__WEBPACK_DEFAULT_EXPORT__)});\n`;function parseFile(file) {  // 省略其余代码  // ......    let newCode = generate(ast).code;  if (hasExport) {    newCode = `${EXPORT_DEFAULT_FUN} ${newCode}`;  }}

最初生成的代码外面export也就解决好了:

__webpack_require__.r的调用添上吧

后面说了,最终生成的代码,每个模块后面都有个__webpack_require__.r的调用

这个只是拿来给模块增加一个__esModule标记的,咱们也给他加上吧,间接在后面export辅助办法前面加点代码就行了:

const ESMODULE_TAG_FUN = `__webpack_require__.r(__webpack_exports__);\n`;function parseFile(file) {  // 省略其余代码  // ......    let newCode = generate(ast).code;  if (hasExport) {    newCode = `${EXPORT_DEFAULT_FUN} ${newCode}`;  }    // 上面增加模块标记代码  newCode = `${ESMODULE_TAG_FUN} ${newCode}`;}

再运行下看看,这个代码也加上了:

创立代码模板

到当初,最难的一块,模块代码的解析和转换咱们其实曾经实现了。上面要做的工作就比较简单了,因为最终生成的代码外面,各种辅助办法都是固定的,动静的局部就是后面解析的模块和入口文件。所以咱们能够创立一个这样的模板,将动静的局部标记进去就行,其余不变的局部写死。这个模板文件的解决,你能够将它读进来作为字符串解决,也能够用模板引擎,我这里采纳ejs模板引擎:

// 模板文件,间接从webpack生成后果抄过来,改改就行/******/ (() => { // webpackBootstrap/******/     "use strict";// 须要替换的__TO_REPLACE_WEBPACK_MODULES__/******/     var __webpack_modules__ = ({                <% __TO_REPLACE_WEBPACK_MODULES__.map(item => { %>                    '<%- item.file %>' :                     ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {                        <%- item.code %>                    }),                <% }) %>            });// 省略两头的辅助办法    /************************************************************************/    /******/     // startup    /******/     // Load entry module// 须要替换的__TO_REPLACE_WEBPACK_ENTRY    /******/     __webpack_require__('<%- __TO_REPLACE_WEBPACK_ENTRY__ %>');    /******/     // This entry module used 'exports' so it can't be inlined    /******/ })()    ;    //# sourceMappingURL=main.js.map

生成最终的代码

生成最终代码的思路就是:

  1. 模板外面用__TO_REPLACE_WEBPACK_MODULES__来生成最终的__webpack_modules__
  2. 模板外面用__TO_REPLACE_WEBPACK_ENTRY__来代替动静的入口文件
  3. webpack代码外面应用后面生成好的AST数组来替换模板的__TO_REPLACE_WEBPACK_MODULES__
  4. webpack代码外面应用后面拿到的入口文件来代替模板的__TO_REPLACE_WEBPACK_ENTRY__
  5. 应用ejs来生成最终的代码

所以代码就是:

// 应用ejs将下面解析好的ast传递给模板// 返回最终生成的代码function generateCode(allAst, entry) {  const temlateFile = fs.readFileSync(    path.join(__dirname, "./template.js"),    "utf-8"  );  const codes = ejs.render(temlateFile, {    __TO_REPLACE_WEBPACK_MODULES__: allAst,    __TO_REPLACE_WEBPACK_ENTRY__: entry,  });  return codes;}

功败垂成

最初将ejs生成好的代码写入配置的输入门路就行了:

const codes = generateCode(allAst, config.entry);fs.writeFileSync(path.join(config.output.path, config.output.filename), codes);

而后就能够应用咱们本人的webpack来编译代码,最初就能够像之前那样关上咱们的html看看成果了:

总结

本文应用简略纯朴的形式讲述了webpack的基本原理,并本人手写实现了一个根本的反对importexportdefaultwebpack

本文可运行代码曾经上传GitHub,大家能够拿下来玩玩:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Engineering/mini-webpack

上面再就本文的要点进行下总结:

  1. webpack最根本的性能其实是将JS的高级模块化语句,importrequire之类的转换为浏览器能意识的一般函数调用语句。
  2. 要进行语言代码的转换,咱们须要对代码进行解析。
  3. 罕用的解析伎俩是AST,也就是将代码转换为形象语法树
  4. AST是一个形容代码构造的树形数据结构,代码能够转换为ASTAST也能够转换为代码。
  5. babel能够将代码转换为AST,然而webpack官网并没有应用babel,而是基于acorn本人实现了一个JavascriptParser。
  6. 本文从webpack构建的后果动手,也应用AST本人生成了一个相似的代码。
  7. webpack最终生成的代码其实分为动静和固定的两局部,咱们将固定的局部写入一个模板,动静的局部在模板外面应用ejs占位。
  8. 生成代码动静局部须要借助babel来生成AST,并对其进行批改,最初再应用babel将其生成新的代码。
  9. 在生成AST时,咱们从配置的入口文件开始,递归的解析所有文件。即解析入口文件的时候,将它的依赖记录下来,入口文件解析完后就去解析他的依赖文件,在解析他的依赖文件时,将依赖的依赖也记录下来,前面持续解析。反复这种步骤,直到所有依赖解析完。
  10. 动静代码生成好后,应用ejs将其写入模板,以生成最终的代码。
  11. 如果要反对require或者AMD,其实思路是相似的,最终生成的代码也是差不多的,次要的差异在AST解析那一块。

参考资料

  1. babel操作AST文档
  2. webpack源码
  3. webpack官网文档

文章的最初,感激你破费贵重的工夫浏览本文,如果本文给了你一点点帮忙或者启发,请不要悭吝你的赞和GitHub小星星,你的反对是作者继续创作的能源。

欢送关注我的公众号进击的大前端第一工夫获取高质量原创~

“前端进阶常识”系列文章源码地址: https://github.com/dennis-jiang/Front-End-Knowledges