webpack之旅(最终篇): 深入理解webpack的打包实现

别来无恙 提交于 2020-02-01 23:09:15

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")

为了大家能够更加直观的理解每个函数的意思,我将每个以上三个关键函数返回值打印的截图放在这里:

  1. 我的打包入口文件内容:
    在这里插入图片描述
  2. moduleAnalyser函数返回值的形式如下:
    在这里插入图片描述
  3. makeDependenciesGraph函数的返回值形式如下:
    在这里插入图片描述
  4. generateCode 函数的返回值形式如下:
    在这里插入图片描述

好了,webpack我们暂时就到这里了,如果大家对于webpack感兴趣也可以去官网深入学习更多的内容。大家 加油!!!!!!

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!