webpack打包原理

一、webpack构建流程

webpack的运行流程是一个串行的过程,从启动到结束一次执行以下流程:
1、初始化参数:从配置文件和shell语句中读取与合并参数,得出最终的参数。
2、开始编译:用上一步得到的参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始执行编译。
3、确定入口:根据配置中的entry找出所有的入口文件。
4、编译模块:从入口文件出发,调用所有配置的loader对模块进行翻译,再找出该模块依赖的模块,在递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
5、完成模块编译:在经过第4步使用loader翻译完所有模块后,得到了每个模块翻译后的最终内容以及他们之前的依赖关系。
6、输出资源:根据入口和模块之间的依赖关系,,组装成一个个包含多个模块的chunk,再把每个chunk转成一个单独的文件加入到输出列表中,这步是可以修改输出内容的最后机会。
7、输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件名写入到文件系统。

在以上过程中,webpack会在特定的时间点广播出特定事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用webpack提供的API改变webpack的运行结果

二、简易 webpack

1、定义Compiler类

	class Complier {
		constructor(options) {
			// webpack 配置
			const  { entry, output } = options;
			// 入口
			this.entry = entry;
			// 出口
			this.output = output;
			// 模块
			this.modules = [];
		}
		//  构建启动
		run() {};
		// 重写require函数,输出bundle
		generate() {};  
	}

2.解析入口文件,获取AST

这里我们使用@babel/parse,这是babel7的工具,来帮助我们分析内部语法,包括es6,返回一个AST抽象语法树。

// webpack.config.js  
const path = require('path');
module.export = {
	entry: './src/index.js',
	output: {
		path: path.resolve(__dirname,'./dist'),
		filename: 'main.js' 	
	}
}

获取AST

const fs = require('fs');
const parser = require('@babel/parser');
const options = require('./webpack.config');
const Parser = {
	getAst: path => {
		// 读取入口文件
		const content = fs.readFileSync(path,'utf-8');
		// 将文件内容转成AST抽象语法树
		return parser.parse(content, {
				sourceType: 'module'
		})
	}
}
class Compiler {
	constructor(options) {
		// webpack 配置
		const { entry, output } = options;
		// 入口
		this.entry = entry;
		// 出口
		this.output = output;
		// 模块
		this.modules = [];
	}
	// 构建启动
	run() {
		const ast  = Parser.getAst(this.entry);
	}
	//  重写 require函数,输出bundle
	generate() {}
}
new Compiler(options).run()

3、找出所有依赖模块

Babel提供了@babel/traverse(遍历)方法维护这个AST 树的整体状态,这里使用它来找出依赖模块

const fs = require('fs');
const path = require('path');
const options = require('./webpack.config');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const Parser = {
	getAst:path => {
		// 读取入口文件
		const content = fs.readFileSync(path, 'utf-8');
		// 将文件内容转为AST抽象语法树
		return parser.parse(content,{
			sourceType: 'module'	
		})
	},
	getDependecies: (ast, filename) => {
		const dependecies = {};
		// 遍历所有import 模块,存入dependecies
		traverse(ast, {
			// 类型为 ImportDeclaration 的 AST 节点 (即为 import  语句)
			ImportDeclaration({ node }){
					const dirname = path.dirname(filename);
					// 保存依赖模块路径,之后生成依赖关系图需要用到
					const filepath = './' +path.join(dirname,node.source.value);
					dependecies[node.source.value] = filepath;
			} 
		})
		return dependecies;
	}
}
class Compiler {
	constructor(options){
		// webpack 配置
		const { entry, output } = options
		// 入口
		this.entry = entry;
		// 出口
		this.output = output;
		// 模块
		this.modules = [];
	}
	// 构建启动
	run() {
		const { getAst, getDependecies } = Parser;
		const ast = getAst(this.entry);
		const dependecies = getDependecies(ast, this.entry);
	}
	// 重写require函数,输出bundle
	generate() {};
}
new Compiler(options).run();

