自定义webpack
webpack 执行流程
- 初始化 Compiler:new Webpack(config) 得到compiler对象
- 开始编译 调用Compiler的run方法开始执行编译
- 确定入口 根据配置中的entry找出所有的文件入口
- 编译模块 从入口文件出发,调用所有配置的loader对模块进行编译,再找出该模块依赖的模块,
递归直到所有模块被加载进来
- 完成模块编译 在经过第4步使用Loader编译完所有模块后,得到了每个模块编译后的最终内容
以及他们之间的依赖关系
- 输出资源 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk
转化成一个单独的文件加入到输出列表
- 输出完成 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
const fs = require('fs');
const path = require('path');
const babelParser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const { transformFromAst } = require('@babel/core');
function webpack(config) {
return new Compiler(config)
}
class Compiler {
constructor(options = {}) {
this.options = options;
this.modules = [];
}
run() {
const filePath = this.options.entry;
const fileInfo = this.build(filePath);
this.modules.push(fileInfo);
this.modules.forEach((fileInfo) => {
const deps = fileInfo.deps;
for (const relativePath in deps) {
if (deps.hasOwnProperty(relativePath)) {
const absolutePath = deps[relativePath];
const fileInfo = this.build(absolutePath);
this.modules.push(fileInfo);
}
}
})
const depsGraph = this.modules.reduce((graph, module) => {
return {
...graph,
[module.filePath]: {
code: module.code,
deps: module.deps,
}
}
}, {})
this.generate(depsGraph);
}
build(filePath) {
const file = fs.readFileSync(filePath, 'utf-8')
const ast = babelParser.parse(file, {
sourceType: 'module',
})
const dirname = path.dirname(filePath);
const deps = {};
traverse(ast, {
ImportDeclaration({ node }) {
const relativePath = node.source.value;
const absolutePath = path.resolve(dirname, relativePath);
deps[relativePath] = absolutePath;
}
})
const { code } = transformFromAst(ast, null, {
presets: ['@babel/preset-env']
})
return {
filePath,
deps,
code,
}
}
generate(depsGraph) {
const bunble = `
(function(depsGraph){
// require目的: 为了加载入口文件
function require(module){
// 定义模块内部的require函数
function localRequire(relativePath) {
// 为了找到引入模块的绝对路径, 通过require加载进来
return require(depsGraph[module].deps[relativePath])
}
// 定义暴露对象,将来我们模块要暴露的东西
var exports = {};
(function(require, exports, code){
eval(code);
})(localRequire, exports, depsGraph[module].code)
// 作为require函数的返回值返回出去 后面的require函数能得到暴露的内容
return exports;
}
require('${this.options.entry}')
}(${JSON.stringify(depsGraph)}))
`
const filePath = path.resolve(this.options.output.path, this.options.output.filename)
fs.writeFileSync(filePath, bunble, 'utf-8')
}
}
module.exports = webpack;