web-increment-plugin v0.0.2
webpack增量插件
用于满足某些领导的要求,打包时排除内容未发生变化的文件,用途不重要,主要是记录一下一个webpack插件从0到发布npm的过程
一、创建项目
使用npm init
创建一个空项目,参考Typescript官网上的步骤,建立一个ts+gulp项目
npm install -g gulp-cli
npm install --save-dev typescript gulp gulp-typescript
在根目录下创建tsconfig.json
文件,输入以下内容
{
"files": [
"src/main.ts"
],
"compilerOptions": {
"noImplicitAny": true,
"target": "es5"
}
}
再创建gulpfile.js
文件,输入以下内容
var gulp = require("gulp");
var ts = require("gulp-typescript");
var tsProject = ts.createProject("tsconfig.json");
gulp.task("default", function () {
return tsProject.src()
.pipe(tsProject())
.js.pipe(gulp.dest("dist"));
});
二、实现功能
webpack插件需要导出一个类(constructor),这个类需要有一个公开的apply方法,apply方法的参数为webpack的compiler
对象,compiler
对象有许多hooks,分别在编译的不同截断触发
我们要实现删除内容未发生变化的文件,可以让开发者传入一个用于比较的文件夹路径,然后在webpack将资源输出到对应文件夹后,通过对比两个文件夹,筛选出内容一致的文件并删除,代码如下:
// src/index.ts
import { Stats } from "fs";
import getMd5 from "./getMd5";
import { AssetEmittedInfo, Compiler } from "webpack";
const path = require("path");
const fsPromise = require("fs/promises");
class IncrementPlugin {
private comparePath: string;
constructor(comparePath: string) {
if (!comparePath) {
throw new Error("patterns must be a directory");
}
fsPromise.stat(comparePath).then((data: Stats) => {
if (!data.isDirectory()) {
throw new Error("patterns must be a directory");
}
this.comparePath = comparePath;
});
}
public apply(compiler: Compiler) {
compiler.hooks.assetEmitted.tap(
"IncrementPlugin",
async (file: any, info: AssetEmittedInfo) => {
let compareFile = path.resolve(this.comparePath, file);
try {
await fsPromise.stat(compareFile);
let compareMd5 = getMd5(await fsPromise.readFile(compareFile));
let currentMd5 = getMd5(info.source.source() as unknown as string);
if (compareMd5 === currentMd5) {
await fsPromise.unlink(path.resolve(info.outputPath, file));
}
} catch (error) {
if (error && error.code === "ENOENT") {
// 新增文件
}
}
}
);
}
}
module.exports = IncrementPlugin;
注意我们使用的是assetEmitted
钩子,这个钩子在输出 asset 到 output 目录之后执行,具体用法可以点击这里
接下来实现文件比较的方法,这里我们将文件内容转换为md5进行比较
// src/getMd5.ts
import { Hash } from "crypto";
const crypto = require("crypto");
export default function getMd5(fileContent: string): string {
const hash: Hash = crypto.createHash("md5");
hash.update(fileContent, "utf8");
return hash.digest("hex");
}
这样我们有两个文件了,修改tsconfig.json
文件
{
"files": [
"src/getMd5.ts",
"src/index.ts"
],
}
接下来我们增加一些单元测试代码 安装依赖
npm i mocha @types/mocha chai @types/chai ts-node --save-dev
注意因为我们是ts项目,如果要让mocha和chai支持类型,需要安装ts-node 编写测试代码,注意不要漏了创建测试用的文件夹和文件:
// tests/md5.test.ts
import * as chai from "chai";
import getMd5 from "../src/getMd5";
const fsPromise = require("fs/promises");
const expect = chai.expect;
describe("md5测试", () => {
it("内容相同的文件,md5值应当相同", async () => {
const md51 = getMd5(
await fsPromise.readFile("./tests/md5-test/a/testMd5.txt")
);
const md52 = getMd5(
await fsPromise.readFile("./tests/md5-test/b/testMd5.txt")
);
expect(md51).to.equal(md52);
});
it("内容不一致,md5值应当不同", async () => {
const md51 = getMd5(
await fsPromise.readFile("./tests/md5-test/a/testMd5.txt")
);
const md53 = getMd5(
await fsPromise.readFile("./tests/md5-test/b/testMd6.txt")
);
console.log(md51, md53);
expect(md51).to.not.equal(md53);
});
it("内容一致,文件和字符串生成的md5值应当相同", async () => {
const md51 = getMd5(
await fsPromise.readFile("./tests/md5-test/a/testMd5.txt")
);
const md53 = getMd5("这是md5一致性测试文件");
console.log(md51, md53);
expect(md51).to.equal(md53);
});
});
运行下面的命令进行单元测试
mocha --reporter spec --require ts-node/register tests/*.test.ts
最后,在终端输入命令gulp
,可以看到在项目目录下出现了一个dist文件夹,里面是已经编译为es5的功能代码
三、安装使用
为了测试插件能否正常使用,我们需要创建一个前端项目进行调试,照例,使用npm init
命令创建项目
安装webpack
和webpack-cli
npm install webpack webpack-cli --save-dev
接下来我们在根目录下,创建一个index.html
文件
<!DOCTYPE html>
<html>
<head>
<title>增量发布插件测试页面</title>
</head>
<body>
</body>
</html>
这个文件空空如也,接下来,我们增加js脚本
创建src文件夹,然后创建一个index.js
文件,再创建两个依赖:say-hello.js
和say-world.js
// src/say-hello.js
export default function () {
return "hello";
}
// src/say-world.js
export default function () {
return "world";
}
// src/index.js
import sayHello from "./say-hello";
import sayWorld from "./say-world";
const ele = document.createElement("div");
ele.id = "hello";
ele.innerHTML = [sayHello(), sayWorld()].join(" ");
document.body.appendChild(ele);
注意我们这里使用了es6 module
语法,如果直接在index.html
中引入src/index.js
,浏览器会报错,这意味着我们需要对js代码进行构建
在项目目录下创建文件webpack.config.js
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
main: "./src/index.js"
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
},
plugins: [
new CleanWebpackPlugin({})
],
};
我们给项目设置了一个入口:src/index.js
文件,webpack
会从入口文件出发,不断拉取依赖,将编译结果输出至目标文件夹。
此外,我们还配置了CleanWebpackPlugin插件,这样,每次构建都会自动删掉之前产生的dist文件夹
修改packge.json
文件,增加build命令
{
"scripts": {
"build": "webpack"
}
}
执行npm run build
命令,可以看到项目下生成了dist文件夹,里面有webpack
构建出来的main.bundle.js
文件
现在我们修改index.html
文件
<!DOCTYPE html>
<html>
<head>
<title>增量发布插件测试页面</title>
</head>
<body>
<script src="./dist/main.bundle.js"></script>
</body>
</html>
用浏览器打开index.html文件
,可以看到页面出现了内容
接下来我们再创建一个模块,在src中增加两个文件
// src/lib/ok.js
export default function (num) {
return ["Are ", ...Array(num).fill("are "), "you ok?"].join("");
}
// src/change-word.js
import ok from "./lib/ok";
window.addEventListener("load", function () {
document.querySelector("#hello").innerHTML = ok(3);
});
然后修改webpack.config.js
,增加一个入口:
{
entry: {
main: "./src/index.js",
change: "./src/change-word.js"
}
}
执行npm run build
,可以看到dist中增加了一个change.bundle.js
文件
修改index.html
文件,在main.bundle.js
下方引入新增的change.bundle.js
文件,刷新页面,可以看到内容发生了变化
在项目目录创建plugins/increment
文件夹,将上一个项目生成的index.js
和getMd5.js
文件复制到此文件夹中,修改webpack.config.js
:
const IncrementPlugin = require("./plugins/increment/index.js");
module.exports = {
plugins: [
new CleanWebpackPlugin({}),
new IncrementPlugin(path.resolve(__dirname, "compare")),
],
}
在项目目录下创建一个compare
文件夹,将dist
文件夹中的文件全部复制到compare
中
修改src/main.js
或src/change-word.js
中的内容,然后重新执行npm run build
可以发现生成的dist中,只保留了我们修改的文件
四、发布npm
回到第一个项目,修改package.json
,更多配置可以参考npm
{
"name": "web-increment-plugin",
"version": "0.0.1",
"description": "webpack增量插件,删除同内容文件",
"main": "./dist/index.js",
"license": "MIT",
}
在终端输入npm login
登录npm,按提示输入用户名账号,如果没有账号,可以先注册一个
注意如果之前通过npm config set registry切换了npm源,此时需要切回官源:
npm config set registry https://registry.npmjs.org
输入npm publish
,完成发布
最后回到测试项目,把plugins
文件夹删除,执行命令npm i web-increment-plugin --save-D
,修改webpack.config.js
文件:
// const IncrementPlugin = require("./plugins/increment/index.js");
const IncrementPlugin = require("web-increment-plugin");
重新运行npm run build
,可以看到产出和上次相同,完工~