4、AST转code

将AST语法树转换为浏览器可执行代码,我们这里使用@babel/core 和 @babel/preset-env。

const fs = require('fs');
const path = require('path');
const options = require('./webpack.config');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const { transformFromAst } = require('@babel/core');
const Parser = {
	getAst : path => {
		// 读取入口文件
		const content = fs.readFileSync(path,'utf-8');
		// 将文件内容转成AST抽象语法树
		return parser.parse(content,{
			sourceType: 'module'
		})
	},
	getDependecies: (ast, filename) => {
		const dependecies = {};
		// 遍历所有的 import 模块,存入dependecies
		traverse(ast, {
			// 类型为 ImportDeclaration 的AST节点(即为import 语句)
			ImportDeclaration({ node }){
					// 保存依赖模块路径,之后生成依赖
					const dirname = './' + path.join(dirname, node.source.value);
					dependecies[node.source.value] = filepath;
			}	
		})
		return dependecies;
	},
	getCode: ast = > {
		// AST 转为 code
		const { code } = transfornFromAst(ast, null,{
			preset: ['@babel/preset-env']	
		})	
		return code;
	}
}
class Compiler {
	constructor(options){
		// webpack配置
		const { entry, output } = options;
		// 入口
		this.entry = entry;
		// 出口
		this.output = output;
		// 模块
		this.modules = [];
	}	
	// 构建启动
	run(){
		const { getAst, getDependecies, getCode } = Parser;
		const ast = getAst(this.entry);
		const dependecies = getDependecies(ast, this.entry);
		const code = getCode(ast);
	}
	// 重写 require 函数,输出bundle
	generate();
}
new Compiler(options).run();

5、递归解析所有依赖项,生成依赖关系图

const fs = require('fs');
const path = require('path');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const { transformFromAst } =  require('@babel/core');
const Parser = {
	getAst: path =>{
		// 读取入口文件
		const content = fs.readFileSync(path, 'utf-8');
		// 将文件内容转为AST抽象语法树
		return parser.parse(content,{
			sourceType: 'module'	
		})
	},
	getDependecies: (ast, filename) => {
		const dependecies = {};
		// 遍历所有 import 模块, 存入dependecies
		traverse(ast,{
			// 类型为ImportDeclaration 的 AST 节点(即为 import 语句)
			ImportDeclaration({ node }){
				const dirname = path.dirname(filename);
				// 保存依赖模块路径,之后生成依赖关系图需要用到
				const filepath = './' + path.join(dirname, node.source.value);
				dependecies[node.source.value] = filepath'
			}
		})
		return dependecies;
	},
	getCode: ast => {
		// ast  转换为 code
		const { code } = transformFromAdt(ast, null, {
			preset: ['@babel/preset-env']	
		})	
		return code;
	}
}
class Compiler {
	construnctor(options){
		// webpack 配置
		const { entry, output } = options;
		// 入口
		this.entry = entry;
		// 出口
		this.output = output;
		// 模块
		this.modules = [];
	}
	// 构建启动
	run(){
		// 解析入口文件
		const info = this.build(this.entry);
		this.modules.push(info);
		this.modules.forEach(({ dependecies }) => {
				// 判断有依赖对象,递归解析所有依赖项
				if(dependecies){
					for (const dependency in dependecies) {
						this.modules.push(this.build(dependecies[dependency]));	
					}
				}
		})
		// 生成依赖关系图
		const dependencyGraph = this.modules.reduce(
		(graph, item) =>	({
		...graph,
		// 使用文件路径作为每个模块的唯一标识,保存对对应模块的依赖对象和文件内容
		[item.filename]: {
			dependecies: item.dependecies,
			code: item.code	
		}	
		}),
		{}
		)
	}
	build(filename){
		const { getAst } = Parser;
		const ast = getAst(filename);
		const dependecies = getDependecies(ast,filename);
		const code = getCode(ast);
		return {
			// 文件路径,可以作为每个模块的唯一标识符
			filename,
			// 依赖对象,保存着依赖模块路径
			dependecies,
			// 文件内容
			code	
		}
	}
	// 重写require函数,输出bundle
	generate() {}
}
new Compiler(options).run();

