一:webpack要解决的两个问题
我有三个js文件,三者存在互相引用的关系,如下代码所示:
a.js
import b from "./b.js";
const a = {
value: "a",
getB: () => b.value + "from a.js",
};
export default a;
b.js
import a from "./a.js";
const b = {
value: "b",
getA: () => a.value + "from b.js",
};
export default b;
index.js
import a from "./a.js";
import b from "./b.js";
console.log(a.getB());
console.log(b.getA());
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="index.js" type="module"></script>
</body>
</html>
问题一:浏览器是不支持import和export的代码关键字,所以我们需要在script标签加上module属性,不过IE8-15不支持,而且一个个文件引入一个项目之中可能会有几千个文件请求,请求过多。
问题二:我们应该怎么做才能打包到一个js里面呢?
二:解决问题一,使用babel可以帮助我们转译打包
bundle.ts
// 请确保你的 Node 版本大于等于 14
// 请先运行 yarn 或 npm i 来安装依赖
// 然后使用 node -r ts-node/register 文件路径 来运行,
// 如果需要调试,可以加一个选项 --inspect-brk,再打开 Chrome 开发者工具,点击 Node 图标即可调试
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import { writeFileSync, readFileSync } from "fs";
import { resolve, relative, dirname, join } from "path";
import * as babel from "@babel/core";
import { mkdir } from "shelljs";
// 设置根目录
const projectName = "project_1";
const projectRoot = resolve(__dirname, projectName);
// 类型声明
type DepRelation = { key: string; deps: string[]; code: string }[];
// 初始化一个空的 depRelation,用于收集依赖
const depRelation: DepRelation = []; // 数组!
// 将入口文件的绝对路径传入函数,如 D:\demo\fixture_1\index.js
collectCodeAndDeps(resolve(projectRoot, "index.js"));
// 先创建 dist 目录
const dir = `./${projectName}/dist`;
mkdir("-p", dir);
// 再创建 bundle 文件
writeFileSync(join(dir, "bundle.js"), generateCode());
console.log("done");
function generateCode() {
let code = "";
code +=
"var depRelation = [" +
depRelation
.map((item) => {
const { key, deps, code } = item;
return `{
key: ${JSON.stringify(key)},
deps: ${JSON.stringify(deps)},
code: function(require, module, exports){
${code}
}
}`;
})
.join(",") +
"];\n";
code += "var modules = {};\n";
code += `execute(depRelation[0].key)\n`;
code += `
function execute(key) {
if (modules[key]) { return modules[key] }
var item = depRelation.find(i => i.key === key)
if (!item) { throw new Error(\`\${item} is not found\`) }
var pathToKey = (path) => {
var dirname = key.substring(0, key.lastIndexOf('/') + 1)
var projectPath = (dirname + path).replace(\/\\.\\\/\/g, '').replace(\/\\\/\\\/\/, '/')
return projectPath
}
var require = (path) => {
return execute(pathToKey(path))
}
modules[key] = { __esModule: true }
var module = { exports: modules[key] }
item.code(require, module, module.exports)
return modules[key]
}
`;
return code;
}
function collectCodeAndDeps(filepath: string) {
const key = getProjectPath(filepath); // 文件的项目路径,如 index.js
if (depRelation.find((i) => i.key === key)) {
// 注意,重复依赖不一定是循环依赖
return;
}
// 获取文件内容,将内容放至 depRelation
const code = readFileSync(filepath).toString();
const { code: es5Code } = babel.transform(code, {
presets: ["@babel/preset-env"],
});
// 初始化 depRelation[key]
const item = { key, deps: [], code: es5Code };
depRelation.push(item);
// 将代码转为 AST
const ast = parse(code, { sourceType: "module" });
// 分析文件依赖,将内容放至 depRelation
traverse(ast, {
enter: (path) => {
if (path.node.type === "ImportDeclaration") {
// path.node.source.value 往往是一个相对路径,如 ./a.js,需要先把它转为一个绝对路径
const depAbsolutePath = resolve(
dirname(filepath),
path.node.source.value
);
// 然后转为项目路径
const depProjectPath = getProjectPath(depAbsolutePath);
// 把依赖写进 depRelation
item.deps.push(depProjectPath);
collectCodeAndDeps(depAbsolutePath);
}
},
});
}
// 获取文件相对于根目录的相对路径
function getProjectPath(path: string) {
return relative(projectRoot, path).replace(/\\/g, "/");
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script src="dist/bundle.js" type="module"></script>
</body>
</html>