webpack打包原理

140 阅读3分钟
  • 原文链接 juejin.cn/post/684490…

  • 首先我们要了解webpack的构建流程 webpack的运行流程是一个串行的过程,从启动到结束会依次执行以下流程

1: 初始化参数:从配置文件和shell语句中读取和合并参数,并得到最终的参数

2: 开始编译:拿到上面的参数生成一个Compiler对象,先加载所有的配置插件,然后执 行run方法,最后开始编译

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

4: 编译模块:从入口文件出现,调用所有配置的loader对模块进行编译,然后找到模块所依赖的模块,在递归出本步骤直到所有入口依赖文件都经过编译

5: 完成模块的编译:根据第四步的步骤使用loader编译完所有模块后,得到每个模块被编译后的最终内容和所有的依赖关系

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

7: 输出完成:确定好输出内容过后,根据所配置的输出路径和输出文件名,将文件内容输出到文件系统当中

  • webpack的基本配置 1: entry: 入口文件
    2: output: 输出文件
    3: Module: 依赖模块
    4: Chunk: 有多个模块组成的文件,用于代码的合并和分割
    5: loader: loader去处理那些非js文件的,loader可以将所有类型的文件转换为 webpack 能够处理的有效模块
    6:Plugin:loader被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务,插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
  • 手写一个小型webpack
    
class Compiler {

    constructor(options) {
        const { entry, output } = options

        this.entry = entry
        this.output = output
        this.modules = []
    }
    run() {
        // 解析入口文件
        const info = this.build(this.entry)
        this.modules.push(info)
        this.modules.forEach(({ dependecies }) => {

            if (dependecies) {
                for (const dependency in dependecies) {

                    this.modules.push(this.build(dependecies[dependency]))
                }
            }
        });
        // 生成依赖关系图
        const dependencyGraph = this.modules.reduce(
            (graph, item) => ({
                ...graph,
                [item.filename]: {
                    dependecies: item.dependecies,
                    code: item.code
                }
            }),
            {}
        ),

    }
    build(filename) {
        // const 
        const ast = parser.getAST(this.entry)

        const dependecies = parser.getDependecies(ast, this.entry)

        const code = parser.getCode(ast)
        return {
            filename,
            dependecies,
            code
        }
    }

    generate() {

        const filePath = path.join(this.output.path, this.output.filename)

        const bundle = `(function(graph){
            function require(module){
                function localRequire(relativePath){
                    return require(graph[module].dependecies[relativePath])
                }

                var exports={}

                (function(require,exports,code){
                    eval(code)
                })(localRequire,exports,graph[module].code);

                return exports
            }
            require("${this.entry}")
        }
            
            )(${JSON.stringify(code)})`

        fs.writeFileSync(filePath, bundle, "utf-8")
    }

}

new Compiler(options).run()

const path = require("path")

module.exports = {

    entry: "./src/index.js",
    output: {
        path: path.resolve(__dirname, "./dist"),
        filename: "main.js"
    }

}


const fs = require("fs")

const parser = require("@babel/parser")  //将入口的文件转换成AST语法树
const options = require("./webpack.config")
// const { parse } = require("path/posix")

const traverse = require("@babel/traverse").default
// const { parse } = require("path/posix")

const { transformFromAst } = require("@babel/core")


const parser = {

    getAST: (path) => {
        // 读取入口文件

        const content = fs.readFileSync(path, "utf-8")

        return parser.parse(content, {
            sourceType: "module"
        })
    },
    getDependecies: (ast, filename) => {

        const dependecies = {}

        traverse(ast, {

            //类型为ImportDeclaration的AST节点

            ImportDeclaration({ node }) {

                const dirname = path.dirname(filename)

                const filepath = "./" + path.join(dirname, node.source.value)

                dependecies[node.source.value] = filepath


            }



        })
        return dependecies




    },

    getCode: (ast) => {

        const { code } = transformFromAst(ast, null, {

            presets: ["@babel/preset-env"]
        })
        return code
    }
}