@plaudit/webpack-extensions 2.94.1 → 2.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -60,6 +60,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
60
60
  - Legacy PostCSS features that have been integrated into modern CSS
61
61
  - `@extends` support
62
62
 
63
+ ## [2.96.0] - 2026-08-07
64
+ ### Added
65
+ - A port of WordPress's Dependency Extraction Webpack Plugin because they keep breaking it
66
+ - Warnings and additional info for entrypoints that are lazy-loaded and assigned to actual locations
67
+
68
+ ### Removed
69
+ - "combineAssetMetadata" - it has never actually been a valid prop
70
+
71
+ ### Fixed
72
+ - Path-query handle handling
73
+ - Double emission of CSS from entrypoint.json files when module processing is enabled
74
+ - A bug that could result in writes outside the webpack context root
75
+
76
+ ### Changed
77
+ - Backported the hook_name fix from the 3.x branch
78
+
79
+ ## [2.95.0] - 2026-08-04
80
+ ### Added
81
+ - Logic so that files and directories with names equal to "example" or "examples" or that include "example" as a name segment are skipped
82
+ - This is done to allow us to include example files
83
+ - Updated the user guide with this information
84
+
63
85
  ## [2.94.1] - 2026-07-20
64
86
  ### Fixed
65
87
  - A bug that caused all styles bound for editor use to be emitted as inline CSS
@@ -289,7 +311,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
289
311
 
290
312
  ## [2.72.1] - 2025-10-29
291
313
  ### Fixed
292
- - The wrapper being unable to find the `DependencyExtractionWebpackPlugin` due to version desyncs
314
+ - The wrapper being unable to find the `EnhancedDependencyExtractionWebpackPlugin` due to version desyncs
293
315
  - This isn't so much a "fix" as it is a "mitigation" - version desyncs are not fixable at the library level
294
316
 
295
317
  ## [2.72.0] - 2025-10-13
package/USER-GUIDE.md CHANGED
@@ -104,7 +104,7 @@ However, if you need to do something unusual or just want to know what everythin
104
104
  - Any time that `dist` is referenced in a path, it should be replaced with the value of `outputDir` in your WebPack file
105
105
  - As with `src`, the value of `outputDir` *should be* `dist`; however, this might not be the case in some inherited or older code
106
106
  - Keys in the `src` *object* are all assumed to be paths relative to `srcDir` if it is specified or the folder in which the `webpack.config.js` file is located if it is not specified
107
- - Paths in values in the `src` *object* are all assumed to be paths relative to `outputDir` if it is specified or the folder in which the `webpack.config.js` file is located if it is not specified
107
+ - Paths in *values* in the `src` object are all assumed to be paths relative to `outputDir` if it is specified or the folder in which the `webpack.config.js` file is located if it is not specified
108
108
  - All file names are informational placeholders. They can be whatever you want provided that they are web-safe (basically, don't do things like calling the email address block "✉️" and you'll be fine)
109
109
 
110
110
  ### Examples
@@ -117,6 +117,10 @@ However, if you need to do something unusual or just want to know what everythin
117
117
  - All folder and file names should be in kebab-case
118
118
  - Assets *should not* be in the root of the `src` folder. Assets that aren't block- or extension-bound should be in the `site` folder (or in a folder named after where they'll be used like, `admin` or `map-page`)
119
119
 
