| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author sheo13666q @sheo13666q
- */
- "use strict";
- /** @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;
|