简介以下仅为个人粗略总结和代码,看不懂的稍加理解,本文主要用做个人记录。先大致总结一下1.从哪里开始:webpack根据入口模块开始。2.如何进行:递归读取每个文件,会形成一个依赖列表,依赖列表的,依赖列表是一个以文件相对路径为key,文件内容为value的对象。3.如何处理:对于每个文件会通过AST解析语法树,返回源码。4.loader在哪:loader是何时何地进行处理?它是在读取文件的时候开始起作用,通过正则匹配文件是否需要处理。然后读取配置文件的loader配置,然后通过递归的方式处理文件。5. Plugins呢:plugins是在合适的时机开始工作,那么这个合适时机如何控制呢,是通过tapable事件流机制,实现发布订阅模式。6.最后:最后返回的是一个匿名自执行函数,定义了一个webpack__require方法,解析传入的依赖列表,递归执行。然后看下代码核心代码如下://入口文件webpack.js#! /usr/bin/env node//第一步:找到当前执行命令的路径,拿到webpack.config.jslet path = require(“path”)let config = require(path.resolve(‘webpack.config.js’))console.log(path.resolve(),‘resolve—————>’)let Compiler = require(’../lib/Compiler’)let compiler = new Compiler(config)//标识运行编译compiler.run()//Compiler.jsconst fs = require(‘fs’)const path = require(‘path’)const babylon = require(‘babylon’)const travere = require(’@babel/traverse’).defaultconst t = require(’@babel/types’)const generator = require(’@babel/generator’).defaultconst ejs = require(’ejs’)const {SyncHook} = require(’tapable’)//babylon把源码转为AST//@babel/traverse//@babel/generator//@babel/typesclass Compiler { constructor(config) { this.config = config //保存入口文件路径 this.entryID = ’’ //主模块入口路径 this.modules = {} //存放模块依赖关系 this.entry = config.entry //入口路径 this.root = process.cwd() //当前工作目录 this.hooks = { entryOption: new SyncHook(), compile: new SyncHook(), afterCompile: new SyncHook(), afterPlugins: new SyncHook(), run: new SyncHook(), emit: new SyncHook(), done: new SyncHook(), } //如果传递了plugins参数 let plugins = this.config.plugins if(Array.isArray(plugins)) { plugins.forEach(plugin => { plugin.apply(this) }) } } run() { //执行,并创建模块的依赖关系 this.buildModule(path.resolve(this.root, this.entry), true) //发射一个文件,就是打包后的文件 this.emitFile() } //构建模块 buildModule(modulePath, isEntry) { //首先读取入口文件 let source = this.getSource(modulePath) //模块的ID = this.root - modulePath let moduleName = ‘./’ + path.relative(this.root, modulePath) if(isEntry) { this.entryID = moduleName //保存入口名字 } //解析,需要把source源码进行改造,返回一个依赖列表 let {sourceCde, dependencies } = this.parse(source, path.dirname(moduleName)) this.modules[moduleName] = sourceCde dependencies.forEach(dep => { //附模块的递归加载 this.buildModule(path.join(this.root, dep), false) }) } //解析源码, AST解析语法树 parse(source, parentPath) { // console.log(source, parentPath) let ast = babylon(source) let dependencies = [] //依赖数组 travere(ast, { CallExpression() { let node = p.node if(node.callee.name === ‘require’) { node.callee.name = ‘webpack_require’ let moduleName = node.arguments[0].value //这里就是引用模块的名字 moduleName = moduleName + (path.extname(moduleName) ? ’’ : ‘.js’) moduleName = ‘./’ + path.join(parentPath, moduleName) //‘src/a.js’ dependencies.push(moduleName) node.arguments = [t.stringLiteral(moduleName)] } } }) let sourceCode = generator(ast).code return {sourceCode, dependencies} } //公用读文件的方法 getSource(modulePath) { let content = fs.readFileSync(modulePath, ‘utf8’) let rules = this.config.module.rules //拿到规则 //拿到每个规则来处理 for(let i=0; i<rules.length; i++) { let rule = rules[i] let { test, use } = rule let len = use.length - 1 if(test.test(modulePath)) { //这个模块需要通过loader转换 function normalLoader() { let loader = require(use[len]) //获取对应loader函数 content = loader(content) //递归调用loader if(len >= 0) { normalLoader() } } normalLoader() } } return content } //发射文件 emitFile() { //用数据 渲染我们的 //拿到输出到哪个目录下 let main = path.join(this.config.output.path, this.config.output.filename) //模板路径 let templateStr = this.getSource(path.join(__dirname, ‘main.ejs’)) let code = ejs.render(templateStr, {entryId: this.entryID, modules: this.modules}) this.assets = { //资源中路径对应的代码 } this.assets[main] = code fs.writeFileSync(main, this.assets[main]) }}module.exports = Compiler