120
+ ### Enforced Naming Conventions
121
+ - Directory and file names that start with a "~" are interpreted as "backup code" and are, therefore, not built.
122
+ - File and directory names that are equal to "example" or "examples", or have "example" as a name segment (see [Location-Encoding Filenames](#location-encoding-filenames) for details on what a name-segment is) are treated as example code and are also not built
123
+
120
124
  ### Blocks
121
125
  - Block folder names *should* match the local portion of their name (ex: `plaudit-query-loop-maps/address-wrapper` should be stored in `address-wrapper`)
122
126
 
@@ -310,7 +314,7 @@ src: {
310
314
  | `srcDir` | `string` | `""` | Source directory (**Required** on new sites) |
311
315
  | `outputDir` | `string` | `""` | Output directory (**Required** on new sites) |
312
316
  | `useUnifiedLoader` | `boolean` | `false` | Generate unified-loader.php (always set to `true` on new sites!) |
313
- | `useWebpackResourceFiltering` | `boolean` | `false` | Generates versioned copies of images, fonts, etc (always set to `true` on new sites!) |
317
+ | `useWebpackResourceFiltering` | `boolean` | `true` | Generates versioned copies of images, fonts, etc (always set to `true` on new sites!) |
314
318
  | `plainEntrypointsVersion` | `1\|2` | `1` | Use 2 for new sites |
315
319
  | `extensionsVersion` | `1\|2\|3` | `1` | Use 3 for new sites |
316
320
  | `verbose` | `boolean` | `false` | Verbose logging |
@@ -354,16 +358,17 @@ Notes on how to read the following table:
354
358
  - `(-|=)` means "this can be either a dash or an equal sign"
355
359
  - Pipe-separated items in parentheses means "any one of these items can go here"
356
360
 
357
- | Name | Token | Usage |
358
- |--------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
359
- | Enqueuing Location | `(enqueuing-location)[(-\|=)priority]` | This is a special section that is actually a collection of values. See the [Enqueuing Location](#enqueuing-location) section for more details |
360
- | Type Override | `script`, `style`, `script-module` | Can be used to override the detected of the asset. Must be the first token in the filename; if used directly, it is shorthand for `both-(Type Override)` |
361
- | Inline | `inline[(-\|=)position]`, `strategy(-\|=)inline[-position]` | Indicates that the script should be inlined. `position` can be either `before` or `after` |
362
- | Strategy | `lazy\|eager`, `strategy(-\|=)(lazy\|eager\|defer\|async)` | Controls the loading strategy. Lazy and eager have shorthand while the remaining values must be prefixed with `strategy` |
363
- | Fetch Priority | `fetchpriority(-\|=)(low\|high\|auto)` | Controls the fetchpriority property; `auto` is the default |
364
- | In Footer | `in-footer`, `in_footer` | Sets the `in_footer` value to `true`; incompatible with `in-header` |
365
- | In Header | `in-header`, `in_header` | Sets the `in_footer` value to `false`; incompatible with `in-footer` |
366
- | Extension | `([ps]?c\|sa)ss`, `m?[jt]sx?` | This determines the type unless it is overridden. This **MUST** be the last token in the name (it is the file extension) |
361
+ | Name | Token | Usage |
362
+ |---------------------|-------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
363
+ | Enqueuing Location | `(enqueuing-location)[(-\|=)priority]` | This is a special section that is actually a collection of values. See the [Enqueuing Location](#enqueuing-location) section for more details |
364
+ | Type Override | `script`, `style`, `script-module` | Can be used to override the detected of the asset. Must be the first token in the filename; if used directly, it is shorthand for `both-(Type Override)` |
365
+ | Inline | `inline[(-\|=)position]`, `strategy(-\|=)inline[-position]` | Indicates that the script should be inlined. `position` can be either `before` or `after` |
366
+ | Strategy | `lazy\|eager`, `strategy(-\|=)(lazy\|eager\|defer\|async)` | Controls the loading strategy. Lazy and eager have shorthand while the remaining values must be prefixed with `strategy` |
367
+ | Fetch Priority | `fetchpriority(-\|=)(low\|high\|auto)` | Controls the fetchpriority property; `auto` is the default |
368
+ | In Footer | `in-footer`, `in_footer` | Sets the `in_footer` value to `true`; incompatible with `in-header` |
369
+ | In Header | `in-header`, `in_header` | Sets the `in_footer` value to `false`; incompatible with `in-footer` |
370
+ | Extension | `([ps]?c\|sa)ss`, `m?[jt]sx?` | This determines the type unless it is overridden. This **MUST** be the last token in the name (it is the file extension) |
371
+ | Example Code Marker | `example` | This informs the build system that the file contains example code and, therefore, should not be build |
367
372
 
368
373
  ### Enqueuing Location
369
374
  If this is the first token, it can be suffixed with `-script`, `-style`, or `-script-module` to override the type.
@@ -2,7 +2,7 @@ import { MinimumViableMetadata, ParsedAssetJsonProvider, ParsedAssetsJson } from
2
2
  import type { ExtensibleEntryObject, VerifiedPlauditWordpressWebpackConfig } from "../utils/common-config-helpers";
3
3
  import { PseudoSemaphore } from "../utils/pseduo-semaphore";
4
4
  import { AbstractBiPhasicGroupPlugin } from "./AbstractBiPhasicGroupPlugin";
5
- import { Compilation, Compiler, Entrypoint } from "webpack";
5
+ import { type Compilation, type Compiler, type Entrypoint } from "webpack";
6
6
  export type EntryProvider<M extends MinimumViableMetadata = MinimumViableMetadata> = () => ExtensibleEntryObject<M> | Promise<ExtensibleEntryObject<M>>;
7
7
  export declare abstract class AbstractBiPhasicGroupAndEntryPlugin<M extends MinimumViableMetadata = MinimumViableMetadata> extends AbstractBiPhasicGroupPlugin {
8
8
  private readonly context;
@@ -24,6 +24,7 @@ export declare class EnhancedBlockJSONPlugin extends AbstractBiPhasicGroupAndEnt
24
24
  constructor(config: VerifiedPlauditWordpressWebpackConfig, dest: VerifiedAdvancedOutputConfig, webpackRemoveEmptyScriptsPlugin: WebpackRemoveEmptyScriptsPlugin, context: string, entry: EntryProvider<BlockEntrypointInfo>);
25
25
  protected processAssets(compilation: Compilation, parsedAssetJsonProvider: ParsedAssetJsonProvider): Promise<void>;
26
26
  private emitBlockLoaderFile;
27
+ private static readonly THEME_JSON_FLAG_PROPS;
27
28
  private static pickOutFlagPropsFromBlockSupports;
28
29
  private callRegisterFunction;
29
30
  private transformBlocks;
@@ -226,37 +226,48 @@ class EnhancedBlockJSONPlugin extends AbstractBiPhasicGroupAndEntryPlugin_1.Abst
226
226
  compilation.emitAsset((0, node_path_1.join)(this.dest.destination, "blockdir.config.php"), new webpack_1.sources.RawSource((0, shared_1.makeEmittableConfigPHP)(blockDirConfig, true)));
227
227
  }
228
228
  }
229
- // This isn't strictly necessary, but it allows us to trivially eliminate commonly-used block supports flags that aren't used in theme.json
230
- // private static readonly IGNORED_THEME_JSON_FLAG_PROPS = ['align', 'alignWide', 'anchor', 'contentRole', 'inserter', 'interactivity', 'listView', 'plaudit'];
229
+ /*
230
+ * The block supports that have a boolean counterpart under theme.json's settings, as
231
+ * `settings key -> {support prop: settings prop}`. This is an allow-list rather than an ignore-list
232
+ * because most supports (align, alignWide, anchor, contentRole, inserter, interactivity, listView,
233
+ * plaudit, ...) have no theme.json settings equivalent at all, and the ones that do don't always
234
+ * share a name: typography's fontSize is settings.typography.customFontSize, and Gutenberg still
235
+ * ships several of these under an __experimental prefix.
236
+ */
237
+ static THEME_JSON_FLAG_PROPS = {
238
+ color: { background: 'background', link: 'link', text: 'text' },
239
+ dimensions: { aspectRatio: 'aspectRatio', minHeight: 'minHeight' },
240
+ spacing: { blockGap: 'blockGap', margin: 'margin', padding: 'padding' },
241
+ typography: {
242
+ fontSize: 'customFontSize',
243
+ lineHeight: 'lineHeight',
244
+ textAlign: 'textAlign',
245
+ fontStyle: 'fontStyle', __experimentalFontStyle: 'fontStyle',
246
+ fontWeight: 'fontWeight', __experimentalFontWeight: 'fontWeight',
247
+ letterSpacing: 'letterSpacing', __experimentalLetterSpacing: 'letterSpacing',
248
+ textDecoration: 'textDecoration', __experimentalTextDecoration: 'textDecoration',
249
+ textTransform: 'textTransform', __experimentalTextTransform: 'textTransform',
250
+ writingMode: 'writingMode', __experimentalWritingMode: 'writingMode'
251
+ }
252
+ };
231
253
  static pickOutFlagPropsFromBlockSupports(supports) {
232
254
  function isRecord(value) {
233
255
  return typeof value === 'object' && !!value && !Array.isArray(value);
234
256
  }
235
257
  let addedProp = false;
236
258
  const res = {};
237
- if ('color' in supports) {
238
- const color = supports.color;
239
- if (isRecord(color)) {
240
- const colorFlags = {};
241
- for (const prop of ['background', 'link', 'text']) {
242
- if (supports[prop]) {
243
- colorFlags[prop] = true;
244
- res.color = colorFlags;
245
- addedProp = true;
246
- }
247
- }
259
+ for (const [settingsKey, props] of Object.entries(EnhancedBlockJSONPlugin.THEME_JSON_FLAG_PROPS)) {
260
+ const support = supports[settingsKey];
261
+ if (!isRecord(support)) {
262
+ continue;
248
263
  }
249
- }
250
- if ('dimensions' in supports) {
251
- const dimensions = supports.dimensions;
252
- if (isRecord(dimensions)) {
253
- const dimensionsFlags = {};
254
- for (const prop of ['aspectRatio']) {
255
- if (supports[prop]) {
256
- dimensionsFlags[prop] = true;
257
- res.color = dimensionsFlags;
258
- addedProp = true;
259
- }
264
+ const flags = {};
265
+ for (const [supportProp, settingsProp] of Object.entries(props)) {
266
+ // A sided support such as `"margin": ["top", "bottom"]` is still just "on" as far as theme.json is concerned
267
+ if (support[supportProp]) {
268
+ flags[settingsProp] = true;
269
+ res[settingsKey] = flags;
270
+ addedProp = true;
260
271
  }
261
272
  }
262
273
  }
@@ -52,7 +52,7 @@ class ExtensionsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAndEntryP
52
52
  compilation.contextDependencies.add(srcRoot);
53
53
  }
54
54
  for await (const dirent of await (0, promises_1.opendir)(srcRoot)) {
55
- if (dirent.name.startsWith("~")) {
55
+ if ((0, shared_1.shouldBeSkipped)(dirent.name)) {
56
56
  continue;
57
57
  }
58
58
  if (dirent.isFile()) {
@@ -43,7 +43,7 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
43
43
  for (const { src, rest, locations, type, handleName, lazyLoader, ...otherProps } of handles) {
44
44
  const cleanBasename = (0, node_path_1.basename)(src).replace(/_(?:script(?:-\d+)?\.js|style(?:-\d+)?\.css)$|(?<!_(script|style))\.(js|css)$/, "");
45
45
  let finalHandleName;
46
- if (typeof handleName === 'string') {
46
+ if (handleName !== undefined) {
47
47
  finalHandleName = (0, shared_1.convertUsageLocationsHandleToEmittableHandle)(handleName, cleanBasename);
48
48
  }
49
49
  else {
@@ -57,13 +57,13 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
57
57
  plainEntrypointsConfig[`${type}Handles`][finalHandleName] = { src, rest, locations, type, lazyLoader, ...otherProps };
58
58
  }
59
59
  }
60
- PlainEntrypointsConfigFileGeneratorPlugin.addHandlesToHandleLists('script', Object.entries(plainEntrypointsConfig.scriptHandles)
60
+ PlainEntrypointsConfigFileGeneratorPlugin.addHandlesToHandleLists(compilation, 'script', Object.entries(plainEntrypointsConfig.scriptHandles)
61
61
  .toSorted(([a], [b]) => a.localeCompare(b)), handleLists);
62
- PlainEntrypointsConfigFileGeneratorPlugin.addHandlesToHandleLists('script_module', Object.entries(plainEntrypointsConfig.script_moduleHandles)
62
+ PlainEntrypointsConfigFileGeneratorPlugin.addHandlesToHandleLists(compilation, 'script_module', Object.entries(plainEntrypointsConfig.script_moduleHandles)
63
63
  .toSorted(([a], [b]) => a.localeCompare(b)), handleLists);
64
64
  const sortedStyleHandles = Object.entries(plainEntrypointsConfig.styleHandles)
65
65
  .toSorted(([a], [b]) => a.localeCompare(b));
66
- PlainEntrypointsConfigFileGeneratorPlugin.addHandlesToHandleLists('style', sortedStyleHandles, handleLists);
66
+ PlainEntrypointsConfigFileGeneratorPlugin.addHandlesToHandleLists(compilation, 'style', sortedStyleHandles, handleLists);
67
67
  const callItemSorter = (a, b) => a.handle.localeCompare(b.handle);
68
68
  for (const list of Object.values(handleLists)) {
69
69
  list.sort(callItemSorter);
@@ -121,8 +121,10 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
121
121
  writer.return(editorSettings);
122
122
  }, { functionArgParameters: [editorSettings], accountForAlreadyDoing: false });
123
123
  }
124
- const lazyLoadedHandles = Object.values(handleLists)
125
- .flatMap(handleList => handleList.map(handle => [handle.data.lazyLoader, handle]))
124
+ // Only the register and inline lists, which are mutually exclusive, so each handle is collected
125
+ // exactly once. Flattening every list instead would collect a handle once per location it declares.
126
+ const lazyLoadedHandles = [...handleLists.register, ...handleLists.inline]
127
+ .map(handle => [handle.data.lazyLoader, handle])
126
128
  .filter((entry) => entry[0] !== undefined)
127
129
  .sort((a, b) => a[0].localeCompare(b[0]) || a[1].priority - b[1].priority);
128
130
  if (lazyLoadedHandles.length > 0) {
@@ -142,7 +144,7 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
142
144
  }
143
145
  (0, shared_1.emitPHPWriterAsAsset)(writer, compilation, "plain-entrypoints-loader.php");
144
146
  }
145
- static addHandlesToHandleLists(type, handles, handleLists) {
147
+ static addHandlesToHandleLists(compilation, type, handles, handleLists) {
146
148
  const defaultPriority = 10;
147
149
  for (const [handle, data] of handles) {
148
150
  if (data.locations.register !== false) {
@@ -151,6 +153,19 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
151
153
  else if (data.inlinedAsset !== undefined) {
152
154
  handleLists.inline.push({ handle, type, data, priority: typeof data.locations.inline === 'number' ? data.locations.inline : 10 });
153
155
  }
156
+ if (data.lazyLoader !== undefined) {
157
+ // A lazily loaded handle is enqueued by its generated function and never by an action, so it
158
+ // is deliberately kept out of the location lists. Those lists are also what the lazy function
159
+ // is built from, and a handle present in several of them would be enqueued once per list.
160
+ const declaredLocations = (0, shared_1.constantKeys)(shared_1.standardLocationNamesMeta)
161
+ .filter(location => data.locations[location] || typeof data.locations[location] === 'number');
162
+ if (declaredLocations.length > 0) {
163
+ const locText = declaredLocations.length === 1 ? "location" : "locations";
164
+ compilation.warnings.push((0, shared_1.newWebpackErrorForFile)(`The handle "${handle}" declares both a lazyLoader (${data.lazyLoader}) and the ${declaredLocations.join(", ")} ${locText}.`
165
+ + ` It will only be enqueued by ${data.lazyLoader}(); remove either the ${locText} or the lazyLoader.`, data.src));
166
+ }
167
+ continue;
168
+ }
154
169
  for (const location of (0, shared_1.constantKeys)(shared_1.standardLocationNamesMeta)) {
155
170
  let priority, hook_name;
156
171
  const dataLocation = data.locations[location];
@@ -194,7 +209,6 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
194
209
  }
195
210
  }
196
211
  static appendEnqueuingHandleLists(writer, handleLists) {
197
- const lazyLoadableHandles = {};
198
212
  for (const [sln, { action, supports_hook_name, additional_action_code }] of (0, shared_1.constantEntries)(shared_1.standardLocationNamesMeta)) {
199
213
  const handleList = handleLists[sln];
200
214
  if (handleList.length > 0) {
@@ -202,11 +216,14 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
202
216
  for (const [priority, prioritizedHandleList] of PlainEntrypointsConfigFileGeneratorPlugin.separateHandleListByPriority(handleList)) {
203
217
  if (supports_hook_name) {
204
218
  const hookNamedHandleLists = PlainEntrypointsConfigFileGeneratorPlugin.separateHandleListByHookName(prioritizedHandleList);
205
- if (!(0, shared_1.arrayIsLength)(hookNamedHandleLists, 1) || hookNamedHandleLists[0][0] !== "") {
219
+ if (hookNamedHandleLists.named.length > 0) {
206
220
  const hook_name_var = new expressions_1.Var("hook_name", "?string");
207
221
  writer.action(action, writer => {
208
222
  additional_action_code?.before?.(writer);
209
- for (const [hook_name, handles] of hookNamedHandleLists) {
223
+ for (const handle of hookNamedHandleLists.global) {
224
+ writer.call(`wp_enqueue_${handle.type}`, [handle.handle]);
225
+ }
226
+ for (const [hook_name, handles] of hookNamedHandleLists.named) {
210
227
  writer.if(expressions_1.Op.binary(hook_name_var, " === ", hook_name));
211
228
  for (const handle of handles) {
212
229
  writer.call(`wp_enqueue_${handle.type}`, [handle.handle]);
@@ -228,7 +245,6 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
228
245
  }
229
246
  }
230
247
  }
231
- return lazyLoadableHandles;
232
248
  }
233
249
  static separateHandleListByPriority(handleItems) {
234
250
  const lists = new Map();
@@ -249,9 +265,16 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
249
265
  * @private
250
266
  */
251
267
  static separateHandleListByHookName(handleItems) {
268
+ const global = [];
252
269
  const lists = new Map();
253
270
  for (const handleItem of handleItems) {
254
271
  const hook_name = handleItem.hook_name ?? "";
272
+ if (hook_name === "" || (Array.isArray(hook_name) && hook_name.includes(""))) {
273
+ // We're going to be handling global hooks (aka those without a name restriction) separately anyway,
274
+ // so there's no sense in mixing them in with the non-global ones
275
+ global.push(handleItem);
276
+ continue;
277
+ }
255
278
  for (const hn of Array.isArray(hook_name) ? hook_name : [hook_name]) {
256
279
  const list = lists.get(hn);
257
280
  if (list) {
@@ -262,15 +285,7 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
262
285
  }
263
286
  }
264
287
  }
265
- return lists.entries().toArray().sort((a, b) => {
266
- if (a[0] === "") {
267
- return b[0] === "" ? 0 : -1;
268
- }
269
- else if (b[0] === "") {
270
- return 1;
271
- }
272
- return a[0].localeCompare(b[0]);
273
- });
288
+ return { global, named: lists.entries().toArray().sort((a, b) => a[0].localeCompare(b[0])) };
274
289
  }
275
290
  attachUniquePhase(compilation) {
276
291
  compilation.hooks.processAssets.tapPromise({ name: this.constructor.name, stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_REPORT }, async () => {
@@ -1,7 +1,4 @@
1
- import DependencyExtractionWebpackPlugin from "@wordpress/dependency-extraction-webpack-plugin";
2
- import { Externals } from "../shared";
1
+ import type { Externals } from "../shared";
2
+ import { EnhancedDependencyExtractionWebpackPlugin } from "../wordpress-ports/dependency-extraction-webpack-plugin";
3
3
  import type { Configuration } from "webpack";
4
- export declare function makeDependencyExtractionPlugin(externals: Externals | undefined, assumeGlobalizedPlauditLibraries: boolean, wantsGroupedDepData: boolean, externalizing: Required<Configuration>['output']['library']): {
5
- instance: DependencyExtractionWebpackPlugin;
6
- addExternalizedDep: (dep: string) => void;
7
- };
4
+ export declare function makeDependencyExtractionPlugin(externals: Externals | undefined, assumeGlobalizedPlauditLibraries: boolean, externalizing: Required<Configuration>['output']['library']): EnhancedDependencyExtractionWebpackPlugin;
@@ -1,19 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.makeDependencyExtractionPlugin = makeDependencyExtractionPlugin;
7
- const dependency_extraction_webpack_plugin_1 = __importDefault(require("@wordpress/dependency-extraction-webpack-plugin"));
8
- function makeDependencyExtractionPlugin(externals, assumeGlobalizedPlauditLibraries, wantsGroupedDepData, externalizing) {
9
- const baseProps = makeDependencyExtractionPluginProps(externals, assumeGlobalizedPlauditLibraries, externalizing);
10
- const instance = new dependency_extraction_webpack_plugin_1.default(wantsGroupedDepData ? { ...baseProps, outputFormat: "json", combineAssets: true } : baseProps);
11
- return {
12
- instance,
13
- addExternalizedDep: (dep) => {
14
- instance.externalizedDeps.add(dep);
15
- }
16
- };
4
+ const dependency_extraction_webpack_plugin_1 = require("../wordpress-ports/dependency-extraction-webpack-plugin");
5
+ const util_1 = require("../wordpress-ports/dependency-extraction-webpack-plugin/util");
6
+ function makeDependencyExtractionPlugin(externals, assumeGlobalizedPlauditLibraries, externalizing) {
7
+ return new dependency_extraction_webpack_plugin_1.EnhancedDependencyExtractionWebpackPlugin(makeDependencyExtractionPluginProps(externals, assumeGlobalizedPlauditLibraries, externalizing));
17
8
  }
18
9
  function makeDependencyExtractionPluginProps(externals, assumeGlobalizedPlauditLibraries, externalizing) {
19
10
  if (!externals) {
@@ -93,7 +84,7 @@ const EXTERNALIZABLE_PLAUDIT_LIBRARIES = {
93
84
  ...Object.fromEntries(['@plaudit/frontend-apis', '@plaudit/library-extensions', '@plaudit/block-supports', '@plaudit/gutenberg-api-extensions']
94
85
  .map(req => {
95
86
  return [req, {
96
- import: ['plaudit', camelCaseDash(req.substring(PLAUDIT_NAMESPACE.length))],
87
+ import: ['plaudit', (0, util_1.camelCaseDash)(req.substring(PLAUDIT_NAMESPACE.length))],
97
88
  handle: `plaudit-${req.substring(PLAUDIT_NAMESPACE.length)}`
98
89
  }];
99
90
  })),
@@ -116,12 +107,6 @@ function normalizeRequest(request) {
116
107
  function curryRequestHandlers(primary, secondary) {
117
108
  return (...args) => primary(...args) ?? secondary(...args);
118
109
  }
119
- /**
120
- * Copied from @wordpress/dependency-extraction-webpack-plugin for consistency
121
- */
122
- function camelCaseDash(string) {
123
- return string.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
124
- }
125
110
  function handleExternalizingEqualsPossibleExternal(possibleExternal, ext) {
126
111
  if (!ext || !possibleExternal) {
127
112
  return possibleExternal;
@@ -151,7 +136,9 @@ function handleExternalizingEqualsPossibleExternal(possibleExternal, ext) {
151
136
  }
152
137
  else {
153
138
  for (const key of ['amd', 'commonjs', 'root']) {
154
- if (key in ext && handleExternalizingEqualsPossibleExternal(possibleExternal, ext[key])) {
139
+ // The recursion returns the external when it should be kept and undefined when it matched, so
140
+ // only an undefined result means this umd name is the thing being built and must not externalize.
141
+ if (key in ext && handleExternalizingEqualsPossibleExternal(possibleExternal, ext[key]) === undefined) {
155
142
  return undefined;
156
143
  }
157
144
  }
package/dist/shared.d.ts CHANGED
@@ -5,17 +5,20 @@ import { type AssetInfo, type Compilation, type Configuration, type Entrypoint,
5
5
  import { SourceType } from "./utils/entrypoint-resolution-logic";
6
6
  import type { NormalizedEnqueuingControlFlags } from "./utils/path-query-and-related-helpers";
7
7
  export * from "./utils/entrypoint-resolution-logic";
8
- export type WPModuleDependency = {
8
+ export type WPModuleDependency = string | {
9
9
  id: string;
10
10
  import?: string;
11
11
  };
12
- export type ParsedAssetsJson = Record<string, {
13
- dependencies: string[];
12
+ export type ParsedAssetJson = {
13
+ dependencies: WPModuleDependency[];
14
+ module_dependencies?: WPModuleDependency[];
14
15
  version: string;
15
- module_dependencies?: (string | WPModuleDependency)[];
16
- }>;
16
+ type?: 'module';
17
+ handle?: string;
18
+ };
19
+ export type ParsedAssetsJson = Record<string, ParsedAssetJson>;
17
20
  export declare function isParsedAssetsJson(thing: any): thing is ParsedAssetsJson;
18
- type BaseRestType = [/* dependencies: */ string[], /* version: */ string];
21
+ type BaseRestType = [/* dependencies: */ WPModuleDependency[], /* version: */ string];
19
22
  export type AssetMetaSegment = {
20
23
  inlinedAsset: InlinedAsset;
21
24
  assetContents: string;
@@ -28,7 +31,7 @@ export type ScriptArgsObject = {
28
31
  strategy?: 'defer' | 'async';
29
32
  in_footer?: boolean;
30
33
  fetchpriority?: 'auto' | 'low' | 'high';
31
- module_dependencies?: (string | WPModuleDependency)[];
34
+ module_dependencies?: WPModuleDependency[];
32
35
  };
33
36
  export type InlinedAsset = {
34
37
  position?: 'before' | 'after';
@@ -118,7 +121,7 @@ export type AdvancedOutputConfig = {
118
121
  directoryLayout?: SourceType;
119
122
  assumeGlobalizedPlauditLibraries?: boolean;
120
123
  externalize?: Required<Configuration>['output']['library'];
121
- bundleAnalyzer?: boolean;
124
+ bundleAnalyzer?: boolean | 'no-auto-open';
122
125
  locations?: UsageLocations | UsageLocations['handle'];
123
126
  lazyLoader?: string;
124
127
  pathQueryParameters?: PathQueryParameters;
@@ -143,7 +146,6 @@ export type PlauditWordpressWebpackConfig = {
143
146
  externals?: Externals;
144
147
  assumeGlobalizedPlauditLibraries?: boolean;
145
148
  processTranslationConfigs?: boolean;
146
- combineAssetMetadata?: boolean;
147
149
  useWebpackResourceFiltering?: boolean;
148
150
  outputDir?: string;
149
151
  extensionsVersion?: 1 | 2 | 3;
@@ -170,7 +172,6 @@ export declare function applyStandards(config: PlauditWordpressWebpackConfig): {
170
172
  readonly externals?: Externals;
171
173
  readonly assumeGlobalizedPlauditLibraries?: boolean;
172
174
  readonly processTranslationConfigs?: boolean;
173
- readonly combineAssetMetadata?: boolean;
174
175
  useWebpackResourceFiltering?: boolean;
175
176
  outputDir?: string;
176
177
  extensionsVersion?: 1 | 2 | 3;
@@ -228,7 +229,7 @@ export declare const styleExtension: RegExp;
228
229
  export declare function scriptOrStyleTest(entryPath: string, scriptExtension: RegExp): "script" | "style" | "";
229
230
  export declare function isStyleField(field: string): field is 'style' | 'viewStyle' | 'editorStyle';
230
231
  export declare function isScriptModuleField(field: string): field is 'viewScriptModule';
231
- export declare function mergeModuleDependencyArrays(a: (string | WPModuleDependency)[] | undefined, b: (string | WPModuleDependency)[] | undefined): (string | WPModuleDependency)[] | undefined;
232
+ export declare function mergeModuleDependencyArrays(a: WPModuleDependency[] | undefined, b: WPModuleDependency[] | undefined): WPModuleDependency[] | undefined;
232
233
  export declare function getHandleGroup(field: string): 'styleHandles' | 'scriptHandles' | 'scriptModuleHandles';
233
234
  export type StripFirstTwoItems<A extends any[]> = A extends [any, any, ...rest: infer R] ? R : never;
234
235
  export declare function hasAtLeastOneItem<T>(list: T[]): list is [T, ...T[]];
@@ -254,7 +255,7 @@ export declare function getParsedAssetsJsonDataForEntrypoint(compilation: Compil
254
255
  assetData: ParsedAssetsJson[string];
255
256
  } | undefined;
256
257
  export declare function emitPHPWriterAsAsset(writer: PHPWriter, compilation: Compilation, file: string, assetInfo?: AssetInfo): void;
257
- export declare function dedent(text: TemplateStringsArray): string;
258
+ export declare function dedent(text: TemplateStringsArray, ...values: unknown[]): string;
258
259
  export declare function resolveLegacyBlockScriptsInFolder(folder: string): string[];
259
260
  /**
260
261
  * @param compilation
@@ -272,3 +273,7 @@ export declare function synchronousDirIterator(dirPath: string): Generator<impor
272
273
  * @link https://dev.to/jdbar
273
274
  */
274
275
  export declare function instanceOfNodeError<T extends new (...args: any) => Error>(value: unknown, errorType: T): value is InstanceType<T> & NodeJS.ErrnoException;
276
+ /**
277
+ * This tests if the name starts with a "~", is equal to "example" or "examples", or includes "example" as a name segment
278
+ */
279
+ export declare function shouldBeSkipped(name: string): boolean;
package/dist/shared.js CHANGED
@@ -53,6 +53,7 @@ exports.appendAddInlineAssetCall = appendAddInlineAssetCall;
53
53
  exports.wrapInlineAssetContents = wrapInlineAssetContents;
54
54
  exports.synchronousDirIterator = synchronousDirIterator;
55
55
  exports.instanceOfNodeError = instanceOfNodeError;
56
+ exports.shouldBeSkipped = shouldBeSkipped;
56
57
  const node_crypto_1 = require("node:crypto");
57
58
  const node_fs_1 = require("node:fs");
58
59
  const promises_1 = require("node:fs/promises");
@@ -84,7 +85,7 @@ exports.standardLocationNamesMeta = {
84
85
  analytics: { action: "plaudit_enqueue_analytics" },
85
86
  };
86
87
  function isStandardLocationName(name) {
87
- return name in exports.standardLocationNamesMeta;
88
+ return Object.hasOwn(exports.standardLocationNamesMeta, name);
88
89
  }
89
90
  function isRegisterScriptArgsShorthandName(name) {
90
91
  return ['lazy', 'eager', 'inline'].includes(name);
@@ -270,18 +271,10 @@ function emitResolveBaseUriFunction(writer) {
270
271
  }
271
272
  function getAssetsJson(compilation) {
272
273
  const asset = compilation.getAsset("assets.json");
273
- if (!asset) {
274
- throw new Error("assets.json is unexpectedly missing");
275
- }
276
- if (isParsedAssetsJson(asset.info["parsed-assets-json"])) {
274
+ if (asset && isParsedAssetsJson(asset.info["parsed-assets-json"])) {
277
275
  return asset.info["parsed-assets-json"];
278
276
  }
279
- const parsedAssetJson = JSON.parse(asset.source.source().toString('utf-8'));
280
- if (!isParsedAssetsJson(parsedAssetJson)) {
281
- throw new Error("assets.json is does not match the RawAssetData format");
282
- }
283
- compilation.updateAsset("assets.json", s => s, a => ({ ...a, "parsed-assets-json": parsedAssetJson }));
284
- return parsedAssetJson;
277
+ throw new Error("assets.json is unexpectedly missing");
285
278
  }
286
279
  function getParsedAssetsJsonDataForEntrypoint(compilation, entrypoint, metadata) {
287
280
  const dest = metadata.dest;
@@ -321,13 +314,24 @@ function emitPHPWriterAsAsset(writer, compilation, file, assetInfo) {
321
314
  const contents = writer.toString() + "\n";
322
315
  compilation[file in compilation.assets ? 'updateAsset' : 'emitAsset'](file, new webpack_1.sources.RawSource(contents), { size: Buffer.byteLength(contents), ...assetInfo });
323
316
  }
324
- function dedent(text) {
325
- const actualText = text.join("\n");
326
- const firstLineIndent = actualText.split("\r?\n").find(line => line.length > 0)?.match(/^\s+/)?.[0];
327
- if (!firstLineIndent) {
328
- return actualText;
317
+ function dedent(text, ...values) {
318
+ const lines = text.reduce((result, part, i) => result + String(values[i - 1]) + part).split(/\r?\n/);
319
+ // A template that opens and closes on its own line frames the content with two throwaway lines
320
+ if (lines[0]?.trim() === "") {
321
+ lines.shift();
322
+ }
323
+ if (lines.length > 1 && lines.at(-1)?.trim() === "") {
324
+ lines.pop();
329
325
  }
330
- return actualText.replaceAll(new RegExp("^" + firstLineIndent, 'mg'), "").replace(/\n{2}$/, "\n");
326
+ // Only the lines with content get a say in the indent, and a line break must never count as one
327
+ const indent = lines.filter(line => line.trim() !== "")
328
+ .reduce((shortest, line) => {
329
+ const lineIndent = /^[ \t]*/.exec(line)?.[0] ?? "";
330
+ return shortest === undefined || lineIndent.length < shortest.length ? lineIndent : shortest;
331
+ }, undefined);
332
+ return indent
333
+ ? lines.map(line => line.startsWith(indent) ? line.substring(indent.length) : line).join("\n")
334
+ : lines.join("\n");
331
335
  }
332
336
  function resolveLegacyBlockScriptsInFolder(folder) {
333
337
  const blockScriptEntrypoints = [];
@@ -390,3 +394,9 @@ function* synchronousDirIterator(dirPath) {
390
394
  function instanceOfNodeError(value, errorType) {
391
395
  return !!value && value instanceof errorType;
392
396
  }
397
+ /**
398
+ * This tests if the name starts with a "~", is equal to "example" or "examples", or includes "example" as a name segment
399
+ */
400
+ function shouldBeSkipped(name) {
401
+ return name.startsWith("~") || name === "example" || name === "examples" || name.startsWith("example.") || name.includes(".example.");
402
+ }
@@ -126,7 +126,7 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
126
126
  const seenExplicitHandles = { scriptHandles: {}, styleHandles: {}, scriptModuleHandles: {} };
127
127
  const loadingEntrypoints = [];
128
128
  for await (const dirent of await (0, promises_1.opendir)(srcRoot)) {
129
- if (!dirent.isDirectory() || dirent.name.startsWith("~")) {
129
+ if (!dirent.isDirectory() || (0, shared_1.shouldBeSkipped)(dirent.name)) {
130
130
  continue;
131
131
  }
132
132
  const dir = joinPossiblyAbsolutePaths(srcRoot, dirent.name);
@@ -233,7 +233,10 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
233
233
  const resolvedBlockEntrypoints = presentEntrypoints
234
234
  .map(presentEntrypoint => {
235
235
  const overallSource = (0, node_path_1.dirname)(blockJsonOrigin);
236
- const overallSourceRelativeName = (0, node_path_1.relative)(overallSource, (0, node_path_1.normalize)((0, node_path_1.join)(blockJsonOrigin, (0, node_path_1.relative)(overallSource, presentEntrypoint.extensionlessExpectedSrc))));
236
+ // The handle suffix is the asset's extensionless name relative to its own block, so it
237
+ // has to come off the real on-disk path rather than the destination-relative expected src.
238
+ const parsedAbsoluteSrc = (0, node_path_1.parse)(presentEntrypoint.absoluteSrc);
239
+ const overallSourceRelativeName = (0, node_path_1.normalize)((0, node_path_1.join)((0, node_path_1.relative)(overallSource, parsedAbsoluteSrc.dir), parsedAbsoluteSrc.name));
237
240
  const handleSuffix = (0, shared_1.convertUsageLocationsHandleToEmittableHandle)(dest.locations.handle, overallSourceRelativeName);
238
241
  const handleGroup = (0, shared_1.getHandleGroup)(presentEntrypoint.entrypointField);
239
242
  if (presentEntrypoint.handle) {
@@ -277,15 +280,15 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
277
280
  const packageJsonOrigin = (0, node_path_1.join)(dir, 'package.json');
278
281
  const packageJson = JSON.parse(await (0, promises_1.readFile)(packageJsonOrigin, 'utf8'));
279
282
  if (packageJson['main']) {
280
- rawEntrypoints.push(...mapToRealEntrypoints(packageJson['main'], dir, commonConfig.scriptExtension.test, { dest, entrypointJsonOrigin: packageJsonOrigin }));
283
+ rawEntrypoints.push(...mapToRealEntrypoints(packageJson['main'], dir, s => commonConfig.scriptExtension.test(s), { dest, entrypointJsonOrigin: packageJsonOrigin }));
281
284
  }
282
285
  if (!processingModules && packageJson['style']) {
283
- rawEntrypoints.push(...mapToRealEntrypoints(packageJson['style'], dir, shared_1.styleExtension.test, { dest, entrypointJsonOrigin: packageJsonOrigin }));
286
+ rawEntrypoints.push(...mapToRealEntrypoints(packageJson['style'], dir, s => shared_1.styleExtension.test(s), { dest, entrypointJsonOrigin: packageJsonOrigin }));
284
287
  }
285
288
  }
286
289
  catch (e) {
287
290
  try {
288
- rawEntrypoints.push(...parseEntrypointsJSON(dir, dest, ep => commonConfig.scriptExtension.test(ep) || shared_1.styleExtension.test(ep)));
291
+ rawEntrypoints.push(...parseEntrypointsJSON(dir, dest, ep => commonConfig.scriptExtension.test(ep) || (!processingModules && shared_1.styleExtension.test(ep))));
289
292
  }
290
293
  catch (e) {
291
294
  // This just means that the directory doesn't contain any declared entrypoints.