Skip to content

Commit

Permalink
feat: added types (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-akait committed Dec 9, 2021
1 parent 671337d commit 02d9b2a
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 28 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@
"start": "npm run build -- -w",
"clean": "del-cli dist",
"prebuild": "npm run clean",
"build": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build:types": "tsc --declaration --emitDeclarationOnly --outDir types && prettier \"types/**/*.ts\" --write && prettier types --write",
"build:code": "cross-env NODE_ENV=production babel src -d dist --copy-files",
"build": "npm-run-all -p \"build:**\"",
"commitlint": "commitlint --from=master",
"security": "npm audit --production",
"lint:prettier": "prettier --list-different .",
"lint:js": "eslint --cache .",
"lint:types": "tsc --pretty --noEmit",
"lint": "npm-run-all -l -p \"lint:**\"",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "npm run test:only -- --watch",
Expand All @@ -34,7 +37,8 @@
"release": "standard-version"
},
"files": [
"dist"
"dist",
"types"
],
"peerDependencies": {
"webpack": "^5.1.0"
Expand All @@ -50,6 +54,7 @@
"@commitlint/cli": "^15.0.0",
"@commitlint/config-conventional": "^15.0.0",
"@gfx/zopfli": "^1.0.15",
"@types/serialize-javascript": "^5.0.1",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^27.0.6",
"cross-env": "^7.0.3",
Expand All @@ -66,6 +71,7 @@
"npm-run-all": "^4.1.5",
"prettier": "^2.3.2",
"standard-version": "^9.3.0",
"typescript": "^4.5.2",
"webpack": "^5.51.0",
"webpack-stats-plugin": "^1.0.3",
"workbox-webpack-plugin": "^6.2.4"
Expand Down
162 changes: 146 additions & 16 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,83 @@ import serialize from "serialize-javascript";

import schema from "./options.json";

/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */
/** @typedef {import("webpack").Compiler} Compiler */
/** @typedef {import("webpack").Compilation} Compilation */
/** @typedef {import("webpack").sources.Source} Source */
/** @typedef {import("webpack").Asset} Asset */
/** @typedef {import("webpack").WebpackError} WebpackError */

/** @typedef {RegExp | string} Rule */

/** @typedef {Rule[] | Rule} Rules */

/**
* @typedef {{ [key: string]: any }} CustomOptions
*/

/**
* @template T
* @typedef {T extends infer U ? U : CustomOptions} InferDefaultType
*/

/**
* @template T
* @typedef {InferDefaultType<T>} CompressionOptions
*/

/**
* @template T
* @callback AlgorithmFunction
* @param {Buffer} input
* @param {CompressionOptions<T>} options
* @param {(error: Error, result: string | Buffer) => void} callback
*/

/**
* @typedef {{[key: string]: any}} PathData
*/

/**
* @typedef {string | ((fileData: PathData) => string)} Filename
*/

/**
* @typedef {boolean | "keep-source-map"} DeleteOriginalAssets
*/

/**
* @template T
* @typedef {Object} BasePluginOptions
* @property {Rules} [test]
* @property {Rules} [include]
* @property {Rules} [exclude]
* @property {string | AlgorithmFunction<T>} [algorithm]
* @property {CompressionOptions<T>} [compressionOptions]
* @property {number} [threshold]
* @property {number} [minRatio]
* @property {DeleteOriginalAssets} [deleteOriginalAssets]
* @property {Filename} [filename]
*/

/**
* @template T
* @typedef {BasePluginOptions<T> & { compressionOptions: CompressionOptions<T>, threshold: number, minRatio: number, deleteOriginalAssets: DeleteOriginalAssets, filename: Filename }} InternalPluginOptions
*/

/**
* @typedef {import("zlib").ZlibOptions} ZlibOptions
*/

/**
* @template [T=ZlibOptions]
*/
class CompressionPlugin {
/**
* @param {BasePluginOptions<T>} [options]
*/
constructor(options = {}) {
validate(schema, options, {
validate(/** @type {Schema} */ (schema), options, {
name: "Compression Plugin",
baseDataPath: "options",
});
Expand All @@ -23,13 +97,17 @@ class CompressionPlugin {
include,
exclude,
algorithm = "gzip",
compressionOptions = {},
compressionOptions = /** @type {CompressionOptions<T>} */ ({}),
filename = "[path][base].gz",
threshold = 0,
minRatio = 0.8,
deleteOriginalAssets = false,
} = options;

/**
* @private
* @type {InternalPluginOptions<T>}
*/
this.options = {
test,
include,
Expand All @@ -42,12 +120,25 @@ class CompressionPlugin {
deleteOriginalAssets,
};

this.algorithm = this.options.algorithm;
/**
* @private
* @type {AlgorithmFunction<T>}
*/
this.algorithm =
/** @type {AlgorithmFunction<T>} */
(this.options.algorithm);

if (typeof this.algorithm === "string") {
/**
* @type {typeof import("zlib")}
*/
// eslint-disable-next-line global-require
const zlib = require("zlib");

/**
* @private
* @type {AlgorithmFunction<T>}
*/
this.algorithm = zlib[this.algorithm];

if (!this.algorithm) {
Expand All @@ -73,42 +164,62 @@ class CompressionPlugin {
zlib.constants.BROTLI_MAX_QUALITY,
},
},
}[algorithm] || {};

this.options.compressionOptions = {
...defaultCompressionOptions,
...this.options.compressionOptions,
};
}[/** @type {string} */ (algorithm)] || {};

this.options.compressionOptions =
/**
* @type {CompressionOptions<T>}
*/
({
.../** @type {object} */ (defaultCompressionOptions),
.../** @type {object} */ (this.options.compressionOptions),
});
}
}

/**
* @private
* @param {Buffer} input
* @returns {Promise<Buffer>}
*/
runCompressionAlgorithm(input) {
return new Promise((resolve, reject) => {
this.algorithm(
input,
this.options.compressionOptions,
(error, result) => {
if (error) {
return reject(error);
reject(error);

return;
}

if (!Buffer.isBuffer(result)) {
// eslint-disable-next-line no-param-reassign
result = Buffer.from(result);
}

return resolve(result);
resolve(result);
}
);
});
}

/**
* @private
* @param {Compiler} compiler
* @param {Compilation} compilation
* @param {Record<string, Source>} assets
* @returns {Promise<void>}
*/
async compress(compiler, compilation, assets) {
const cache = compilation.getCache("CompressionWebpackPlugin");
const assetsForMinify = (
await Promise.all(
Object.keys(assets).map(async (name) => {
const { info, source } = compilation.getAsset(name);
const { info, source } = /** @type {Asset} */ (
compilation.getAsset(name)
);

if (info.compressed) {
return false;
Expand All @@ -124,6 +235,9 @@ class CompressionPlugin {
return false;
}

/**
* @type {string | undefined}
*/
let relatedName;

if (typeof this.options.algorithm === "function") {
Expand All @@ -133,6 +247,9 @@ class CompressionPlugin {
.update(serialize(this.options.filename))
.digest("hex")}`;
} else {
/**
* @type {string}
*/
let filenameForRelatedName = this.options.filename;

const index = filenameForRelatedName.indexOf("?");
Expand Down Expand Up @@ -202,6 +319,7 @@ class CompressionPlugin {
for (const asset of assetsForMinify) {
scheduledTasks.push(
(async () => {
// @ts-ignore
const { name, source, buffer, output, cacheItem, info, relatedName } =
asset;

Expand All @@ -210,7 +328,7 @@ class CompressionPlugin {
try {
output.compressed = await this.runCompressionAlgorithm(buffer);
} catch (error) {
compilation.errors.push(error);
compilation.errors.push(/** @type {WebpackError} */ (error));

return;
}
Expand All @@ -235,16 +353,21 @@ class CompressionPlugin {
});
const newInfo = { compressed: true };

// TODO: possible problem when developer uses custom function, ideally we need to get parts of filname (i.e. name/base/ext/etc) in info
// otherwise we can't detect an asset as immutable
if (
info.immutable &&
typeof this.options.filename === "string" &&
/(\[name]|\[base]|\[file])/.test(this.options.filename)
) {
// @ts-ignore
newInfo.immutable = true;
}

if (this.options.deleteOriginalAssets) {
if (this.options.deleteOriginalAssets === "keep-source-map") {
compilation.updateAsset(name, source, {
// @ts-ignore
related: { sourceMap: null },
});
}
Expand All @@ -261,9 +384,13 @@ class CompressionPlugin {
);
}

return Promise.all(scheduledTasks);
await Promise.all(scheduledTasks);
}

/**
* @param {Compiler} compiler
* @returns {void}
*/
apply(compiler) {
const pluginName = this.constructor.name;

Expand All @@ -284,8 +411,11 @@ class CompressionPlugin {
.tap(
"compression-webpack-plugin",
(compressed, { green, formatFlag }) =>
// eslint-disable-next-line no-undefined
compressed ? green(formatFlag("compressed")) : undefined
compressed
? /** @type {Function} */ (green)(
/** @type {Function} */ (formatFlag)("compressed")
)
: ""
);
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/options.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"type": "object",
"additionalProperties": false,
"definitions": {
"Rule": {
Expand Down Expand Up @@ -114,6 +115,5 @@
}
]
}
},
"type": "object"
}
}
Loading

0 comments on commit 02d9b2a

Please sign in to comment.