跳到主要内容

自己动手实现一个打包器

· 阅读需 2 分钟
const fs = require("fs");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const core = require("@babel/core");
const path = require("path");

function readCode(filePath) {
// read file content
const content = fs.readFileSync(filePath, "utf-8");
// generate ast
const ast = parser.parse(content, { sourceType: "module" });
// find the dependent relationship in current file
const dependencies = [];
traverse(ast, {
ImportDeclaration({ node }) {
dependencies.push(node.source.value);
},
});

// transform the code into es5 by AST
const { code } = core.transformFromAst(ast, null, {
presets: ["@babel/preset-env"],
});

return {
filePath,
dependencies,
code,
};
}

function getDependencies(entry) {
// read entry file
const entryObject = readCode(entry);
const dependencies = [entryObject];

// dependencies is not static, it will change while pushing dependency inside, so the iterator will execute several rounds
for (const assets of dependencies) {
// get file directory
const dirname = path.dirname(assets.filePath);
assets.dependencies.forEach((relativePath) => {
// get absolute path
const absolutePath = path.join(dirname, relativePath);

if (/\.css$/.test(absolutePath)) {
const content = fs.readFileSync(absolutePath, "utf-8");
const code = `
const style = document.createElement('style')
style.innerText = ${JSON.stringify(content).replace(/\\r\\n/g, "")}
document.head.appendChild(style)
`;
dependencies.push({
filePath: absolutePath,
relativePath,
dependencies: [],
code,
});
} else {
const child = readCode(absolutePath);
child.relativePath = relativePath;
dependencies.push(child);
}
});
}
return dependencies;
}

function bundle(entry) {
entry = path.resolve(__dirname, entry);
dependencies = getDependencies(entry);

let modules = "";
dependencies.forEach((dep) => {
const filePath = dep.relativePath || entry;
modules += `'${filePath}': (
function(module, exports, require){${dep.code}}
),`;
});

const result = `
(function(modules){
function require(id){
const module = {exports: {}}
modules[id](module, module.exports, require)
return module.exports
}
require('${entry}')
})({${modules}})
`;

fs.writeFileSync(path.join(__dirname, "dist", "bundle.js"), result);
}

module.exports = {
bundle,
};