Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added types #269

Merged
merged 5 commits into from
Dec 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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