0.0.2 • Published 2 years ago

web-increment-plugin v0.0.2

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

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命令创建项目 安装webpackwebpack-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.jssay-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.jsgetMd5.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.jssrc/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,可以看到产出和上次相同,完工~