学习实现webpack打包

学习实现一个简陋的打包流程

随着前端模块化的趋势,知道 webpack 如何打包是很有必要的
简单打包需要 3 个步骤:

  1. 传入入口文件的路径,返回一个包含文件名,依赖和代码的对象。
    1. 通过 babel 的 parse 解析生成文件抽象语法树
    2. 通过 traverse 遍历 AST, 获取依赖
    3. 通过 transformFromAst 方法,依据你指定的规则生成对应的代码(https://babeljs.io/docs/en/babel-preset-env)
  2. 分析文件依赖,生成依赖图谱,包含文件路径和对应的依赖以及代码
  3. 输出浏览器可执行的代码到指定的输出目录下

preparation

1
2
3
4
5
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const { transformFromAst } = require("@babel/core");

step one

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const getModule = entryFile => {
//读取到文件内容
const content = fs.readFileSync(entryFile, "utf-8");
//解析文件内容成抽象语法树
const ast = parser.parse(content, {
sourceType: "module" //用于识别es module
});

const dependencies = {};
//遍历ast, 找出import并提取依赖保存到dependencies对象中。
traverse(ast, {
ImportDeclaration({ node }) {
const fileName = node.source.value,
dirname = path.dirname(filename), //获取文件的目录路径
filePath = path.join(dirname, fileName); //拼接目录路径与文件名,合成完成路径
dependencies[fileName] = filePath;
}
});

//按照定义的规则,如果没有就是默认规则,生成代码块
const { code } = transformFromAst({
presets: ["@babel/preset-env"]
});
//返回文件解析后对应的内容,包含文件名,依赖对象,代码
return {
filename,
dependencies,
code
};
};

step two

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const genDepGraph = entryFile => {
//首先拿到入口文件的模块信息
const module = getModule(entryFile);
const modules = [];
//将入口文件的模块信息加入到模块数组中
modules.push(module);
//通过入口模块,去检索并将所有依赖模块信息存入
for (let i = 0; i < modules.length; i++) {
const item = modules[i];
const { dependencies } = item;
if (dependencies) {
for (let j in dependencies) {
modules.push(getModule(dependencies[j]));
}
}
}
//生成依赖图谱
const graph = modules.reduce((acc, item) => {
const { filename, dependencies, code } = item;
acc[filename] = {
dependencies,
code
};
}, {});
return graph;
};

step three

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const genCode = entryFile => {
const graph = JSON.stringify(genDepGraph(entryFile));
//这里像webpack那样植入了一段js 代码用来告诉浏览器应该怎么去加载生成的代码。
//稍微的分析
/**
1. require做了什么?
(function(require, exports, code){}(localRequire, exports, graph[module].code))()
这个自执行函数会去递归的执行生成的代码,并以对象的形式返回。
*/
const bundle = `
(function(graph){
function require(module){
function localRequire(relativePath){
return require(graph[module].dependencies[relativePath])
}
var exports = {};
(function(require, exports, code){
eval(code)
})(localRequire, exports, graph[module].code)
return exports;
}
require('${entryFile}')
})(${graph})
`;
fs.writeFileSync(path.join(__dirname, "./dist/main.js"), bundle, "utf-8");
};
genCode("./src/index.js");

按照 webpack 官方文档学习制作简单的 loader 和 plugin

首先是需要安装 webpack, 创建一个 loader 文件夹,创建一个你自己 loader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
npm install webpack -D

mkdir loader
touch replaceLoader.js

//loader其实说白了就是接收配置参数,做转换并将转换后的输出
//每个loader的功能单一,只关注一方面的内容
const loaderUtils = require("loader-utils");

module.exports = function(src) {
console.log(typeof src);
const ops = loaderUtils.getOptions(this);
const res = src.replace(/kkb/gi, ops.name);
return res;
};

配置 webpack config 文件 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = {
mode: "development",
resolveLoader: {
modules: ["node_modules", "./loader"]
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: "replaceLoader",
options: {
name: "开课吧"
}
}
]
}
]
}
};

最后就是执行 webpack,检查输出的内容。https://webpack.docschina.org/contribute/writing-a-loader/

1
npx webpack

关于 plugin, 创建 plugins 目录,创建 CopyrightWebpackPlugin.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class CopyrightWebpackPlugin {
constructor(options) {}

apply(compiler) {
compiler.hooks.emit.tapAsync(
"CopyrightWebpackPlugin",
(compilation, cb) => {
compilation.assets["copyright.txt"] = {
source: function() {
return "hello copy";
},
size: function() {
return 20;
}
};
cb();
}
);
}
}

module.exports = CopyrightWebpackPlugin;

配置 webpack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// @ts-nocheck
const path = require("path");
const CopyrightWebpackPlugin = require("./plugins/CopyrightWebpackPlugin");

module.exports = {
mode: "development",
resolveLoader: {
modules: ["node_modules", "./loader"]
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: "replaceLoaderAsync",
options: {
name: "babala"
}
},
{
loader: "replaceLoader",
options: {
name: "开课吧"
}
}
]
}
]
},
plugins: [
new CopyrightWebpackPlugin({
name: "kkb"
})
]
};

执行 webpack,检查生成 dist 目录下的文件

https://webpack.docschina.org/contribute/writing-a-plugin/

1
npx webpack