webpack通过配置项就可以实现各种场景下的打包,那么它究竟是怎么打包的呢?网上的简易打包原理怎么看都云里雾里,不如自己悄咪咪实现一个,揭开这层神秘的面纱…通过本文学到什么?
- webpack打包后的结果分析
- 通过demo实现webpack简易打包原理
- 涉及到的知识点:babel模块、fs模块、path模块
- webpack4打包结果
- 手写demo实现简易打包过程
- 获取 modules (单个模块路径、模块内容、所属依赖)
1.1 读取模块内容
1.2 获取抽象语法树
1.3 获取依赖
1.4 获取内容
1.5 总结
- 获取所有依赖内容并处理成想要的格式
2.1 获取所有依赖内容
2.2 处理成想要的格式
2.3 总结
- 立即执行函数
3.1 相关知识梳理
3.2 实现
3.3 总结
- 打包结果放入dist文件
- 总结
- 获取 modules (单个模块路径、模块内容、所属依赖)
目录结构
文章图片
index.js
import add from "./add.js"
import {minus} from "./minus.js";
const sum = add(1,2);
const division = minus(2,1);
console.log(sum);
console.log(division);
add.js
export default (a,b)=>{
return a+b;
}
minus.js
export const minus = (a,b)=>{
return a-b
}
一、webpack4打包结果
正常利用webpack4打包后的代码不利于阅读,所以配置 webpack.config.js 不压缩打包结果。
optimization: {
namedModules: true, // true 打包后的 moduleId 为文件路径。false 打包后的 moduleId 为数字
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress: false,
},
}),
],
},
查看webpack输出结果去分析webpack打包流程
输出的 bundle.js,删掉了一些代码,突出重点。
可以看到是一个
立即执行函数
,传入的是一个对象。这个对象存放了项目用到的所有模块的对象,其中每个模块存储为{ 模块路径: 模块导出代码函数 }。/******/ (function(modules) { ...
/******/ })
/******/ ({/***/ "./src/add.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);
\n/* harmony default export */ __webpack_exports__[\"default\"] = ((a,b)=>{\nreturn a+b;
\n});
\n\n//# sourceURL=webpack:///./src/add.js?");
/***/ }),/***/ "./src/index.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);
\n/* harmony import */ var _add_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./add.js */ \"./src/add.js\");
\n/* harmony import */ var _minus_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./minus.js */ \"./src/minus.js\");
\n\n\n\nconst sum = Object(_add_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"])(1,2);
\nconst division = Object(_minus_js__WEBPACK_IMPORTED_MODULE_1__[\"minus\"])(2,1);
\n\nconsole.log(sum);
\nconsole.log(division);
\n\n//# sourceURL=webpack:///./src/index.js?");
/***/ }),/***/ "./src/minus.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);
\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"minus\", function() { return minus;
});
\nconst minus = (a,b)=>{\nreturn a-b\n}\n\n//# sourceURL=webpack:///./src/minus.js?");
/***/ })/******/ });
我们来看看 modules 传入这个函数后做了什么?
/******/ (function(modules) { // webpackBootstrap
/******/// 模块缓存作用,已加载的模块可以不用再重新读取,提升性能
/******/var installedModules = {};
/******/
/******/// 关键函数,加载模块代码,形式有点像Node的CommonJS模块,但这里是可跑在浏览器上的es5代码
/******/function __webpack_require__(moduleId) {
/******/
/******/// 缓存检查,有则直接从缓存中取得
/******/if(installedModules[moduleId]) {
/******/return installedModules[moduleId].exports;
/******/}
/******/// 先创建一个空模块,塞入缓存中
/******/var module = installedModules[moduleId] = {
/******/i: moduleId,
/******/l: false, // 标记是否已经加载
/******/exports: {} // 初始模块为空
/******/};
/******/
/******/// 把要加载的模块内容,挂载到module.exports上
/******/modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/module.l = true;
// 标记为已加载
/******/// 返回加载的模块,调用方直接调用即可
/******/return module.exports;
/******/}
/******/
/******/// 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(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/}
/******/Object.defineProperty(exports, '__esModule', { value: true });
/******/};
/******/
/******/// 启动入口模块index.js
/******/return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })
/************************************************************************/
/******/ ({...
/******/ });
- 首先定义了一个缓存区
installedModules
,从 return 中返回的 ./src/index.js 就是在 webpack.config.js 中定义的 entry。也就是入口文件路径。 - 执行
webpack_require
函数,加载 ./src/index.js 模块代码,加载的是模块路径作为 moduleId 传入。 - 利用先前定义的缓存区 installedModules 判断当前
moduleId
(其中moduleId就是模块路径,如./src/commonjs/index.js)是否存在在缓存区 installedModules 中,如果存在从缓存取用,否则通过创建空模块,把要加载的模块内容,挂载到module.exports上,并返回。
模块路径
、模块内容
、所属依赖
这三个,分别在执行期间动态插入。那么如果我们要手动实现具体怎么操作呢?二、下面我们来手写demo实现这个过程
目录结构
文章图片
1. 获取 modules (单个模块路径、模块内容、所属依赖)
其中 modules 存放所有模块的数组,数组中每个元素存储{ 模块路径: 模块导出代码函数 },其中模块导出代码函数包括模块依赖和当前模块内容,都以es5的方式存储。
流程如下图所示,主要是利用
AST抽象语法树
,获取模块的依赖,再将当前模块内容转为浏览器可识别的es5语法,最后将当前模块路径,依赖,模块内容输出为想要的格式。文章图片
1.1 读取模块内容 1.1.1 相关知识梳理 Node.js 内置的
fs
模块就是文件系统模块,负责读写文件。也同时提供了异步和同步的方法。1) 异步获取文件
异步方法是因为JavaScript的单线程模型,执行IO操作时,JavaScript代码无需等待,而是传入回调函数后,继续执行后续JavaScript代码。
异步读文件为 readFile ,回调函数接收两个参数。其使用方式为:
fs.readFile('sample.txt', 'utf-8', function (err, data) {
if (err) {
console.log(err);
} else {
console.log(data);
}
});
2) 同步读文件
其中 readFileSync 为同步方法。与异步不同的是不接收回调函数,函数直接返回结果。
var data = https://www.it610.com/article/fs.readFileSync('sample.txt', 'utf-8');
1.1.2 实现 此处使用同步、异步方法都行,这里我使用同步获取文件的方法,由于要捕获错误,所以Try-Catch包裹。
try {
const body = fs.readFileSync(file,'utf-8')
} catch (err) {
// 出错了
}
1.2 获取抽象语法树 读取文件后我们希望获取文件的内容和依赖,可以通过正则表达式、语义分析等等,正则可以匹配但是有复杂的代码结构时,正则看起来繁琐又不能理解,所以出现了抽象语法树AST的方式,对源代码的树状表现形式,既让代码有了意义,又能让维护者容易维护。
其实无论是代码编译(babel),打包(webpack),代码压缩,css预处理,代码校验(eslint),代码美化(pretiier),这些的实现都离不开AST。只是各个过程的实现算法不同。
常见的Javascript Parser有很多:
- babylon:应用于bable
- acorn:应用于webpack
- espree:应用于eslint
1.2.1 相关知识梳理 babel
Babel 是一个编译器(输入源码 => 输出编译后的代码)。就像其他编译器一样,编译过程分为三个阶段:解析、转换和打印输出。在parse阶段,babel使用babylon库将源代码转换为AST,在transform阶段,利用各种插件进行代码转换,在generator阶段,再利用代码生成工具,将AST转换成代码。
文章图片
解析
利用 @babel/parser 去解析,有两个API,分别是 parse 和 parseExpression 和 parseExpression 方法。
babelParser.parse(code, [options])
babelParser.parseExpression(code, [options])
区别是 parse 将提供的代码作为一个完整的ECMAScript程序进行解析,parseExpression 则尝试解析单个表达式并考虑性能。
其中 options 中与我们有关的便是 sourceType 参数,指示分析代码的模式。可以是"script", “module"或"unambiguous"之一。默认为"script”。 “unambiguous"将使@babel/parser尝试根据存在的ES6导入或导出语句进行猜测。带有ES6 import和export的文件被视为"module”,否则是"script"。
1.1.2 实现 我们只需要将代码作为完整的ECMAScript程序进行解析就行,所以选择 parse 方法。 sourceType 参数根据官网的定义带有ES6 import和export的文件被视为"module"。
const ast = parser.parse(body,{
sourceType:'module' //表示我们要解析的是ES模块
});
我们来看看获取到的结果
文章图片
只能看出来是一个数组,对应我们的index.js文件的AST,具体内容我们通过 ast.program.body 打印出来
文章图片
可以看到当前index.js的AST中,引入了add.js, minus.js。这个就是我们的依赖,所以我们要收集这个文件的这些依赖和所对应的路径。
1.3 获取依赖 由于经过上述 parse 转换得到的AST树并不能得到我们想要的依赖,所以我们对于上述结构再次进行转换,其中babel为我们提供了 babel-traverse 方法。
1.3.1 相关知识梳理
babel-traverse
是一个对ast进行遍历的工具。类似于字符串的replace方法,指定一个正则表达式,就能对字符串进行替换。只不过babel-traverse是对ast进行替换。使用ast对代码修改会更有优势,支持各种语法匹配模式,比如条件表达式、函数表达式,while循环等。看看官网的使用:
traverse(ast, {
enter(path) {
if (
path.node.type === "Identifier" &&
path.node.name === "n"
) {
path.node.name = "x";
}
}
});
其中enter代表ast的当前节点为enter的值放入path变量中,利用path去获取其他节点属性值并做更改。
1.3.2 实现
文章图片
我们看第二步中获取到的ast树,这里我们想要获取依赖的路径,得通过ImportDeclaration节点的source.value来获取依赖的文件名,拼好对应的路径。
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file);
const abspath = './' + path.join(dirname,node.source.value);
deps[node.source.value] = abspath;
}
})
其中,ImportDeclaration方法代表的是对type类型为ImportDeclaration的节点的处理;那么依赖名就是node.source.value;path.dirname()会返回当前路径;path.join()方法会将当前路径和依赖名拼接到一起,获取依赖路径。
最后我们看获取到的结果,当前index.js 文件又两个依赖,分别是add.js, minus.js,对应路径分别是’./src/add.js’, ‘./src/minus.js’。
文章图片
1.4 获取内容 获取依赖后,便是转换当前文件和依赖的内容,将 es6 语法转换为浏览器能识别的 es5 代码。
1.4.1 相关知识梳理 babel同样为我们提供转译的功能,包括字符串转码、文件(同步/异步)转码、Babel AST转码。由于我们上面得到是 AST 树,所以这里利用Babel AST转码(transformFromAst 方法)。transformFromAst 方法便是利用之前得到的 AST 转换为浏览器可识别的 es5 的代码。
babel.transformFromAst(ast, code, options);
// => { code, map, ast }
其中 options 参数转换的配置对象。
@babel/preset-env 是一个智能的babel预设, 让你能使用最新的JavaScript语法, 它会帮你转换成代码的目标运行环境支持的语法, 提升你的开发效率并让打包后的代码体积更小。
1.4.2 实现
const {code} = babel.transformFromAst(ast,null,{
presets:["@babel/preset-env"]
})
转换结果就是整个js转换为es5。
文章图片
1.5 总结 目前整体代码为
const getModuleInfo = (file)=>{
// 获取当前文件内容
const body = fs.readFileSync(file,'utf-8')
// 当前文件转换为AST
const ast = parser.parse(body,{
sourceType:'module'
});
// 获取当前文件依赖
const deps = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file);
const abspath = './' + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})
// 当前文件内容转码
const {code} = babel.transformFromAst(ast,null,{
presets:["@babel/preset-env"]
})const moduleInfo = {file,deps,code}return moduleInfo;
}
getModuleInfo('./src/index.js');
通过以上的四步,我们就已经获取到了入口文件 index.js 的依赖,转码后的内容,打印出结果可以看到当前文件的
路径
、依赖
、转译后的内容
构成的对象。文章图片
2 获取所有依赖内容并处理成想要的格式
对于入口模块得到的路径、依赖、转译后的内容,我们可以看到对于 add.js 和 minus.js 两个模块还没有处理,从模块的依赖中可以得到这两个文件的路径,再次进行上一步的单个模块获取路径、依赖、转译后的内容步骤,当直到所有模块的依赖为空的时候,才算处理完。最后将这个以所有模块得到的路径、依赖、转译后的内容为对象的数组进行转化为路径为key,依赖、内容为value的对象(这种格式方便后期立即执行函数使用)。
文章图片
2.1 获取所有依赖内容 我们可以看到当前获取的内容只是入口文件的内容,那么入口文件所依赖的文件也得进行依赖查找,对应的文件内容进行转译。所以我们将入口文件 index.js 的依赖文件再次进行遍历,进行上一步的当前文件的路径、依赖、转译后的内容。层层递进,直到当前文件依赖为空。
const entry =getModuleInfo(file)
const temp = [entry]for (let i = 0;
i
先将入口文件的 moduleInfo 对象放入 temp 数组,然后遍历入口文件的依赖文件,依次循坏,结果如下:
文章图片
2.2 处理成想要的格式 根据 webpack 打包后打印出的 moudles 结果与我们现在从上一步得到的结果 temp 数组中每一项 moduleInfo 不太一致,所以需要将 moduleInfo.file 文件路径作为对象的属性,moduleInfo.deps 依赖和 moduleInfo.code 内容作为属性值输出。
temp.forEach(moduleInfo=>{
depsGraph[moduleInfo.file] = {
deps:moduleInfo.deps,
code:moduleInfo.code
}
})
输出结果为:
文章图片
2.3 总结 目前 bundle.js 是这样的
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const babel = require('@babel/core')
// 获取单个模块依赖、内容
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,'utf-8')const ast = parser.parse(body,{
sourceType:'module'
});
const deps = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file);
const abspath = './' + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})const {code} = babel.transformFromAst(ast,null,{
presets:["@babel/preset-env"]
})const moduleInfo = {file,deps,code}
return moduleInfo;
}
// 递归获取所有模块依赖、内容并转换为文件的路径为key,{code,deps}为值的形式存储
const parseModules = (file) =>{
const entry =getModuleInfo(file)
const temp = [entry]
const depsGraph = {}for (let i = 0;
i{
depsGraph[moduleInfo.file] = {
deps:moduleInfo.deps,
code:moduleInfo.code
}
})return depsGraph}
// 从入口文件开始执行
parseModules('./src/index.js');
3 立即执行函数
经过以上步骤我们获取到了文件的路径,各个依赖,内容,将index.js 与它的依赖整合起来,当 require 调用依赖的时候,就执行依赖的内容,就实现了代码插入执行。
3.1 相关知识梳理 当 require 调用依赖的时候,就执行依赖的内容,这个地方需要用到立即执行函数,就是 eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。
eval(string)
其中,string 表示 JavaScript 表达式、语句或一系列语句的字符串。表达式可以包含变量与已存在对象的属性。返回字符串中代码的返回值。如果返回值为空,则返回 undefined。
3.2 实现 3.2.1 字符串转化 首先将获取到的 moudles 转化为字符串
const depsGraph = JSON.stringify(parseModules(file));
其中 depsGraph 以每个模块的路径为key,{code,deps}(code:当前模块转译后的内容;deps:当前模块依赖)为值的形式存储的对象字符串。
3.2.2 单个模块立即执行 我们来看一下 index.js 转译的结果,
index.js
"use strict"
var _add = _interopRequireDefault(require("./add.js"));
var _minus = require("./minus.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj };
}
var sum = (0, _add["default"])(1, 2);
var division = (0, _minus.minus)(2, 1);
console.log(sum);
console.log(division);
add.js
"use strict";
Object.defineProperty(exports, "__esModule", {value: true});
exports["default"] = void 0;
var _default = function _default(a, b) {return a + b;
};
exports["default"] = _default;
执行 index.js 的时候,这个代码浏览器没办法识别 require 方法,所以我们要在立即执行函数中重新写 require 方法,在执行 require 的时候,去找引入的模块具体内容,然后执行。
所以我们的思路就是
- 立即执行函数传入 depsGraph ;
- 执行 require 入口文件方法;
- 在 depsGraph 查找入口文件的内容;
- 立即执行内容;
(function (graph) {
function require(file) {
(function (code) {
eval(code)
})(graph[file].code)
}
require(file)
})(depsGraph)
其中,depsGraph 当前项目所有依赖、路径、转译后的内容;file 为当前入口文件路径; graph[file].code 为当前入口文件转译后的内容;
3.2.3 项目立即执行 【webpack|webpack打包原理浅浅析(前端打包工具)】在执行内容的时候又会遇到 require 函数,给的是相对路径,那我们如何获取绝对路径并且层层递进全部给执行了呢?
文章图片
上图是 depsGraph 当前 index.js 模块,相对路径可以从 depsGraph 变量中的当前模块的 deps 属性中所对应了当前模块的相对路径属性的属性值。
从而这里的流程是
- 立即执行函数传入 depsGraph ;
- 执行 require 入口文件方法;
- 在 depsGraph 查找入口文件的内容;
- 立即执行内容(执行eval(code));
- 当前模块内容(也就是执行eval(code)的时候)遇到 require(“XXX”),那么执行 absRequire ,传入模块名XXX;
- 在 depsGraph 中查找当前模块的绝对路径下的 deps 属性中所对应的相对路径为 XXX 属性的属性值,也就是 XXX 的绝对路径作为变量传入 require 函数;
- 再次执行 require 函数,回到步骤2,直到没有 require 函数;
(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
(function (require,code) {
eval(code)
})(absRequire,graph[file].code)
}
require(file)
})(depsGraph)
其实就是一个递归,结束条件为当前模块内容中没有 require 函数。
3.2.4 模块中的 exports 处理 在 add.js 中执行的时候又会遇到 exports 这个函数,这个还没有定义,我们看看怎么处理?
"use strict";
Object.defineProperty(exports, "__esModule", {value: true});
exports["default"] = void 0;
var _default = function _default(a, b) {return a + b;
};
exports["default"] = _default;
其中, Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。我们看到的
Object.defineProperty(exports, "__esModule", {value: true});
就是直接在 exports 对象上定义一个新属性,输出的 exports 为一个对象,也就是这个模块的内容。
exports = {
__esModule:{value: true},
default:function _default(a, b) {return a + b;
}
}
从 index.js 中看到
"use strict"
var _add = _interopRequireDefault(require("./add.js"));
var _minus = require("./minus.js");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj };
}
var sum = (0, _add["default"])(1, 2);
var division = (0, _minus.minus)(2, 1);
console.log(sum);
console.log(division);
interopRequireDefault 方法会将 exports 中的 default 这个属性给add,因此_add = function _default(a, b) { return a + b; }
在这里将 exports 返回就行,具体代码为
(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
var exports = {}
(function (require,exports,code) {
eval(code)
})(absRequire,exports,graph[file].code)
return exports
}
require(file)
})(depsGraph)
所以exports就是一个对象,ES6模块引入的是一个对象引用。
3.3 总结 当前 bundle.js 文件为
const getModuleInfo = (file)=>{
// 获取当前文件内容
const body = fs.readFileSync(file,'utf-8')
// 当前文件转换为AST
const ast = parser.parse(body,{
sourceType:'module'
});
// 获取当前文件依赖
const deps = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file);
const abspath = './' + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})
// 当前文件内容转码
const {code} = babel.transformFromAst(ast,null,{
presets:["@babel/preset-env"]
})const moduleInfo = {file,deps,code}return moduleInfo;
}
// 立即执行
const bundle = (file) =>{
const depsGraph = JSON.stringify(parseModules(file))
return `(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
var exports = {}
(function (require,exports,code) {
eval(code)
})(absRequire,exports,graph[file].code)
return exports
}
require('${file}')
})(${depsGraph})`
}const content = bundle('./src/index.js')
console.log(content);
打印结果为
文章图片
4 打包结果放入dist文件
fs.mkdirSync('./dist');
fs.writeFileSync('./dist/bundle.js',content)
执行
node bundle.js
生成的目录结构为
文章图片
其中 dist/bundle.js 文件内容为
(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
var exports = {}
(function (require,exports,code) {
eval(code)
})(absRequire,exports,graph[file].code)
return exports
}
require('./src/index.js')
})({"./src/index.js":{"deps":{"./add.js":"./src/add.js","./minus.js":"./src/minus.js"},"code":"\"use strict\";
\n\nvar _add = _interopRequireDefault(require(\"./add.js\"));
\n\nvar _minus = require(\"./minus.js\");
\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { \"default\": obj };
}\n\nvar sum = (0, _add[\"default\"])(1, 2);
\nvar division = (0, _minus.minus)(2, 1);
\nconsole.log(sum);
\nconsole.log(division);
"},"./src/add.js":{"deps":{},"code":"\"use strict\";
\n\nObject.defineProperty(exports, \"__esModule\", {\nvalue: true\n});
\nexports[\"default\"] = void 0;
\n\nvar _default = function _default(a, b) {\nreturn a + b;
\n};
\n\nexports[\"default\"] = _default;
"},"./src/minus.js":{"deps":{},"code":"\"use strict\";
\n\nObject.defineProperty(exports, \"__esModule\", {\nvalue: true\n});
\nexports.minus = void 0;
\n\nvar minus = function minus(a, b) {\nreturn a - b;
\n};
\n\nexports.minus = minus;
"}})
在 index.html 中引入 dist/bundle.js 文件,在浏览器中打开 index.html
可以看到输出了
文章图片
至此,整个打包流程就结束了。
5 总结
文章图片
所以我们重新梳理一下这个流程:
- 由模块内容利用AST抽象语法树获取模块的依赖、浏览器可识别的模块内容。
- 将模块路径、依赖、内容作为对象,放入全局的数组。
- 根据上一步得到的依赖进行遍历,再次进行步骤1,直到依赖为空。
- 将获取到的全局数组转换为下一步立即执行函数能用的对象格式,文件路径作为对象的属性,依赖和内容作为属性值输出的对象。
- 将步骤4拿到的对象传入立即执行函数。
- 在立即执行函数中重写 require 和 exports 方法。
- 当 require 模块的时候,立即执行模块内容并返回exports对象,实现模块动态插入。
以上就是这个小demo利用webpack思想打包的流程,众所周知webpack打包比这个demo远远复杂,所以继续fighting吧~
欢迎各位大佬指正~
参考:
https://webpack.docschina.org/
https://babeljs.io/docs/en/babel-parser
https://nodejs.org/api/fs.html
https://nodejs.org/api/path.html
https://juejin.im/post/6844903858179670030(实现一个简单的Webpack)
https://www.lagou.com/lgeduarticle/82247.html(掌握AST,再也不怕被问babel,vue编译,Prettier等原理)
https://juejin.im/post/6844903832061739015(AST 原理分析)
https://juejin.im/post/6844904007463337997#heading-4 (Webpack4打包机制原理简析)
https://juejin.im/post/6844903802382860296(Webpack 模块打包原理)
https://juejin.im/post/6854573217336541192 (手写webpack核心原理,再也不怕面试官问我webpack原理)
https://github.com/Pines-Cheng/blog/issues/45 (Webpack将代码打包成什么样子?)
推荐阅读
- webpack|webpack打包原理
- webpack|webpack5原理
- 前端|Vue.js环境搭建、安装Vue-cli脚手架、Visual studio code
- Ned的前端学习日记|Vue.js基础环境的搭建以及简单使用Element-ui
- vue3|Vue3学习之旅-Vue3组件化开发(三)-动态/异步组件-vue3生命周期-组件的v-model
- webpack|-4048webpack安装 没有文件文件夹或者目录的权限解决办法
- 前端面试|webpack
- 如何将React(Webpack)捆绑路径注入WordPress脚本功能
- webpack 打包(尚硅谷)