Compilation类:
Compilation描述一个编译过程
文件结构:
./src/entry1.js中依赖了./title1
./src/entry2.js中依赖了./title2
entry的结构:
{
entry1:'./src/entry1.js',
entry2:'./src/entry2.js'
}
一个entry对应一个chunk,每个chunk的结构如下:
let chunk = {
name: entryName,
entryModule,
modules
}
每个module的结构如下:
let module = {
id: './src/entry1.js',
dependencies: [
{
depModuleId: './src/title1.js',
depModulePath: 'C:/aproject/zhufengwebpack202106/4.flow/src/title1'
}
],
name: 'entry1',
extraNames: []
}
入口模块(entryModule):
let entryModule = {
id: './src/entry1.js',
dependencies: [
{
depModuleId: './src/title1.js',
depModulePath: 'C:/aproject/zhufengwebpack202106/4.flow/src/title1'
}
],
name: 'entry1',
extraNames: []
}
非入口模块,或叫依赖模块(depModule):
注:
name用来标识当前模块属于哪个入口模块
extraNames用来标识哪些入口模块依赖了该模块
let depModule = {
id: './src/title.js',
dependencies: [],
name: 'entry1',
extraNames: ['entry2']
}
const path = require('path');
const fs = require('fs');
const types = require('babel-types');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const baseDir = toUnitPath(process.cwd());//\
function toUnitPath(filePath) {
return filePath.replace(/\/g, '/');
}
class Complication {
constructor(options) {
this.options = options;
//webpack4 数组 webpack5 set
this.entries = [];//存放所有的入口
this.modules = [];// 存放所有的模块
this.chunks = [];//存放所的代码块
this.assets = {};//所有产出的资源
this.files = [];//所有产出的文件
}
build(callback) {
//5. 根据配置中的entry找出入口文件
let entry = {};
if (typeof this.options.entry === 'string') {
entry.main = this.options.entry;
} else {
entry = this.options.entry;
}
//entry={entry1:'./src/entry1.js',entry2:'./src/entry2.js'}
for (let entryName in entry) {
//5.获取 entry1的绝对路径
let entryFilePath = toUnitPath(path.join(this.options.context, entry[entryName]));
//6.从入口文件出发,调用所有配置的Loader对模块进行编译
let entryModule = this.buildModule(entryName, entryFilePath);
//this.modules.push(entryModule);
//8. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
let chunk = {
name: entryName, entryModule, modules: this.modules.filter(item => {
return item.name === entryName || item.extraNames.includes(entryName);
})
};
this.entries.push(chunk);
this.chunks.push(chunk);
}
//9. 再把每个Chunk转换成一个单独的文件加入到输出列表
this.chunks.forEach(chunk => {
let filename = this.options.output.filename.replace('[name]', chunk.name);
// this.assets就是输出列表 key输出的文件名 值就是输出的内容
this.assets[filename] = getSource(chunk);
});
callback(null, {
entries: this.entries,
chunks: this.chunks,
modules: this.modules,
files: this.files,
assets: this.assets
});
}
//name=名称,modulePath=模块的绝对路径
buildModule(name, modulePath) {
//6. 从入口文件出发,调用所有配置的Loader对模块进行编译
//1.读取模块文件的内容
let sourceCode = fs.readFileSync(modulePath, 'utf8');//console.log('entry1');
let rules = this.options.module.rules;
let loaders = [];///寻找匹配的loader
for (let i = 0; i < rules.length; i++) {
let { test } = rules[i];
//如果此rule的正则和模块的路径匹配的话
if (modulePath.match(test)) {
loaders = [...loaders, ...rules[i].use];
}
}
sourceCode = loaders.reduceRight((sourceCode, loader) => {
return require(loader)(sourceCode);
}, sourceCode);
/* for(let i=loaders.length-1;i>=0;i--){
let loader = loaders[i];
sourceCode = require(loader)(sourceCode);
} */
//console.log('entry1');//2//1
//console.log(sourceCode);
//7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
//获得当前模块模块ID ./src/index.js
let moduleId = './' + path.posix.relative(baseDir, modulePath);
let module = { id: moduleId, dependencies: [], name, extraNames: [] };
let ast = parser.parse(sourceCode, { sourceType: 'module' });
traverse(ast, {
CallExpression: ({ node }) => {
if (node.callee.name === 'require') {
//依赖的模块的相对路径
let moduleName = node.arguments[0].value;//./title1
//获取当前模块的所有的目录
let dirname = path.posix.dirname(modulePath);// /
//C:/aproject/zhufengwebpack202106/4.flow/src/title1
let depModulePath = path.posix.join(dirname, moduleName);
let extensions = this.options.resolve.extensions;
depModulePath = tryExtensions(depModulePath, extensions);//已经包含了拓展名了
//得到依赖的模块ID C:/aproject/zhufengwebpack202106/4.flow/src/title1.js
//相对于项目根目录 的相对路径 ./src/title1.js
let depModuleId = './' + path.posix.relative(baseDir, depModulePath);
//require('./title1');=>require('./src/title1.js');
node.arguments = [types.stringLiteral(depModuleId)];
//依赖的模块绝对路径放到当前的模块的依赖数组里
module.dependencies.push({ depModuleId, depModulePath });
}
}
});
let { code } = generator(ast);
module._source = code;//模块源代码指向语法树转换后的新生成的源代码
//7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
module.dependencies.forEach(({ depModuleId, depModulePath }) => {
let depModule = this.modules.find(item => item.id === depModuleId);
if (depModule) {
depModule.extraNames.push(name);
} else {
let dependencyModule = this.buildModule(name, depModulePath);
this.modules.push(dependencyModule);
}
});
return module;
}
}
function getSource(chunk) {
return `
(() => {
var modules = ({
${chunk.modules.map(module => `
"${module.id}":(module,exports,require)=>{
${module._source}
}
`).join(',')
}
});
var cache = {};
function require(moduleId) {
var cachedModule = cache[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = cache[moduleId] = {
exports: {}
};
modules[moduleId](module, module.exports, require);
return module.exports;
}
var exports = {};
(() => {
${chunk.entryModule._source}
})();
})()
;
`
}
function tryExtensions(modulePath, extensions) {
extensions.unshift('');
for (let i = 0; i < extensions.length; i++) {
let filePath = modulePath + extensions[i];// ./title.js
if (fs.existsSync(filePath)) {
return filePath;
}
}
throw new Error(`Module not found`);
}
module.exports = Complication;