webpack 执行流程 手写webapck

1.初始化 Compiler:new Webpack(config) 得到 Compiler对象

2.开始编译:调用Compiler对象 run方法开始执行编译

3.确定入口:根据配置中entry找出所有的入口文件

4.编译模块:从入口文件出发,调用所有配置的Loader 对模块进行编译,再找出该模块依赖的模块,递归直到所有模块被加载进来

5.完成模块编译:在经过4步使用Loader 编译完所有模块后,得到了每个模块被编译后的最终内容以及他们之间的依赖关系。

6.输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表。(注意:这步是可以修改输出内容后的最后机会)

7.输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

//parse.js 文件
const fs=require('fs')
const path=require('path')
const babelParser=require('@babel/parser')
const traverse=require('@babel/traverse').default;
const {transformFromAst}=require('@babel/core')
const parse={
    getAst(filePath){
        const file=fs.readFileSync(filePath,'utf-8')
        // 2. 将其解析成ast抽象语法树
        const ast=babelParser.parse(file,{
            sourceType:'module'// 解析文件的模块化方案是 ES Module
        })
        return ast;
    },
    getDeps(ast,filePath){
         // 获取到文件所处文件夹路径
         const dirname=path.dirname(filePath)
        // 定义存储依赖的容器
        const deps={}
        // 收集依赖
        traverse(ast,{
            // 内部会遍历ast中program.body 判断里面语句类型
            // 如果type ImportDeclartion 就会触发当前函数
            ImportDeclaration({node}){
                //文件相对路径 './add.js'
                const relativePath=node.source.value;
                // 生成基于入口文件的绝对路径
                const absolutePath=path.resolve(dirname,relativePath);
                // 添加依赖
                deps[relativePath]=absolutePath;
            }
        })
        return deps;
    },
    getCode(ast){
        // 编译代码:将代码中浏览器不能识别的语法进行编译
        const {code}=transformFromAst(ast,null,{
            presets:['@babel/preset-env']
        })
        return code
    }
}

 
module.exports=parse;

// Compiler.js 文件
const fs=require('fs');
const path=require('path')
const {getAst,getDeps,getCode}=require('./parse')
class Compiler{
    constructor(options){
        this.options=options
        this.modules=[];
    }
    //启动webpack打包
    run(){
        // 入口文件路径
        const filePath=this.options.entry;
        // 第一次构建,得到入口文件的信息
        const fileInfo=this.build(filePath)
        // console.log('fileInfo',fileInfo)
        this.modules.push(fileInfo)
        // 遍历所有的依赖
        this.modules.forEach(fileInfo=>{
            // 取出当前文件的所有依赖
            const deps=fileInfo.deps;
            // 遍历
            for(const relativePath in deps){
                // 依赖文件的绝对路径
                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){

        //  1.将文件解析成ast
        const ast=getAst(filePath)
        // 2. 获取ast中所有的依赖
        const deps=getDeps(ast,filePath)
        // 3.将ast解析成code
        const code=getCode(ast)
        return {
            // 当前文件路径
            filePath,
            // 当前文件的所有依赖
            deps,
            //  当前文件解析后的代码
            code
        }
    }
    generate(depsGraph){
        const build=`
            (function(depsGraph){
                // require 目的:为了加载入口文件
                function require(module){
                    // 定义模块内部的requre函数
                    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,build,'utf-8')
    }
} 
module.exports=Compiler;



//myWebpack.js 文件
const Compiler=require('./Compiler')
function myWebpack(config){
    return new Compiler(config)
}
module.exports=myWebpack;

你可能感兴趣的:(webpack 执行流程 手写webapck)