-
-
Notifications
You must be signed in to change notification settings - Fork 165
feat: load minimizers by module path in workers #744
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e50aeee
feat: load minimizers by module path in workers
xiaoxiaojx 4a3929e
Merge branch 'main' into feat/worker-implementation-path
alexander-akait 8cb5b9f
docs: a minimizer named by module path, and a changeset for it
alexander-akait 2575275
fix: a payload holding a function keeps the whole task on transform
alexander-akait a102dd0
fix: a minimizer named by path keeps its helpers and its own identity
alexander-akait 51f787f
fix: a minimizer's module path is a cache key, not a hash
alexander-akait c557b38
fix: a minimizer's module tells builds apart without tying them to a …
alexander-akait 86c83d3
fix: hash the module a minimizer reference resolves to
alexander-akait File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "minimizer-webpack-plugin": minor | ||
| --- | ||
|
|
||
| Take a minimizer as a module path or `{ path, export }`, which a worker requires rather than rebuilding it from its source. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| /** @typedef {import("./index.js").MinimizedResult} MinimizedResult */ | ||
| /** @typedef {import("./index.js").CustomOptions} CustomOptions */ | ||
| /** @typedef {import("./index.js").MinimizeFunctionHelpers} MinimizeFunctionHelpers */ | ||
| /** @typedef {import("./index.js").ImplementationModuleRef} ImplementationModuleRef */ | ||
| /** | ||
| * @typedef {import("./index.js").BasicMinimizerImplementation<CustomOptions> & MinimizeFunctionHelpers} MinimizerFn | ||
| */ | ||
|
|
||
| /** | ||
| * @param {unknown} implementation a minify function, module path, or path ref | ||
| * @returns {ImplementationModuleRef | undefined} how to `require` it in a worker | ||
| */ | ||
| function getImplementationModuleRef(implementation) { | ||
| if (typeof implementation === "string") { | ||
| return { path: implementation }; | ||
| } | ||
|
|
||
| if ( | ||
| implementation && | ||
| typeof implementation === "object" && | ||
| typeof (/** @type {ImplementationModuleRef} */ (implementation).path) === | ||
| "string" | ||
| ) { | ||
| const ref = /** @type {ImplementationModuleRef} */ (implementation); | ||
|
|
||
| return typeof ref.export === "string" && ref.export.length > 0 | ||
| ? { path: ref.path, export: ref.export } | ||
| : { path: ref.path }; | ||
| } | ||
|
|
||
| return undefined; | ||
| } | ||
|
|
||
| /** | ||
| * @param {unknown} implementation a minify function, module path, or path ref | ||
| * @returns {MinimizerFn} the minify function | ||
| */ | ||
| function loadImplementation(implementation) { | ||
| if (typeof implementation === "function") { | ||
| return /** @type {MinimizerFn} */ (implementation); | ||
| } | ||
|
|
||
| const ref = getImplementationModuleRef(implementation); | ||
|
|
||
| if (!ref) { | ||
| throw new TypeError( | ||
| "Invalid minimizer implementation: expected a function, module path string, or { path, export }", | ||
| ); | ||
| } | ||
|
|
||
| const mod = require(ref.path); | ||
|
|
||
| const loaded = | ||
| typeof ref.export === "string" | ||
| ? mod[ref.export] | ||
| : typeof mod === "function" | ||
| ? mod | ||
| : mod && mod.default; | ||
|
|
||
| if (typeof loaded !== "function") { | ||
| throw new TypeError( | ||
| typeof ref.export === "string" | ||
| ? `Minimizer export "${ref.export}" is not a function in ${ref.path}` | ||
| : `Minimizer module does not export a function: ${ref.path}`, | ||
| ); | ||
| } | ||
|
|
||
| return /** @type {MinimizerFn} */ (loaded); | ||
| } | ||
|
|
||
| /** | ||
| * Whether a value holds a function anywhere inside it. A worker reached by | ||
| * module path is handed the payload as it is, and a structured clone throws on | ||
| * one rather than dropping it. | ||
| * @param {unknown} value what a worker would be handed | ||
| * @param {Set<unknown>=} seen values already walked | ||
| * @returns {boolean} true when a function is in there | ||
| */ | ||
| function holdsFunction(value, seen = new Set()) { | ||
| if (typeof value === "function") { | ||
| return true; | ||
| } | ||
|
|
||
| if (!value || typeof value !== "object" || seen.has(value)) { | ||
| return false; | ||
| } | ||
|
|
||
| seen.add(value); | ||
|
|
||
| // A structured clone carries a `Map` or a `Set` but not what it holds, and | ||
| // neither answers to `Object.values`. | ||
| if (value instanceof Map) { | ||
| return [...value].some( | ||
| ([key, one]) => holdsFunction(key, seen) || holdsFunction(one, seen), | ||
| ); | ||
| } | ||
|
|
||
| if (value instanceof Set) { | ||
| return [...value].some((one) => holdsFunction(one, seen)); | ||
| } | ||
|
|
||
| return Object.values(/** @type {Record<string, unknown>} */ (value)).some( | ||
| (one) => holdsFunction(one, seen), | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * True when every `minimizer.implementation` is a module path (`string` or | ||
| * `{ path, export }`). Inline minify functions keep `transform`. When | ||
| * `embedded` is present, *every* configured implementation must be a path — | ||
| * a single inline function in the embedded set forces `transform` for the | ||
| * whole asset task, even if that asset's own matched minimizers are paths. | ||
| * @template T | ||
| * @param {import("./index.js").InternalOptions<T>} options options | ||
| * @returns {boolean} whether `worker.minify` can run without `transform` | ||
| */ | ||
| function canMinifyByPath(options) { | ||
| /** | ||
| * @param {unknown} implementation implementation | ||
| * @returns {boolean} true when a module path is known | ||
| */ | ||
| const hasPath = (implementation) => | ||
| Boolean(getImplementationModuleRef(implementation)); | ||
|
|
||
| const minimizers = Array.isArray(options.minimizer.implementation) | ||
| ? options.minimizer.implementation | ||
| : [options.minimizer.implementation]; | ||
|
|
||
| if (!minimizers.every(hasPath)) { | ||
| return false; | ||
| } | ||
|
|
||
| // `extractComments` and a minimizer's own options both take functions, and | ||
| // those only ever reached a worker as source. | ||
| if ( | ||
| holdsFunction(options.extractComments) || | ||
| holdsFunction(options.minimizer.options) | ||
| ) { | ||
| return false; | ||
| } | ||
|
|
||
| if (!options.embedded) { | ||
| return true; | ||
| } | ||
|
|
||
| if (holdsFunction(options.embedded.options)) { | ||
| return false; | ||
| } | ||
|
|
||
| const embedded = Array.isArray(options.embedded.implementation) | ||
| ? options.embedded.implementation | ||
| : [options.embedded.implementation]; | ||
|
|
||
| return embedded.every(hasPath); | ||
| } | ||
|
|
||
| /** | ||
| * The file `loadImplementation` would `require`, which is what tells two | ||
| * references apart: a bare specifier and a file of that name are not one module. | ||
| * @param {unknown} implementation a minify function, module path, or path ref | ||
| * @returns {string | undefined} its resolved module, or nothing where no module is named | ||
| */ | ||
| function resolveImplementationModule(implementation) { | ||
| const ref = getImplementationModuleRef(implementation); | ||
|
|
||
| return ref ? require.resolve(ref.path) : undefined; | ||
| } | ||
|
|
||
| module.exports = { | ||
| canMinifyByPath, | ||
| getImplementationModuleRef, | ||
| loadImplementation, | ||
| resolveImplementationModule, | ||
| }; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.