自定义webpack

85 阅读2分钟

自定义webpack

webpack 执行流程

  1. 初始化 Compiler:new Webpack(config) 得到compiler对象
  2. 开始编译 调用Compiler的run方法开始执行编译
  3. 确定入口 根据配置中的entry找出所有的文件入口
  4. 编译模块 从入口文件出发,调用所有配置的loader对模块进行编译,再找出该模块依赖的模块, 递归直到所有模块被加载进来
  5. 完成模块编译 在经过第4步使用Loader编译完所有模块后,得到了每个模块编译后的最终内容 以及他们之间的依赖关系
  6. 输出资源 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的chunk,再把每个chunk 转化成一个单独的文件加入到输出列表
  7. 输出完成 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
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 = [];
    }
    // 启动webpack打包
    run() {
        // 1. 读取入口文件内容
        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);
                    // 处理后结果添加到modules中
                    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')
        // 2. 将其解析成ast抽象语法树
        const ast = babelParser.parse(file, {
            sourceType: 'module', // 解析文件的模块化方案是 ES Module
        })
        // 获取到文件文件夹路径
        const dirname = path.dirname(filePath);
        // 定义一个存储依赖的容器
        const deps = {};
        // 收集依赖 遍历ast.program.body,判读里面语句类型,如果
        // type = ImportDeclaration 就会触发当前函数
        traverse(ast, {
            ImportDeclaration({ node }) {
                // 文件相对路径 ./add.js
                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;