手写mini-webpack(仅用了81行代码)

842 阅读1分钟

前言

最近打算跳槽到大厂,webpack打包流程必须了解,于是尝试一下手写一个打包器

准备工作

1. 3个js文件

index.js -> 依赖 subtraction.js => 依赖 sum.js

image.png

2. 5个npm依赖包

image.png

代码

const path = require("path")
const parser = require("@babel/parser")
const traverse = require("@babel/traverse").default
const fs = require("fs")
const { transformFromAst } = require("babel-core")
const config = {
    entry: "./src/index.js",
    output: {
        path: "./src/",
        filename: "build.js",
    },
}
const { output } = config
let id = 0
const createAsset = (entryFile) => {
    // 读取文件
    const source = fs.readFileSync(entryFile, "utf-8")
    // 代码转为ast,为了转换成ES5
    const ast = parser.parse(source, {
        sourceType: "module",
    })
    const dependents = {}
    // 借用traverse提取文件import的依赖
    traverse(ast, {
        ImportDeclaration({ node }) {
            dependents[node.source.value] = node.source.value
        },
    })
    // es6语法转es5
    const { code } = transformFromAst(ast, null, {
        presets: ["env"],
    })
    return {
        entryFile,
        dependents,
        code,
        id: id++,
        mapping: {},
    }
}
const createGraph = (rootPath) => {
    // 从根路径出发,获取所有与根路径相关依赖存放到modules中
    const mainAsset = createAsset(rootPath)
    const modules = [mainAsset]
    const dirname = path.dirname(rootPath)
    for (let asset of modules) {
        const { dependents } = asset
        for (let dep in dependents) {
            const childPath = path.join(dirname, dependents[dep])
            const childAsset = createAsset(childPath)
            asset.mapping[dependents[dep]] = childAsset.id
            modules.push(childAsset)
        }
    }
    return modules
}
// 转换一下数据结构
const createModules = (graph) => {
    const obj = {}
    graph.forEach((item) => {
        obj[item.id] = [item.code, item.mapping]
    })
    return obj
}
// 生成文件
const writeFiles = (modules) => {
    // 编译模板,modules是不固定的,其他都一样
    const bundle = `
    ;(function (modules) {
        const require = (id) => {
            const [code, mapping] = modules[id]
            const exports = {}
            ;(function (_require, exports, code, mapping) {
                const require = (path) => {
                    return _require(mapping[path])
                }
                eval(code)
            })(require, exports, code, mapping)
            return exports
        }
        require(0)
    })(${JSON.stringify(modules)})
    `
    // 生成文件
    const filePath = path.join(output.path, output.filename)
    fs.writeFileSync(filePath, bundle, "utf-8")
}
const graph = createGraph(config.entry)
const modules = createModules(graph)
writeFiles(modules)