6、重写require函数,输出bundle

const fs = require('fs');
const path = require('path');
const options = require('./webpack.config');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const { transformFromAst } = require('@babel/core');

const Parser = {
	getAst: path => {
		// 读取文件
		const content = fs.readFileSync(path, 'utf-8');	
		// 将文件内容转为AST抽象语法树
		return parser.parse(content,  {
			sourceType: 'module'
		})
	},
	getDependecies: (ast, filename) => {
			const dependecies = {};
			// 遍历所有的 import 模块,存入dependecies
			traverse(ast, {
					// 类型为ImportDeclaration de AST节点(即为 import 语句)
					ImportDeclaration({ node }) {
						const dirname = path.dirname(filename);
						// 保存依赖模块路径,之后生成依赖关系图需要用到
						const filepath = './' + path.join(dirname, node.source.value);
						dependecies[node.source.value] = filepath;
					}
			})
			return dependecies;
	},
	getCode: ast =>{
		// AST转为code
		const { code } = transformFromAst(ast, null, {
			presets: ['@babel/preset-env']
		})	
		return code;
	}	
}
class Compiler {
	constructor(options) {
			// webpack 配置
			const { entry, output } = options;
			// 入口
			this.entry = entry;
			// 出口
			this.output = output;
			// 模块
			this.modules = [];
	}	
	// 构建启动
	run() {
		// 解析入口文件
		const info = this.build(this.entry);
		this.modules.push(info);
		this.modules.forEach(( { dependecies} ) => {
				// 判断有依赖对象,递归解析所有依赖项
				if(dependecies){
					for(dependency in dependecies){
						this.modules.push(this.build(dependecies[dependency]));
					}	
				}
		})
		// 生成依赖关系图
		const dependencyGraph = this.modules.reduce(
			(graph, item) => ({
				...graph,
				// 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容
				[item.filename]: {
					dependecies: item.dependecies,
					code: item.code	
				}
			}),
			{}
		)
	}
	build(filename){
			const { getAst, getDependecies, getCode } = Parser;
			const ast = getAst(filename);
			const dependecies = getDependecies(ast, filename);
			const code = getCode(ast);
			return {
				// 文件路径,可以作为每个模块的唯一标识
				filename,
				// 依赖对象,保存着依赖模块的路径
				dependecies,
				// 文件内容
				code	
			}
	}
	// 重写 require函数 (浏览器不能识别commonjs语法),输出bundle
	generate(code){
		// 输出文件路径
		const filepath = path.join(this.output.path, this.output.filename);
		const bundle = `(function(graph){
			function require(module){
				function localRequire(relativePath){
					return require(graph[module].dependecies[relativePath]);
				}
				var exports = {};
				(function(require,exports,code){
					eval(code)	
				})(localRequire,exports,graph[module].code);
				return exports;
			}
			require('${this.entry}');
		})(${JSON.stringify(code)})`
		// 把文件内容写入到文件系统
		fs.writeFileSync(filePath,bundle,'utf-8');
	}
}
new Compiler(options).run();

七、bundle实现示例

;(function(graph) {
  function require(moduleId) {
    function localRequire(relativePath) {
      return require(graph[moduleId].dependecies[relativePath])
    }
    var exports = {}
    ;(function(require, exports, code) {
      eval(code)
    })(localRequire, exports, graph[moduleId].code)
    return exports
  }
  require('./src/index.js')
})({
  './src/index.js': {
    dependecies: { './hello.js': './src/hello.js' },
    code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
  },
  './src/hello.js': {
    dependecies: {},
    code:
      '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n  value: true\n});\nexports.say = say;\n\nfunction say(name) {\n  return "hello ".concat(name);\n}'
  }
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值