webpack 打包 bundle 原理分析
const compier = webpack(options)
compier.run()
通过 webpack 生命周期概率,可以知道打包大致过程
- 读取到配置的入口
- 入口模板的路径
- 模块分析
- 模块依赖(依赖的路径)
- 可以用递归的方式处理依赖模块
- 内容处理(处理后的内容代码)
- 依赖图谱对象
- 模块依赖(依赖的路径)
- webpackBootstrap 入口函数
- 读取到配置的出口
- 生产文件
- 文件存放的位置
- 生产文件
根据以上的分析,编写一个自定义 webpack 打包文件
编写自定义 webpack
打包项目
新增 webpack.config.js
, 跟 webpack
默认配置一样
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'), // 输出的文件目录,必须是绝对路径
filename: 'main.js' // 输出的文件名称
},
mode: 'development'
}
在此之前,先安装 @babel 库
npm i -D @babel/parser @babel/traverse @babel/core @babel/preset-env
@babel/parser
来支持解析内容转ast
树方便解析
@babel/traverse
来可以过滤ast
树
@babel/core
和@babel/preset-env
将ast
转为js
Ast
抽象语法书:以 node 节点为单位的 tree 树结构
新建 /lib/webpack.js
, 打包的核心文件
const fs = require('fs')
const path = require('path')
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const {transformFromAst} = require('@babel/core')
module.exports = class webpack {
constructor(options) {
// console.log(options)
const {entry, output} = options
this.entry = entry
this.output = output
this.modules = []
}
run() {
const info = this.parse(this.entry)
// 输出依赖图谱
this.modules.push(info)
for (let i = 0; i < this.modules.length; i++) {
const item = this.modules[i]
const {yilai} = item
for (let j in yilai) {
this.modules.push(this.parse(yilai[j]))
}
}
// 数据结构转换 arr->obj
const obj = {}
this.modules.forEach(item => {
obj[item.entryFile] = item
})
this.file(obj)
}
parse(entryFile) {
// 读取入口文件
const content = fs.readFileSync(entryFile, 'utf-8')
// 解析内容为 ast 树
const ast = parser.parse(content, {sourceType: 'module'})
// 过滤 ast 树
const dirname = path.dirname(entryFile)
//分析依赖 以及依赖路径
// 内容处理
const yilai = {}
traverse(ast, {
ImportDeclaration({node}) {
const newPathName = './' + path.join(dirname, node.source.value)
yilai[node.source.value] = newPathName
}
})
// ast 转 js
const {code} = transformFromAst(ast, null, {
presets: ['@babel/preset-env']
})
return {
entryFile,
yilai,
code
}
}
file(code) {
// 生产文件,放入到 dist 的目录
const filePath = path.join(this.output.path, this.output.filename)
// 生产 bundle 文件内容
const newCode = JSON.stringify(code)
const bundle = `(function (modules) {
function require(module) {
function pathRequire(relaPath) {
// relaPath: ./a.js -> ./src/a.js
return require(modules[module].yilai[relaPath])
}
const exports = {};
(function (require,exports, code) {
eval(code)
})(pathRequire,exports, modules[module].code)
return exports
}
require('${this.entry}')
})(${newCode})`
// webpackBootstrap
fs.writeFileSync(filePath, bundle, 'utf-8')
}
}
以下测试代码,新建 src/b.js
export const strb = 'b.js'
新建 src/a.js
import {strb} from "./b.js";
export const stra = 'a.js' + strb
新建入口文件 src/index.js
import {stra} from './a.js'
console.log(stra + 'index.js')
最后新建打包文件 bundle.js
const webapck = require('./lib/webpack')
const options = require('./webpack.config')
new webapck(options).run();
执行 node bundle.js
, 即可生产 ./dist/main.js
(function (modules) {
function require(module) {
function pathRequire(relaPath) {
// relaPath: ./a.js -> ./src/a.js
return require(modules[module].yilai[relaPath])
}
const exports = {};
(function (require,exports, code) {
eval(code)
})(pathRequire,exports, modules[module].code)
return exports
}
require('./src/index.js')
})({"./src/index.js":{"entryFile":"./src/index.js","yilai":{"./a.js":"./src\\a.js"},"code":"\"use strict\";\n\nvar _a = require(\"./a.js\");\n\nconsole.log(_a.stra + 'index.js');"},"./src\\a.js":{"entryFile":"./src\\a.js","yilai":{"./b.js":"./src\\b.js"},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.stra = void 0;\n\nvar _b = require(\"./b.js\");\n\nvar stra = 'a.js' + _b.strb;\nexports.stra = stra;"},"./src\\b.js":{"entryFile":"./src\\b.js","yilai":{},"code":"\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.strb = void 0;\nvar strb = 'b.js';\nexports.strb = strb;"}})
运行以上代码结 果
a.jsb.jsindex.js