webpack之旅这么久,迎来了最终的一个章节,最近由于事情较多,所以最后一张稍稍来的比较晚,但既然是最后一节内容,那我们就来玩点有意思的: 手写一个简易的webpack打包,帮助大家更加深入的理解webpack的打包实现
- 首先我们先理清一下思路: webpack作为一个模块打包工具,它需要知道我们到底引入了哪些模块,我们的js代码中的高级语法的还原为低版本的es语法。那么,我们的思路是: 通过nodejs模块读取出js入口文件的内容,根据js文件的模块引入,递归的查询整个项目的文件依赖,让后将所有的相关的js文件内容读取出来,使用babel进行语法的解析,一个简易的webpack打包不就基本实现了吗? 现在我们就开始来编写我们的webpack吧:
// 引入node的文件模块
const fs = require("fs")
const path = require("path")
/**
* 使用@babel/parser可以对我们读取出的js代码进行分析,提取出js代码中的相关的依赖:
* @babel-parser下的parse()可以读取出相应依赖的一个抽象语法树(AST)
*/
const parser = require("@babel/parser")
/**
* 使用@babel/traverse模块可以对@babel/parser得到的抽象语法树进行分析
*/
const traverse = require("@babel/traverse").default
/**
* 使用babel的核心库处理js文件: @babel/core
*/
const babel = require("@babel/core")
/**
* 分析打包js的入口文件
* @param {string} filename 打包文件的入口地址
*/
function moduleAnalyser(filename){
// 读取出打包入口文件
const content = fs.readFileSync(filename, "utf-8")
// 分析打包文件
const ast = parser.parse(content, {
sourceType: "module"
})
// 提出出相关的依赖
const dependencies = {}
traverse(ast, {
ImportDeclaration({node}){
const dirname = path.dirname(filename)
const file = node.source.value.substring(1, node.source.value.length)
const newFile = dirname + file + ".js"
dependencies[node.source.value] = newFile
}
})
// 使用核心库处理代码:
const {code} = babel.transformFromAst(ast, null, {
presets: ["@babel/preset-env"]
})
return {
filename,
dependencies
code
}
}
/**
* 根据依赖关系分析依赖图谱
* @param {string} entry 打包入口文件名
**/
function makeDependenciesGraph(entry){
const entryModule = moduleAnalyser(entry)
const graphArray = [entryModule]
// 递归找出所有的依赖关系:
for(let i = 0; i < graphArray.length; i++){
const item = graphArray[i]
const {dependencies} = item
if(dependencies){
for(let j in dependencies){
graphArray.push(moduleAnalyser(dependencies[j]))
}
}
}
const graph = {}
graphArray.forEach(item => {
graph[item.filename] = {
dependencies: item.dependencies,
code: item.code
}
})
return graph
}
/**
* 根据依赖图整理生成完整的js打包代码
* @param {string} entry 打包入口文件名
*/
function generateCode(entry) {
const graph = makeDependenciesGraph(entry)
return `
(function(graph){
function require(module){
function localRequire(relativePath){
return require(graph[module].dependencies[relativePath])
}
var exports = {};
(function(require, exports, code){
eval(code);
})(localRequire, exports, graph[module].code)
return exports;
}
require('${entry}')
})(${graph})
`
}
/**
* 将打包处理好的code写入到文件中
* @param {string} dir 文件存放的目录名
* @param {string} filename 打包存放的文件名
* @param {string} entry 打包入口文件名
*/
function writeCodeToDir(dir, filename, entry){
const code = generateCode(entry)
fs.mkdir(dir, function(){
fs.writeFile(`./${dir}/${filename}`, code, function(){
console.log("代码打包成功!")
})
})
}
writeCodeToDir(build, "bundle.js", "./src/index.js")
为了大家能够更加直观的理解每个函数的意思,我将每个以上三个关键函数返回值打印的截图放在这里:
- 我的打包入口文件内容:

- moduleAnalyser函数返回值的形式如下:

- makeDependenciesGraph函数的返回值形式如下:

- generateCode 函数的返回值形式如下:

好了,webpack我们暂时就到这里了,如果大家对于webpack感兴趣也可以去官网深入学习更多的内容。大家 加油!!!!!!
来源:CSDN
作者:一树梨花
链接:https://blog.csdn.net/qq_44746132/article/details/104137499



