1.通过入口文件查找依赖
2.遍历递归依赖
3.转换成代码
4.拼接成立即执行函数,输出
整体编译转换功能主要由Compiler对象实现,
Compiler主要结构如下
-
run():开始编译
-
buildModule():构建模块
-
emitFiles():输出文件
const {getAST,getDependencies,transform} = require("./parser") //引入工具函数
class Compiler{
//接收实例化时传入的webpack配置文件参赛
constructor(options){
const {entry,output} = options;
this.entry = entry; //入口
this.output = output; //出口
this.modules = []
}
//开启编译
run(){
//递归调用buildModule,将模块及其依赖都存入modules数组中
const entryModule = this.buildModule(this.entry,true);
this.modules.push(entryModule)
this.modules.map((_module)=>{
_module.dependencies.map((dependency)=>{
this.modules.push(this.buildModule(dependency));
})
})
//然后调用输出文件
this.emitFiles();
}
//构建模块相关
buildModule(filename,isEntry){
//将文件都转化成filename,dependencies,transformCode类型的对象
let ast;
if(isEntry){//如果是入口文件路径,就不用拼接路径,直接传入
ast = getAST(filename)
}else{
const absolutePath = path.join(process.cwd(),"./src",filename);
ast = getAST(absolutePath)
}
return {
filename,//文件名称
dependencies:getDependencies(ast),//依赖列表
transformCode:transform(ast),//转化后的代码
}
}
//输出文件
emitFiles(){
const outputPath = path.join(this.output.path,this.output.filename);
let modules = "";
//构建成文件路径,函数的形式
this.modules.map((_module)=>{
modules +=`'${_module.filename}':function(require,module,exports) {${_module.transformCode}},`
})
const bundle = `
(function(modules){
function require(fileName){
const fn = modules[fileName];
const module = {exports:{}};
fn(require,module,module.exports)
return module.exports
}
require('${this.entry}')
})({${modules}})`;
fs.writeFileSync(outputPath,bundle,"utf-8")
}
}
输出文件函数emitFiles这里看着一大坨,有点复杂,我们单独拿出来
这里先看一下webpack打包后的精简代码:
// dist/index.xxxx.js
(function(modules) {
// 已经加载过的模块
var installedModules = {};
// 模块加载函数
function __webpack_require__(moduleId) {
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
__webpack_require__(0); //启动函数
})([
/* 0 module */
(function(module, exports, __webpack_require__) {
...
}),
/* 1 module */
(function(module, exports, __webpack_require__) {
...
}),
/* n module */
(function(module, exports, __webpack_require__) {
...
})]);
webpack会把一个个模块,都构建成函数
function(module, exports, webpack_require){模块编译后的代码}
这样的形式,然后存入一个数组,然后整体就是一个立即执行函数,将这个数组作为参数。
函数内部定义了一个对象installedModules,用数组下标作为key,如果对象中已经存在该模块,则返回该模块内容,否则调用该函数,返回内容
emitFile函数也是实现了和上面类似的功能:
emitFiles(){
const outputPath = path.join(this.output.path,this.output.filename);
let modules = "";
//构建成{文件路径:函数}的形式
this.modules.map((_module)=>{
modules +=`'${_module.filename}':function(require,module,exports) {${_module.transformCode}},`
})
const bundle = `
(function(modules){
function require(fileName){
const fn = modules[fileName];
const module = {exports:{}};
fn(require,module,module.exports)
return module.exports
}
require('${this.entry}') //类似于上面的 __webpack_require__(0),启动函数
})({${modules}})`;
fs.writeFileSync(outputPath,bundle,"utf-8")
}
Compiler中导入了3个工具函数,还需要我们实现一下
- getAST:将模块转换成ast树(用@babel/parser的parse去转换)
- getDependencies:收集模块中的依赖(用@babel/traverse的traverse去转换)
- transform:将模块中的es6代码转换成es5(用babel-core的transformFromAst去转换)
具体实现如下:
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const {transformFromAst} = require("babel-core");
getAST:(path) =>{
const source = fs.readFileSync(path,"utf-8")
return parser.parse(source,{
sourceType:"module", //表示解析的是es模块
})
},
//对AST节点进行递归遍历
getDependencies:(ast)=>{
const dependencies = [];
traverse(ast,{
ImportDeclaration:({node})=>{
dependencies.push(node.source.value);
}
})
return dependencies
},
//将获得的es6的ast转化成es5
transform:(ast)=>{
const {code} = transformFromAst(ast,null,{
presets:["env"],
});
return code;
}
以上就实现了Compiler对象
然后我们将其 new 一下,导入webpack配置项文件,就像这样
const Compiler = require('./compiler')
const options = require("../forestpack.config") //webpakc配置项文件
new Compiler(options).run()
const path = require("path")
module.exports ={
entry:path.join(__dirname,"./src/index.js"),
output:{
path:path.join(__dirname,"./dist"),
filename:"bundle.js",
}
}
然后运行命令 node lib/index.js,就能在dist文件夹看到与webpack大差不差的bundle了,然后导入index.html运行,就能看到最终效果。