一、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}'
}
})