webpack
Version:
Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.
169 lines (156 loc) • 8.91 kB
JavaScript
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author sheo13666q @sheo13666q
*/
;
/** @import { CssEnvironment, CssTransformOptions } from "./syntax" */
/**
* What a renderer made of one embedded body: the minified text, and anything it
* has to report about it. A bare string is the text alone.
* @typedef {{ code?: string, warnings?: (Error | string)[], errors?: (Error | string)[] }} EmbeddedSourceResult
*/
/**
* Minifies source a stylesheet embeds; may answer asynchronously.
* @typedef {(source: string, info: { type: string, hostType: string }) => Promise<string | EmbeddedSourceResult | undefined> | string | EmbeddedSourceResult | undefined} AsyncEmbeddedSourceRenderer
*/
/** @import { SourceMap } from "../util/SourceProcessor" */
/**
* A `minify` function for `minimizer-webpack-plugin` (passed as its `minify`
* option): safely serializes one CSS asset's minimized form (collapse
* whitespace, drop redundant separators and empty rules, shorten colors /
* numbers / easing functions / identifier escapes, normalize string and `url()`
* quoting, keep custom-property values verbatim unless asked otherwise), so CSS minification reuses
* that plugin's pipeline — source maps, caching and worker-thread parallelization.
*
* The parser is read from the public `webpack.css.syntax` API inside the body, not
* imported at module scope: `minimizer-webpack-plugin` ships this function to its
* worker pool as source (a top-level import wouldn't survive), and `require("webpack")`
* re-resolves in the worker — via the installed package, or the dev self-link.
* @param {{ [file: string]: string }} input a single `{ filename: code }` entry
* @param {object=} sourceMap the asset's input source map — the plugin chains it onto the map returned here, so it isn't read directly
* @param {{ as?: "stylesheet" | "block-contents", environment?: CssEnvironment, convertLengthUnits?: boolean, rewriteCustomProperties?: boolean, renderEmbeddedSource?: AsyncEmbeddedSourceRenderer } & CssTransformOptions=} minimizerOptions minimizer options — `environment` carries the target's CSS abilities (see `output.environment`), so a spelling the target cannot read is not reached for; the rest is `optimization.minimize.css` (e.g. `convertLengthUnits`, and the per-transform switches). `renderEmbeddedSource` minifies source this stylesheet embeds, and may be asynchronous: one parse serves both it and the output
* @returns {Promise<{ code: string, map?: SourceMap, warnings?: (Error | string)[], errors?: (Error | string)[] }>} the minified CSS, its input->output source map (one anchor for the whole of a `block-contents` print, which is emitted as one piece), and what a renderer reported over what this embeds
*/
const cssMinify = async (input, sourceMap, minimizerOptions = {}) => {
const webpack = /** @type {typeof import("../index")} */ (
// eslint-disable-next-line import/no-extraneous-dependencies -- webpack self-require, re-resolved inside the worker
require(/** @type {string} */ ("webpack"))
);
// TODO expose the remaining declined transforms on `optimization.minimize.css`
// like `convertLengthUnits` — the switches below turn off what webpack does
// make, not what it declines. Each is on in other minifiers; what each costs:
// - dropping a declaration a later one overrides, which loses a fallback pair
// an engine that cannot read the newer spelling depends on;
// - merging rules a third stands between, and merging non-adjacent `@media`,
// both of which reorder the cascade (csso loses ~12,900 Tailwind classes to
// it). Adjacent rules sharing a block do join, nothing being between them to
// step over — only where every selector is a shape each engine parses, since
// one it cannot invalidates the whole list and loses the rest with it;
// - the color conversions webpack declines rather than guesses. Every polar and
// Lab function converts to hex, except two cases the other minifiers convert
// regardless: a channel landing near a `.5` boundary, where implementations
// round opposite ways (it is why esbuild and lightningcss emit different
// bytes for `hwb(194 0% 0%)`), and a Lab-family color outside the sRGB gamut,
// which hex would clip to a different color rather than respell.
// Which longhand families merge into their shorthand is decided in
// `tooling/generate-css-data.js`, and three kinds are excluded there for good
// rather than pending an option:
// - a shorthand gathering a whole family (`border`, `font`, `background`,
// `transition`, `flex`, `columns`) resets longhands `computed` does not
// name — `border` clears `border-image`, `font` clears `font-size-adjust` —
// so the merge would drop a declaration nothing in the family wrote. The
// ones checked against a browser and found to reset nothing else do merge
// (`FAMILY_LONGHANDS`), where each value parses back into its own slot;
// - a shorthand materially newer than its longhands (`place-items`/`-content`
// /`-self`) would lose both declarations, not one, on a target reading only
// the longhands. `output.environment` states this for `inset` alone.
// `overflow` is newer only two values wide, so it merges when it collapses;
// - a pair two shorthands both claim, which cannot be right for both. None
// today: `mdn-data` gave `corner-inline-start-shape` the block-start edge's
// corners, corrected in the generator to the pair Chromium computes.
const {
SourceProcessor,
askEmbeddedRenderer,
collectEmbeddedDiagnostics,
embeddedText,
pickTransforms
} = webpack.css.syntax;
const [[file, code]] = Object.entries(input);
// `process` parses once, and with `mode: "minify"` the same walk also prints
// the safely minified serialization — no second parse. Naming the input with
// `source` / `content` is what asks for the map, which the plugin composes
// back to the original source.
const {
as,
environment,
convertLengthUnits,
rewriteCustomProperties,
renderEmbeddedSource
} = minimizerOptions;
// One set of options, handed to the print. `as` names the production: a
// stylesheet, or the declaration list an HTML `style=""` holds — the printer
// composes that list the same way it composes a rule's block.
const printOptions = {
mode: /** @type {"minify"} */ ("minify"),
as,
source: file,
content: code,
environment,
convertLengthUnits,
rewriteCustomProperties,
// The per-transform switches stand beside those rather than nested in
// them, so a config names one the way it names `convertLengthUnits`;
// `pickTransforms` is what knows which names those are.
transforms: pickTransforms(minimizerOptions)
};
/** @type {EmbeddedSourceResult[]} */
const reported = [];
// Handed each body the print offers, with what it reported kept: `processAsync`
// spells the `url()` around one from the answer, and prints an untapped run's
// spelling for a body declined or thrown on.
const renderer =
renderEmbeddedSource === undefined
? undefined
: (/** @type {string} */ _source, /** @type {EXPECTED_ANY} */ hole) =>
askEmbeddedRenderer(renderEmbeddedSource, hole, reported).then(
embeddedText
);
const result = await new SourceProcessor().processAsync(code, {
...printOptions,
renderEmbeddedSource: renderer
});
return reported.length === 0
? result
: { ...result, ...collectEmbeddedDiagnostics(reported) };
};
// Worker-safe (see the body's in-worker `require`), so it may run in the shared
// worker-thread pool alongside terser.
cssMinify.supportsWorkerThreads = () => true;
/**
* The language this minifies, for a caller dispatching source that carries no
* filename — CSS a module embeds in a JavaScript string literal.
* @returns {string[]} the languages
*/
cssMinify.getTypes = () => ["css"];
/**
* The languages this can offer a caller through `renderEmbeddedSource` — what a
* `url()` `data:` payload's media type may name.
* @returns {string[]} the languages
*/
cssMinify.getEmbeddedTypes = () => [
// A copy: the list is one module-level array, and what a caller does with what
// it is handed is not this module's to bound. `htmlMinify` copies too.
.../** @type {typeof import("../index")} */ (
// eslint-disable-next-line import/no-extraneous-dependencies -- webpack self-require, as the body does
require(/** @type {string} */ ("webpack"))
).css.syntax.EMBEDDED_LANGUAGES
];
// When several minify functions share one `minimizer-webpack-plugin` instance,
// each asset is dispatched only to the ones whose `filter` accepts it — this
// claims CSS, so terser (JS) and this can coexist in a single plugin / worker pool.
/**
* @param {string} name asset filename
* @returns {boolean} true for CSS assets
*/
cssMinify.filter = (name) => /\.css(\?.*)?$/i.test(name);
module.exports = cssMinify;