@plaudit/webpack-extensions 2.85.3 → 2.86.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
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.86.0] - 2026-03-16
9
+ ### Added
10
+ - Support for `Location-Encoding Filenames`
11
+ - Support for standardized flag sets
12
+
8
13
  ## [2.85.3] - 2026-03-06
9
14
  ### Fixed
10
15
  - `defer` and `async` not being supported strategies in some pipelines
package/USER-GUIDE.md CHANGED
@@ -28,6 +28,11 @@
28
28
  * [Pattern 4: Block Editor Styles](#pattern-4-block-editor-styles)
29
29
  * [Reference](#reference)
30
30
  * [Root Options](#root-options)
31
+ * [Standard Flag Sets](#standard-flag-sets)
32
+ * [Location-Encoding Filenames](#location-encoding-filenames)
33
+ * [Enqueuing Location](#enqueuing-location)
34
+ * [Examples](#examples-1)
35
+ * [Block- and Plain-compatible](#block--and-plain-compatible)
31
36
  * [Per-Entrypoint Options](#per-entrypoint-options)
32
37
  * [Path Queries](#path-queries)
33
38
  * [Boolean-form](#boolean-form)
@@ -138,85 +143,26 @@ The config tells webpack **what** to build and **where** to load it in WordPress
138
143
 
139
144
  ```javascript
140
145
  module.exports = require("@plaudit/webpack-extensions/wordpress-scripts-wrapper")({
141
- src: {
142
- // Build all blocks in src/blocks/
143
- "blocks": true,
144
-
145
- // Build block extensions from src/extensions/
146
- "extensions": {directoryLayout: 'extensions'},
147
-
148
- // Auto-load in the head on frontend
149
- "site/index-header.ts": {
150
- locations: {
151
- clientView: true,
152
- registerScriptArgs: false
153
- }
154
- },
155
-
156
- // Auto-load at the bottom of the body on frontend
157
- "site/index-footer.ts": {
158
- locations: {
159
- clientView: true,
160
- registerScriptArgs: 'lazy'
161
- }
162
- },
163
- // Auto-load in admin
164
- "site/wp-admin.ts": {
165
- locations: {
166
- admin: true
167
- }
168
- },
169
- // Auto-load in admin
170
- "site/wp-admin.pcss": {
171
- locations: {
172
- admin: true
173
- }
174
- },
175
- // Auto-load in the block editor
176
- "site/block-editor.pcss": {
177
- locations: {
178
- clientEditor: true
179
- }
180
- },
181
- // Auto-load in the head on frontend
182
- "site/public.pcss": {
183
- locations: {
184
- clientView: true
185
- }
186
- },
187
- // A script that can be enqueued via its handle, but is not autoloaded
188
- "site/manually-loaded.ts": {
189
- locations: "plaudit-theme/manually-loaded-script"
190
- },
191
- // A script that will be inlined into the footer
192
- "site/inlined-script.ts?inline=true": {
193
- locations: "plaudit-theme/inlined-script"
194
- }
195
- },
196
- useWebpackResourceFiltering: true, // Always use this!
197
- extensionsVersion: 3,
198
- plainEntrypointsVersion: 2,
199
- srcDir: "src", // Always use this!
200
- outputDir: "dist", // Always use this!
201
- useUnifiedLoader: true, // Always use this!
146
+ standard: '2026-03-16',
202
147
  variables: {
203
148
  font_size: 16 // Required for pxAsRem to work; replace the 16 with your site's base font size
204
149
  }
205
150
  });
206
151
  ```
152
+ The `standard` key is shorthand to the standard set of flags as of the given date. See the [Standard Flag Sets](#standard-flag-sets) section for details.
207
153
 
208
154
  ### 2. Set Up Your File Structure
209
155
 
210
156
  **Blocks** (subdirectories with block.json):
211
- - **Config:** `'blocks': true`
157
+ - **Config:** optional so long as the directory name is `blocks`; `'<dirname>': true` otherwise
212
158
  - **File Structure:** [See Here](#file-structure)
213
159
 
214
160
  **Extensions** (flat files that modify existing blocks):
215
- - **Config:** `'extensions': {directoryLayout: 'extensions'}`
161
+ - **Config:** optional so long as the directory name is `extensions`; `'<dirname>': {directoryLayout: 'extensions'}` otherwise
216
162
  - **File Structure:** [See Here](#file-structure-1)
217
163
 
218
164
  **Other Files** (scripts, styles):
219
- - **Config:** List each file individually in `src` object
165
+ - **Config:** Optional so long as you are using [Location-Encoding Filenames](#location-encoding-filenames); List each file that does not encode its own location in `src` object
220
166
  - **File Structure:** [See Here](#file-structure-2)
221
167
 
222
168
  ### 3. Include unified-loader.php in Your Plugin
@@ -357,21 +303,93 @@ src: {
357
303
  # Reference
358
304
 
359
305
  ## Root Options
360
- | Option | Type | Default | Description |
361
- |------------------------------------|-----------|--------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
362
- | `src` | `object` | **Required** | Files/directories to process (see the next section for what you can put in here) |
363
- | `srcDir` | `string` | `""` | Source directory (**Required** on new sites) |
364
- | `outputDir` | `string` | `""` | Output directory (**Required** on new sites) |
365
- | `useUnifiedLoader` | `boolean` | `false` | Generate unified-loader.php (always set to `true` on new sites!) |
366
- | `useWebpackResourceFiltering` | `boolean` | `false` | Generates versioned copies of images, fonts, etc (always set to `true` on new sites!) |
367
- | `plainEntrypointsVersion` | `1\|2` | `1` | Use 2 for new sites |
368
- | `extensionsVersion` | `1\|2\|3` | `1` | Use 3 for new sites |
369
- | `verbose` | `boolean` | `false` | Verbose logging |
370
- | `variables` | `object` | Auto | CSS/JS variables. If a variables.js file is present, this will automatically load from that file |
371
- | `onlyRunPostCSSOnPCSS` | `boolean` | `false` | If true, the PostCSS processor will not be run on CSS files. This is true for all new sites and can be safely enabled on most sites if desired. |
372
- | `assumeGlobalizedPlauditLibraries` | `boolean` | `true` | **[Advanced]** When `false`, normally-externalized plaudit libraries will be included.<br>**DO NOT USE THIS. IF YOU SEE IT BEING USED, CONTACT JOSH** |
373
- | `externals` | `object` | - | **[Advanced]** Allows dependencies to be marked as external. This prevents them from being included in the bundle under the assumption that they will be provided via an alternate mechanism |
374
- | `targetHandlePrefix` | `string` | Auto | **[Legacy]** Prefix for handles. Auto-detected from composer.json if omitted. Do not set this unless absolutely necessary |
306
+ | Option | Type | Default | Description |
307
+ |------------------------------------|-----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
308
+ | `standard` | `enum` | - | An identifier for the standard set of flags as of a given date. See the [Standards Flag Sets](#standard-flag-sets) section for values |
309
+ | `src` | `object` | `{}` | Files/directories to process (see the next section for what you can put in here) |
310
+ | `srcDir` | `string` | `""` | Source directory (**Required** on new sites) |
311
+ | `outputDir` | `string` | `""` | Output directory (**Required** on new sites) |
312
+ | `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!) |
314
+ | `plainEntrypointsVersion` | `1\|2` | `1` | Use 2 for new sites |
315
+ | `extensionsVersion` | `1\|2\|3` | `1` | Use 3 for new sites |
316
+ | `verbose` | `boolean` | `false` | Verbose logging |
317
+ | `variables` | `object` | Auto | CSS/JS variables. If a variables.js file is present, this will automatically load from that file |
318
+ | `onlyRunPostCSSOnPCSS` | `boolean` | `false` | If true, the PostCSS processor will not be run on CSS files. This is true for all new sites and can be safely enabled on most sites if desired. |
319
+ | `assumeGlobalizedPlauditLibraries` | `boolean` | `true` | **[Advanced]** When `false`, normally-externalized plaudit libraries will be included.<br>**DO NOT USE THIS. IF YOU SEE IT BEING USED, CONTACT JOSH** |
320
+ | `externals` | `object` | - | **[Advanced]** Allows dependencies to be marked as external. This prevents them from being included in the bundle under the assumption that they will be provided via an alternate mechanism |
321
+ | `targetHandlePrefix` | `string` | Auto | **[Legacy]** Prefix for handles. Auto-detected from composer.json if omitted. Do not set this unless absolutely necessary |
322
+
323
+ ### Standard Flag Sets
324
+ Each flag set is identified by the date that it was added to the standards set. Individual values can be overridden by specifying them in the config file
325
+
326
+ **2026-03-13**
327
+ ```javascript
328
+ {
329
+ useWebpackResourceFiltering: true,
330
+ extensionsVersion: 3,
331
+ plainEntrypointsVersion: 2,
332
+ srcDir: "src",
333
+ outputDir: "dist",
334
+ useUnifiedLoader: true,
335
+ onlyRunPostCSSOnPCSS: true,
336
+ }
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Location-Encoding Filenames
342
+ This section covers how asset emission locations can be encoded in filenames.
343
+ While every common flag is accounted for in this schema, there are some things that cannot be encoded in filenames that will require enumeration in the `webpack.config.js` file's `src` object.
344
+
345
+ Location-encoding filenames are composed of two or more `.`-separated segments (the file extension being one of those segments).
346
+ The following is a brief list of notes followed by a breakdown of the individual segments:
347
+ - The only required segments are the location and type segments (the type segment is the file extension).
348
+ - So long as there is at least one location and type segment (the type segment MUST be last), "invalid" segments are allowed (this is for the purposes of giving human-readable names to files)
349
+ - Each file can enumerate any combination enqueuing locations
350
+ - The first item is special in that, if it encodes a location, in can optionally be suffixed with a type override (see the [Enqueuing Location](#enqueuing-location) section for more details)
351
+
352
+ Notes on how to read the following table:
353
+ - Brackets in the token column mean "optional section"
354
+ - `(-|=)` means "this can be either a dash or an equal sign"
355
+ - Pipe-separated items in parentheses means "any one of these items can go here"
356
+
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) |
367
+
368
+ ### Enqueuing Location
369
+ If this is the first token, it can be suffixed with `-script`, `-style`, or `-script-module` to override the type.
370
+ Specifying the type override alone is interpreted as `both-(Type Override)` for compatibility with how blocks expect assets to be named.
371
+
372
+ Blocks do not support anything other than `view`, `editor`, and `both` (typically written as `script` or `style`).
373
+
374
+ **Valid Values**:
375
+
376
+ | Token | Meaning |
377
+ |-------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
378
+ | `view`, `client-view`, `clientView` | Enqueue the asset on the frontend (the part of the site seen by visitors) |
379
+ | `editor`, `client-editor`, `clientEditor` | Enqueue the asset in the *display* portion of the block editor |
380
+ | `both` | Shorthand for `view.editor` |
381
+ | `block-assets`, `block-assets` | Enqueue the asset in the *wrapper* portion of the block editor |
382
+ | `admin` | Enqueue the asset in the admin area |
383
+ | `login` | Enqueue the asset on the standard login page |
384
+ | `customizer` | Enqueue the asset in the legacy customizer interface |
385
+ | `analytics` | Enqueue the asset when analytics scripts have been accepted by the visitor (this "location" is unique to Plaudit's systems) |
386
+ | `register` | Register the asset *without* enqueuing it anywhere (this is primarily useful when localizing the asset) |
387
+
388
+ ### Examples
389
+ #### Block- and Plain-compatible
390
+ - **A view script**: `view.ts`
391
+ - **An editor script**: `editor.ts`
392
+ - **A view and editor script**: `script.ts`
375
393
 
376
394
  ---
377
395
 
@@ -380,6 +398,7 @@ This section covers the shared options for entrypoints. See the [Entrypoint Type
380
398
 
381
399
  ### Path Queries
382
400
  - These are URL-style queries added to the end of entrypoint *source* paths and are used to set entrypoint-specific properties in Plain and Block contexts.
401
+ - If you find yourself using these, consider switching to [Location-Encoding Filenames](#location-encoding-filenames)
383
402
  - At present, this is only being used to configure inlining, but expansion to other systems is being considered
384
403
  - The available options are: `strategy`, `inline`, `in_footer`, `fetchpriority`, and `position`
385
404
 
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.EnhancedBlockJSONPlugin = void 0;
7
7
  const node_crypto_1 = __importDefault(require("node:crypto"));
8
- const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const promises_1 = require("node:fs/promises");
9
9
  const node_fs_1 = __importDefault(require("node:fs"));
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const AbstractBiPhasicGroupAndEntryPlugin_1 = require("./AbstractBiPhasicGroupAndEntryPlugin");
@@ -37,8 +37,7 @@ class EnhancedBlockJSONPlugin extends AbstractBiPhasicGroupAndEntryPlugin_1.Abst
37
37
  if (asset) {
38
38
  const epBlockJson = entrypoint.name + ".json";
39
39
  if (!applicableBlockJsonFiles[epBlockJson]) {
40
- const blockJsonText = EnhancedBlockJSONPlugin.extractAssetSource(compilation, epBlockJson)
41
- ?? await promises_1.default.readFile(srcPath, 'utf-8');
40
+ const blockJsonText = EnhancedBlockJSONPlugin.extractAssetSource(compilation, epBlockJson) ?? await (0, promises_1.readFile)(srcPath, 'utf-8');
42
41
  if (!blockJsonText) {
43
42
  compilation.errors.push((0, shared_1.newWebpackErrorForFile)(`Unable to extract the source for ${epBlockJson}`, srcPath));
44
43
  continue;
@@ -66,10 +65,8 @@ class EnhancedBlockJSONPlugin extends AbstractBiPhasicGroupAndEntryPlugin_1.Abst
66
65
  .map(file => [file, (0, shared_1.scriptOrStyleTest)(file, shared_1.scriptExtension)])
67
66
  .filter((item) => item[1] !== '')
68
67
  .map(([file, assetType]) => {
69
- const pathQueryParameters = metadata.pathQueryParameters !== undefined || this.dest.pathQueryParameters !== undefined
70
- ? { ...metadata.pathQueryParameters, ...this.dest.pathQueryParameters }
71
- : undefined;
72
- const { scriptArgsObject, inlinedAsset } = (0, path_query_and_related_helpers_1.parseScriptArgsObjectFromPathQueryParameters)(compilation, file, pathQueryParameters);
68
+ const enqueuingFlags = (0, path_query_and_related_helpers_1.mergeTwoScriptEnqueuingControlFlagSets)(file, metadata.enqueuingFlags, this.dest.enqueuingFlags);
69
+ const { scriptArgsObject, inlinedAsset } = (0, path_query_and_related_helpers_1.convertEnqueuingControlFlagsToScriptArgsObject)(compilation, file, enqueuingFlags);
73
70
  const wasOriginallyAStyleField = (0, shared_1.isStyleField)(metadata.entrypointField);
74
71
  const outputPath = this.stripOffBlocksDestPrefix(file);
75
72
  if (wasOriginallyAStyleField !== (assetType === "style")) { // This means that the file is extracted
@@ -302,7 +302,8 @@ class PlainEntrypointsConfigFileGeneratorPlugin extends AbstractBiPhasicGroupAnd
302
302
  const isScript = type !== 'style';
303
303
  const dependencies = isScript === entrypointChunkIsScript ? assetData.dependencies : [];
304
304
  const { lazyLoader, locations } = this.dest;
305
- const { inlinedAsset, scriptArgsObject } = (0, path_query_and_related_helpers_1.parseScriptArgsObjectFromPathQueryParameters)(compilation, file, (0, path_query_and_related_helpers_1.mergeInPathQueryParameters)(file, locations.registerScriptArgs, this.dest.pathQueryParameters));
305
+ const { flags } = (0, path_query_and_related_helpers_1.unpackEnqueuingControlFlagsFromPathQueryParameters)(typeof locations.registerScriptArgs === 'object' ? locations.registerScriptArgs : { strategy: locations.registerScriptArgs }, file, "registerScriptArgs");
306
+ const { inlinedAsset, scriptArgsObject } = (0, path_query_and_related_helpers_1.convertEnqueuingControlFlagsToScriptArgsObject)(compilation, file, (0, path_query_and_related_helpers_1.mergeTwoScriptEnqueuingControlFlagSets)(file, flags, this.dest.enqueuingFlags));
306
307
  const rest = isScript && scriptArgsObject !== undefined ? [dependencies, assetData.version, scriptArgsObject] : [dependencies, assetData.version];
307
308
  const destPath = node_path_1.default.join(compilation.outputOptions.path, file);
308
309
  handles.push({
package/build/shared.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { PHPWriter } from "@plaudit/php-writer";
2
2
  import type { Options as PostcssFunctionsOptions } from "postcss-functions";
3
3
  import { AssetInfo, Compilation, Configuration, Entrypoint, WebpackError, Compiler } from "webpack";
4
+ import type { NormalizedEnqueuingControlFlags } from "./utils/path-query-and-related-helpers";
4
5
  export type ParsedAssetsJson = Record<string, {
5
6
  dependencies: string[];
6
7
  version: string;
@@ -54,16 +55,19 @@ export type StandardLocationNameMeta = {
54
55
  };
55
56
  };
56
57
  export type StandardLocationNames = keyof typeof standardLocationNamesMeta;
58
+ export declare function isStandardLocationName(name: string): name is StandardLocationNames;
57
59
  type LocationWithHookNameSupport = boolean | number | string | string[] | {
58
60
  hook_name?: string | string[];
59
61
  priority?: number;
60
62
  };
61
63
  type InputRegisterScriptArgs = ScriptArgsObject | boolean | 'lazy' | 'eager' | 'inline';
62
- export type UsageLocations = {
64
+ export declare function isRegisterScriptArgsShorthandName(name: string): name is Extract<InputRegisterScriptArgs, string>;
65
+ export type RealUsageLocations = {
63
66
  [K in StandardLocationNames]?: typeof standardLocationNamesMeta[K] extends {
64
67
  supports_hook_name: true;
65
68
  } ? LocationWithHookNameSupport : boolean | number;
66
- } & {
69
+ };
70
+ export type UsageLocations = RealUsageLocations & {
67
71
  register?: boolean | number;
68
72
  inline?: number;
69
73
  handle?: string | ((generatedHandle: string) => string);
@@ -74,7 +78,7 @@ export type NormalizedUsageLocations = Omit<UsageLocations, 'registerScriptArgs'
74
78
  };
75
79
  export declare function isNormalizedUsageLocations(usageLocations: UsageLocations): usageLocations is NormalizedUsageLocations;
76
80
  export declare function constantKeys<K extends string, V>(object: {
77
- [k in K]: V;
81
+ [k in K]?: V;
78
82
  }): K[];
79
83
  export declare function constantEntries<K extends string, V>(object: {
80
84
  [k in K]: V;
@@ -84,6 +88,7 @@ export declare const enum SourceType {
84
88
  extensions = "extensions",
85
89
  plain = "plain"
86
90
  }
91
+ export declare function isSourceType(str: string): str is SourceType;
87
92
  export declare function determineCurrentSourceType(dest: string | AdvancedOutputConfig, srcIsDirectory: boolean): SourceType;
88
93
  export interface WebpackPlugin {
89
94
  apply(compiler: Compiler): void;
@@ -107,17 +112,20 @@ export type AdvancedOutputConfig = {
107
112
  locations?: UsageLocations | UsageLocations['handle'];
108
113
  lazyLoader?: string;
109
114
  pathQueryParameters?: PathQueryParameters;
115
+ enqueuingFlags?: NormalizedEnqueuingControlFlags;
110
116
  };
111
117
  type OptionalCfgFields = 'directoryLayout' | 'externalize' | 'lazyLoader' | 'pathQueryParameters';
112
- export type VerifiedAdvancedOutputConfig = Required<Omit<AdvancedOutputConfig, 'locations' | OptionalCfgFields>> & Pick<AdvancedOutputConfig, OptionalCfgFields> & {
118
+ export type VerifiedAdvancedOutputConfig = Required<Omit<AdvancedOutputConfig, 'locations' | 'enqueuingFlags' | OptionalCfgFields>> & Pick<AdvancedOutputConfig, OptionalCfgFields> & {
119
+ enqueuingFlags: NormalizedEnqueuingControlFlags | undefined;
113
120
  locations: NormalizedUsageLocations;
114
121
  };
115
122
  export type SourcesObject = Record<string, string | AdvancedOutputConfig | boolean>;
116
123
  export type PlauditWordpressWebpackConfig = {
124
+ standard?: '2026-03-13';
117
125
  standaloneBlocks?: boolean;
118
126
  variables?: Record<string, any>;
119
127
  verbose?: boolean;
120
- src: string[] | SourcesObject;
128
+ src?: string[] | SourcesObject;
121
129
  stats?: Configuration['stats'];
122
130
  postcss?: {
123
131
  functions?: (variables: (name: string) => unknown) => PostcssFunctionsOptions['functions'];
@@ -148,6 +156,7 @@ export type FileSegmentBlockEntrypointInfo = {
148
156
  dest: VerifiedAdvancedOutputConfig;
149
157
  absoluteSrc: string;
150
158
  pathQueryParameters: PathQueryParameters | undefined;
159
+ enqueuingFlags: NormalizedEnqueuingControlFlags | undefined;
151
160
  };
152
161
  export type PathQueryParameters = Record<string, unknown | [unknown, ...unknown[]]>;
153
162
  export type BlockEntrypointInfo = FileSegmentBlockEntrypointInfo | {
package/build/shared.js CHANGED
@@ -5,9 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.styleExtension = exports.scriptWithModuleExtension = exports.scriptWithoutModuleExtension = exports.scriptExtension = exports.entrypointFields = exports.standardLocationNamesMeta = void 0;
7
7
  exports.isParsedAssetsJson = isParsedAssetsJson;
8
+ exports.isStandardLocationName = isStandardLocationName;
9
+ exports.isRegisterScriptArgsShorthandName = isRegisterScriptArgsShorthandName;
8
10
  exports.isNormalizedUsageLocations = isNormalizedUsageLocations;
9
11
  exports.constantKeys = constantKeys;
10
12
  exports.constantEntries = constantEntries;
13
+ exports.isSourceType = isSourceType;
11
14
  exports.determineCurrentSourceType = determineCurrentSourceType;
12
15
  exports.convertUsageLocationsHandleToEmittableHandle = convertUsageLocationsHandleToEmittableHandle;
13
16
  exports.makeEmittableConfigPHP = makeEmittableConfigPHP;
@@ -30,13 +33,13 @@ exports.emitPHPWriterAsAsset = emitPHPWriterAsAsset;
30
33
  exports.dedent = dedent;
31
34
  exports.resolveLegacyBlockScriptsInFolder = resolveLegacyBlockScriptsInFolder;
32
35
  exports.appendAddInlineAssetCall = appendAddInlineAssetCall;
36
+ const node_crypto_1 = require("node:crypto");
33
37
  const node_fs_1 = __importDefault(require("node:fs"));
34
38
  const promises_1 = __importDefault(require("node:fs/promises"));
35
39
  const node_path_1 = __importDefault(require("node:path"));
36
40
  const php_writer_1 = require("@plaudit/php-writer");
37
41
  const expressions_1 = require("@plaudit/php-writer/expressions");
38
42
  const webpack_1 = require("webpack");
39
- const node_crypto_1 = require("node:crypto");
40
43
  function isParsedAssetsJson(thing) {
41
44
  if (!thing || typeof thing !== 'object') {
42
45
  return false;
@@ -59,6 +62,12 @@ exports.standardLocationNamesMeta = {
59
62
  customizer: { action: "customize_controls_enqueue_scripts" },
60
63
  analytics: { action: "plaudit_enqueue_analytics" },
61
64
  };
65
+ function isStandardLocationName(name) {
66
+ return name in exports.standardLocationNamesMeta;
67
+ }
68
+ function isRegisterScriptArgsShorthandName(name) {
69
+ return ['lazy', 'eager', 'inline'].includes(name);
70
+ }
62
71
  function isNormalizedUsageLocations(usageLocations) {
63
72
  return typeof usageLocations.registerScriptArgs !== 'string';
64
73
  }
@@ -68,6 +77,9 @@ function constantKeys(object) {
68
77
  function constantEntries(object) {
69
78
  return Object.entries(object);
70
79
  }
80
+ function isSourceType(str) {
81
+ return str === "blocks" /* SourceType.blocks */ || str === "extensions" /* SourceType.extensions */ || str === "plain" /* SourceType.plain */;
82
+ }
71
83
  function determineCurrentSourceType(dest, srcIsDirectory) {
72
84
  if (typeof dest === 'string') {
73
85
  return srcIsDirectory ? "blocks" /* SourceType.blocks */ : "plain" /* SourceType.plain */;
@@ -107,7 +119,7 @@ function leadingSlashIt(pathOrSomething) {
107
119
  exports.scriptExtension = /(?<filename>.+)(?<extension>\.m?[jt]sx?)$/i;
108
120
  exports.scriptWithoutModuleExtension = /(?<filename>.+)(?<extension>\.[jt]sx?)($|\?)/i;
109
121
  exports.scriptWithModuleExtension = /(?<filename>.+)(?<extension>\.m[jt]sx?)($|\?)/i;
110
- exports.styleExtension = /(?<filename>.+)(?<extension>\.(p?c|sa)ss)($|\?)/i;
122
+ exports.styleExtension = /(?<filename>.+)(?<extension>\.([ps]?c|sa)ss)($|\?)/i;
111
123
  function scriptOrStyleTest(entryPath, scriptExtension) {
112
124
  return scriptExtension.test(entryPath) ? "script" : (exports.styleExtension.test(entryPath) ? "style" : "");
113
125
  }
@@ -3,7 +3,7 @@ import type { AdditionalDependencyInjectorPlugin } from "../plugins/AdditionalDe
3
3
  import { EntrypointFields, PlauditWordpressWebpackConfig, BlockEntrypointInfo, VerifiedAdvancedOutputConfig, MinimumViableMetadata, WebpackPlugin } from "../shared";
4
4
  import type { Compiler, Configuration, DynamicEntryPlugin, WebpackPluginInstance } from "webpack";
5
5
  import type WebpackRemoveEmptyScriptsPlugin from "webpack-remove-empty-scripts";
6
- export type VerifiedPlauditWordpressWebpackConfig = Required<Omit<PlauditWordpressWebpackConfig, 'variables' | 'src' | 'externals'>> & {
6
+ export type VerifiedPlauditWordpressWebpackConfig = Required<Omit<PlauditWordpressWebpackConfig, 'variables' | 'src' | 'externals' | 'standard'>> & {
7
7
  variablesFilePath?: string;
8
8
  currentVariables: Record<string, any>;
9
9
  } & Pick<PlauditWordpressWebpackConfig, 'externals'>;
@@ -10,6 +10,7 @@ exports.commonMakeWebpackConfig = commonMakeWebpackConfig;
10
10
  const node_fs_1 = __importDefault(require("node:fs"));
11
11
  const promises_1 = __importDefault(require("node:fs/promises"));
12
12
  const node_path_1 = __importDefault(require("node:path"));
13
+ const location_encoding_filename_parser_1 = require("./location-encoding-filename-parser");
13
14
  const path_query_and_related_helpers_1 = require("./path-query-and-related-helpers");
14
15
  const shared_1 = require("../shared");
15
16
  const css_minimizer_webpack_plugin_1 = __importDefault(require("css-minimizer-webpack-plugin"));
@@ -36,9 +37,10 @@ function mapToRealEntrypoints(entrypoint, dir, supportedExtensions, args) {
36
37
  .map(ep => joinPossiblyAbsolutePaths(dir, mapper(ep)))
37
38
  .filter(ep => supportedExtensions(ep) && node_fs_1.default.statSync(ep, { throwIfNoEntry: false })?.isFile())
38
39
  .map(path_query_and_related_helpers_1.unpackPotentiallyPrefixedFilePath)
39
- .map(([ep, pathQueryParameters]) => {
40
+ .map(([ep, rawPathQueryParameters]) => {
40
41
  const parsedEntrypoint = node_path_1.default.parse(ep);
41
42
  const entrypointField = shared_1.styleExtension.test(ep) ? 'style' : shared_1.scriptWithModuleExtension.test(ep) ? 'viewScriptModule' : 'script';
43
+ const { flags: enqueuingFlags, remainder: pathQueryParameters } = (0, path_query_and_related_helpers_1.unpackEnqueuingControlFlagsFromPathQueryParameters)(rawPathQueryParameters, ep, 'path query parameters');
42
44
  const fakeEntrypointInfo = {
43
45
  blockJsonOrigin: args.entrypointJsonOrigin,
44
46
  entrypointField,
@@ -48,7 +50,8 @@ function mapToRealEntrypoints(entrypoint, dir, supportedExtensions, args) {
48
50
  handleGroup: (0, shared_1.getHandleGroup)(entrypointField),
49
51
  dest,
50
52
  absoluteSrc: ep,
51
- pathQueryParameters
53
+ pathQueryParameters,
54
+ enqueuingFlags,
52
55
  };
53
56
  return [joinPossiblyAbsolutePaths(dest.destination, node_path_1.default.basename(parsedEntrypoint.dir), parsedEntrypoint.name),
54
57
  { import: [ep], plauditMetadata: fakeEntrypointInfo }];
@@ -133,6 +136,7 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
133
136
  const blockJsonOrigin = node_path_1.default.join(dir, 'block.json');
134
137
  const blockJson = JSON.parse(await promises_1.default.readFile(blockJsonOrigin, 'utf8'));
135
138
  const blockJsonChunkName = node_path_1.default.join(dest.destination, node_path_1.default.relative(srcRoot, dir), "block");
139
+ const filesRegisteredByJson = [];
136
140
  const presentEntrypoints = (await Promise.all(entrypointFields
137
141
  .filter(entrypointField => entrypointField in blockJson)
138
142
  .flatMap(entrypointField => {
@@ -140,16 +144,19 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
140
144
  .filter(originalValue => typeof originalValue === 'string')
141
145
  .filter(originalValue => originalValue?.startsWith("file:"))
142
146
  .map(originalValue => {
143
- const [entrypointPath, localPathQueryParameters] = (0, path_query_and_related_helpers_1.unpackPotentiallyPrefixedFilePath)(originalValue);
147
+ const [entrypointPath, rawLocalPathQueryParameters] = (0, path_query_and_related_helpers_1.unpackPotentiallyPrefixedFilePath)(originalValue);
144
148
  const absoluteSrc = node_path_1.default.normalize(node_path_1.default.join(dir, entrypointPath));
145
- const pathQueryParameters = (0, path_query_and_related_helpers_1.mergeInPathQueryParameters)(absoluteSrc, localPathQueryParameters, dest.pathQueryParameters);
149
+ filesRegisteredByJson.push(absoluteSrc);
150
+ const { flags: localEnqueuingFlags, remainder: localPathQueryParameters } = (0, path_query_and_related_helpers_1.unpackEnqueuingControlFlagsFromPathQueryParameters)(rawLocalPathQueryParameters, absoluteSrc, "path query parameters");
151
+ const enqueuingFlags = (0, path_query_and_related_helpers_1.mergeTwoScriptEnqueuingControlFlagSets)(absoluteSrc, localEnqueuingFlags, dest.enqueuingFlags);
152
+ const pathQueryParameters = { ...dest.pathQueryParameters, ...localPathQueryParameters };
146
153
  return promises_1.default.stat(absoluteSrc)
147
154
  .then(stats => {
148
155
  if (stats.isFile()) {
149
156
  const parsedEntrypoint = node_path_1.default.parse(node_path_1.default.normalize(node_path_1.default.join(dest.destination, node_path_1.default.relative(srcRoot, dir), entrypointPath)));
150
157
  const extensionlessExpectedSrc = node_path_1.default.normalize(node_path_1.default.join(parsedEntrypoint.dir, parsedEntrypoint.name));
151
158
  const entrypointName = node_path_1.default.normalize(joinPossiblyAbsolutePaths(parsedEntrypoint.dir, parsedEntrypoint.name));
152
- return { entrypointField, originalValue, entrypointName, extensionlessExpectedSrc, absoluteSrc, pathQueryParameters };
159
+ return { entrypointField, originalValue, entrypointName, extensionlessExpectedSrc, absoluteSrc, pathQueryParameters, enqueuingFlags };
153
160
  }
154
161
  else {
155
162
  return undefined;
@@ -157,6 +164,56 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
157
164
  }, () => undefined);
158
165
  });
159
166
  }))).filter(pe => pe !== undefined);
167
+ for await (const dirent of await promises_1.default.opendir(dir)) {
168
+ if (!dirent.isFile()) {
169
+ continue;
170
+ }
171
+ const absoluteSrc = node_path_1.default.normalize(node_path_1.default.join(dir, dirent.name));
172
+ if (filesRegisteredByJson.includes(absoluteSrc)) {
173
+ continue;
174
+ }
175
+ const parsedFilename = (0, location_encoding_filename_parser_1.parseLocationEncodingFilenameForBlock)(absoluteSrc);
176
+ if (!parsedFilename) {
177
+ continue;
178
+ }
179
+ const { type, locations, flags } = parsedFilename;
180
+ let entrypointField;
181
+ if (Object.keys(locations).length === 2) {
182
+ if (type === 'script-module') {
183
+ console.error(`Blocks cannot have script modules enqueued in editor mode. Found one enqueued in both editor and view in ${dirent.parentPath}. It will be enqueued in view alone.`);
184
+ entrypointField = 'viewScriptModule';
185
+ }
186
+ else {
187
+ entrypointField = type;
188
+ }
189
+ }
190
+ else {
191
+ const location = (0, shared_1.constantKeys)(locations)[0];
192
+ if (type === 'script-module') {
193
+ if (location !== 'clientView') {
194
+ console.error(`Blocks cannot have script modules enqueued in editor mode. Found one in ${dirent.parentPath}. It will be ignored`);
195
+ continue;
196
+ }
197
+ entrypointField = 'viewScriptModule';
198
+ }
199
+ else {
200
+ entrypointField = (location.substring(6).toLowerCase() + type.substring(0, 1).toUpperCase() + type.substring(1));
201
+ }
202
+ }
203
+ const parsedEntrypoint = node_path_1.default.parse(node_path_1.default.normalize(node_path_1.default.join(dest.destination, node_path_1.default.relative(srcRoot, dir), `./${dirent.name}`)));
204
+ const extensionlessExpectedSrc = node_path_1.default.normalize(node_path_1.default.join(parsedEntrypoint.dir, parsedEntrypoint.name));
205
+ const entrypointName = node_path_1.default.normalize(joinPossiblyAbsolutePaths(parsedEntrypoint.dir, parsedEntrypoint.name));
206
+ const enqueuingFlags = (0, path_query_and_related_helpers_1.mergeTwoScriptEnqueuingControlFlagSets)(absoluteSrc, flags, dest.enqueuingFlags);
207
+ presentEntrypoints.push({
208
+ entrypointField,
209
+ originalValue: dirent.name,
210
+ entrypointName,
211
+ extensionlessExpectedSrc,
212
+ absoluteSrc,
213
+ pathQueryParameters: dest.pathQueryParameters,
214
+ enqueuingFlags
215
+ });
216
+ }
160
217
  const entrypointNamesWithEffectiveDuplicates = presentEntrypoints
161
218
  .reduce((a, entry) => {
162
219
  // If it's undefined, then this is the first instance, otherwise, it's not the first instance and, therefore, is expected to be a duplicate
@@ -201,7 +258,7 @@ function resolveEntryFromDirectory(commonConfig, srcRoot, dest) {
201
258
  }
202
259
  ];
203
260
  }));
204
- rawEntrypoints.push([blockJsonChunkName, { import: [blockJsonOrigin], plauditMetadata: { purpose: "block-json-inclusion-assurance", dest, absoluteSrc: blockJsonOrigin, pathQueryParameters: undefined } }]);
261
+ rawEntrypoints.push([blockJsonChunkName, { import: [blockJsonOrigin], plauditMetadata: { purpose: "block-json-inclusion-assurance", dest, absoluteSrc: blockJsonOrigin } }]);
205
262
  wpmlFiles.push(node_path_1.default.join(dir, 'block.json'));
206
263
  }
207
264
  catch (e) {
@@ -0,0 +1,25 @@
1
+ import { RealUsageLocations } from "../shared";
2
+ import { NormalizedEnqueuingControlFlags } from "./path-query-and-related-helpers";
3
+ export type ParsedLocationEncodingFilename = {
4
+ type: 'script' | 'style' | 'script-module';
5
+ locations: RealUsageLocations & {
6
+ register?: boolean | number;
7
+ };
8
+ flags: NormalizedEnqueuingControlFlags;
9
+ };
10
+ export declare function parseLocationEncodingFilename(filename: string): ParsedLocationEncodingFilename | undefined;
11
+ export declare function parseLocationEncodingFilenameForBlock(filename: string): Omit<ParsedLocationEncodingFilename, 'locations'> & {
12
+ locations: Pick<RealUsageLocations, 'clientEditor' | 'clientView'>;
13
+ } | undefined;
14
+ /**
15
+ * This handles detecting all keys of standardLocationNamesMeta as well as "view", "client-view", "editor", "client-editor", and "block-assets".
16
+ * It MUST be kept in sync with standardLocationNamesMeta
17
+ * @param segment
18
+ */
19
+ export declare function extractLocationFromPropertyEncodingFilenameSegment(segment: string): {
20
+ readonly locationName: "clientView" | "clientEditor" | "blockAssets" | "admin" | "login" | "customizer" | "analytics" | "register";
21
+ readonly priority: number | true;
22
+ } | {
23
+ readonly locationName: false;
24
+ readonly priority?: undefined;
25
+ };
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseLocationEncodingFilename = parseLocationEncodingFilename;
4
+ exports.parseLocationEncodingFilenameForBlock = parseLocationEncodingFilenameForBlock;
5
+ exports.extractLocationFromPropertyEncodingFilenameSegment = extractLocationFromPropertyEncodingFilenameSegment;
6
+ const node_path_1 = require("node:path");
7
+ const shared_1 = require("../shared");
8
+ function parseLocationEncodingFilename(filename) {
9
+ const { name, ext } = (0, node_path_1.parse)(filename);
10
+ if (name.startsWith("~")) {
11
+ return undefined;
12
+ }
13
+ let type = /^\.(?:s[ac]|p?c)ss$/i.test(ext) ? 'style' : /^\.m[jt]sx?/i.test(ext) ? 'script-module' : /^\.[jt]sx?/i.test(ext) ? 'script' : false;
14
+ if (type === false) {
15
+ return undefined;
16
+ }
17
+ const locations = {};
18
+ const flags = {};
19
+ const nameSegments = name.split("."); //Split always returns at least one string
20
+ const typeOverrideMatch = /^(?<enqueuePosition>.+)-(?<typeOverride>script|style|script-module)$|^(?<typeOverride>script|style|script-module)$/.exec(nameSegments[0]);
21
+ if (typeOverrideMatch) {
22
+ nameSegments[0] = typeOverrideMatch.groups?.["enqueuePosition"] ?? "both"; // We override the first segment with the version without
23
+ type = typeOverrideMatch.groups?.["typeOverride"];
24
+ }
25
+ const inlinePattern = /^(?<inline>inline)(?:[-=](?<position>before|after))?$|^strategy[-=](?<inline>inline)(?:-(?<position>before|after))?$/;
26
+ const strategyPattern = /^strategy[-=](?<strategy>eager|lazy|defer|async)$|^(?<strategy>lazy|eager)$/;
27
+ const fetchpriorityPattern = /^fetchpriority[-=](auto|low|high)$/;
28
+ const bothViewAndEditorLocationPattern = /^(?:client-)?(?<both>both)(?:[-=](?<priority>-?\d+))?$/;
29
+ for (const segment of nameSegments) {
30
+ const { locationName, priority } = extractLocationFromPropertyEncodingFilenameSegment(segment);
31
+ if (locationName) {
32
+ locations[locationName] = priority;
33
+ continue;
34
+ }
35
+ const lcSegment = segment.toLowerCase();
36
+ const { both, priority: bothPriority } = bothViewAndEditorLocationPattern.exec(lcSegment)?.groups ?? {};
37
+ if (both) {
38
+ const priority = bothPriority !== undefined ? parseInt(bothPriority) : true;
39
+ locations.clientView = priority;
40
+ locations.clientEditor = priority;
41
+ continue;
42
+ }
43
+ const { inline, position: inlinePosition } = (inlinePattern.exec(lcSegment)?.groups ?? {});
44
+ if (inline) {
45
+ flags.inline = true;
46
+ flags.position = inlinePosition;
47
+ continue;
48
+ }
49
+ const strategy = strategyPattern.exec(lcSegment)?.groups?.['strategy']?.toLowerCase();
50
+ if (strategy) {
51
+ flags.strategy = strategy;
52
+ continue;
53
+ }
54
+ const fetchpriority = fetchpriorityPattern.exec(lcSegment)?.[1];
55
+ if (fetchpriority !== undefined) {
56
+ flags.fetchpriority = fetchpriority;
57
+ continue;
58
+ }
59
+ switch (segment.toLowerCase()) {
60
+ // Loading flags
61
+ case "in-footer":
62
+ case "in_footer":
63
+ flags.in_footer = true;
64
+ break;
65
+ case "in-header":
66
+ case "in_header":
67
+ flags.in_footer = false;
68
+ break;
69
+ default:
70
+ break;
71
+ }
72
+ }
73
+ // This means that it wasn't a location-encoding filename
74
+ if (Object.values(locations).length === 0) {
75
+ return undefined;
76
+ }
77
+ return { type, locations, flags };
78
+ }
79
+ function parseLocationEncodingFilenameForBlock(filename) {
80
+ const parsedFilename = parseLocationEncodingFilename(filename);
81
+ if (parsedFilename !== undefined) {
82
+ if ((0, shared_1.constantKeys)(parsedFilename.locations).some(value => value !== 'clientView' && value !== 'clientEditor')) {
83
+ throw (0, shared_1.newWebpackErrorForFile)(`Block assets cannot be enqueued anywhere other than view, editor, or both. Saw: ${(0, shared_1.constantKeys)(parsedFilename).join(", ")}`, filename);
84
+ }
85
+ if (Object.values(parsedFilename.locations).some(value => typeof value === 'number')) {
86
+ throw (0, shared_1.newWebpackErrorForFile)(`Block assets cannot be enqueued with a priority`, filename);
87
+ }
88
+ }
89
+ return parsedFilename;
90
+ }
91
+ const standardLocationNamesAndPriorityPatternInFilenameSegment = /^(?:(?<view>clientView|client-view|view)|(?<editor>clientEditor|client-editor|editor)|(?<blockAssets>blockAssets|block-assets)|(?<locationName>admin|login|customizer|analytics|register))(?:[-=](?<priority>-?\d+))?$/;
92
+ /**
93
+ * This handles detecting all keys of standardLocationNamesMeta as well as "view", "client-view", "editor", "client-editor", and "block-assets".
94
+ * It MUST be kept in sync with standardLocationNamesMeta
95
+ * @param segment
96
+ */
97
+ function extractLocationFromPropertyEncodingFilenameSegment(segment) {
98
+ const { view, editor, blockAssets, locationName, priority: rawPriority } = standardLocationNamesAndPriorityPatternInFilenameSegment.exec(segment)
99
+ ?.groups ?? {};
100
+ const priority = rawPriority !== undefined ? parseInt(rawPriority) : true;
101
+ if (view) {
102
+ return { locationName: "clientView", priority };
103
+ }
104
+ if (editor) {
105
+ return { locationName: "clientEditor", priority };
106
+ }
107
+ if (blockAssets) {
108
+ return { locationName: "blockAssets", priority };
109
+ }
110
+ if (locationName) {
111
+ return { locationName, priority };
112
+ }
113
+ return { locationName: false };
114
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Compilation } from "webpack";
2
- import { InlinedAsset, PathQueryParameters, ScriptArgsObject, UsageLocations } from "../shared";
2
+ import { InlinedAsset, PathQueryParameters, ScriptArgsObject } from "../shared";
3
3
  export declare function getAssetFileContents(compilation: Compilation, name: string): string;
4
4
  export declare function unpackPotentiallyPrefixedFilePath(filePath: string): [string, PathQueryParameters | undefined];
5
5
  /**
@@ -26,28 +26,32 @@ export declare function newInvalidFetchPriorityError(fetchpriority: unknown, fil
26
26
  export type PositionForInlineStrategy = 'before' | 'after';
27
27
  export declare function isValidPositionForInlineStrategy(position: unknown): position is PositionForInlineStrategy;
28
28
  export declare function newInvalidPositionForInlineStrategyError(position: unknown, file: string): import("webpack").WebpackError;
29
- export declare function mergeInPathQueryParameters(file: string, registerScriptArgs: UsageLocations['registerScriptArgs'], pathQueryParameters: PathQueryParameters | undefined): NormalizedScriptEnqueuingControlFlags | undefined;
30
29
  /**
31
30
  * This function ensures that the passed pathQueryParameters are normalized for the config-loading step, NOT the emission step
32
31
  */
33
- export type NormalizedScriptEnqueuingControlFlags = {
32
+ export type NormalizedEnqueuingControlFlags = {
34
33
  strategy?: Exclude<LoadingStrategy, 'inline'>;
35
34
  in_footer?: InFooter;
36
35
  inline?: boolean;
37
36
  fetchpriority?: FetchPriority;
38
37
  position?: 'before' | 'after';
39
38
  };
39
+ export declare function mergeTwoScriptEnqueuingControlFlagSets(file: string, baseArgs: NormalizedEnqueuingControlFlags | undefined, ancestorArgs: NormalizedEnqueuingControlFlags | undefined): NormalizedEnqueuingControlFlags | undefined;
40
+ export declare function unpackEnqueuingControlFlagsFromPathQueryParameters(pathQueryParameters: PathQueryParameters | undefined, file: string, sourceType: 'registerScriptArgs' | 'path query parameters'): {
41
+ flags?: NormalizedEnqueuingControlFlags;
42
+ remainder?: PathQueryParameters;
43
+ };
40
44
  /**
41
45
  * This function does a few things:
42
46
  * <ol>
43
- * <li>It extracts inline, position, strategy, in_footer, and fetchpriority from pathQueryParameters</li>
44
- * <li>If inline is true or the strategy is, 'inline', it records the contents of the asset for later injection inline and removes the asset from the compilation output</li>
47
+ * <li>It converts the strategy, in_footer, and fetchpriority flags into a valid script args object</li>
48
+ * <li>If inline is true, it records the contents of the asset and the value of the position flag for later injection and removes the asset from the compilation output</li>
45
49
  * </ol>
46
50
  * @param compilation
47
51
  * @param file
48
- * @param pathQueryParameters
52
+ * @param flags
49
53
  */
50
- export declare function parseScriptArgsObjectFromPathQueryParameters(compilation: Compilation, file: string, pathQueryParameters: PathQueryParameters | undefined): {
54
+ export declare function convertEnqueuingControlFlagsToScriptArgsObject(compilation: Compilation, file: string, flags: NormalizedEnqueuingControlFlags | undefined): {
51
55
  scriptArgsObject?: ScriptArgsObject;
52
56
  inlinedAsset?: InlinedAsset;
53
57
  };
@@ -11,8 +11,9 @@ exports.isValidFetchPriority = isValidFetchPriority;
11
11
  exports.newInvalidFetchPriorityError = newInvalidFetchPriorityError;
12
12
  exports.isValidPositionForInlineStrategy = isValidPositionForInlineStrategy;
13
13
  exports.newInvalidPositionForInlineStrategyError = newInvalidPositionForInlineStrategyError;
14
- exports.mergeInPathQueryParameters = mergeInPathQueryParameters;
15
- exports.parseScriptArgsObjectFromPathQueryParameters = parseScriptArgsObjectFromPathQueryParameters;
14
+ exports.mergeTwoScriptEnqueuingControlFlagSets = mergeTwoScriptEnqueuingControlFlagSets;
15
+ exports.unpackEnqueuingControlFlagsFromPathQueryParameters = unpackEnqueuingControlFlagsFromPathQueryParameters;
16
+ exports.convertEnqueuingControlFlagsToScriptArgsObject = convertEnqueuingControlFlagsToScriptArgsObject;
16
17
  const node_path_1 = require("node:path");
17
18
  const shared_1 = require("../shared");
18
19
  function getAssetFileContents(compilation, name) {
@@ -101,96 +102,98 @@ function isValidPositionForInlineStrategy(position) {
101
102
  function newInvalidPositionForInlineStrategyError(position, file) {
102
103
  return (0, shared_1.newWebpackErrorForFile)(`The position value for the inlined asset was invalid. Received: ${position}, Expected: undefined|'before'|'after'`, file);
103
104
  }
104
- function mergeInPathQueryParameters(file, registerScriptArgs, pathQueryParameters) {
105
- const baseArgs = unpackRegisterScriptArgsFromPathQueryParameters(typeof registerScriptArgs === 'object' ? registerScriptArgs : { strategy: registerScriptArgs }, file, 'registerScriptArgs');
106
- const pathQueryArgs = unpackRegisterScriptArgsFromPathQueryParameters(pathQueryParameters, file, 'path query parameters');
105
+ function mergeTwoScriptEnqueuingControlFlagSets(file, baseArgs, ancestorArgs) {
107
106
  if (baseArgs === undefined) {
108
- return pathQueryArgs;
107
+ return ancestorArgs;
109
108
  }
110
- if (pathQueryArgs === undefined) {
109
+ if (ancestorArgs === undefined) {
111
110
  return baseArgs;
112
111
  }
112
+ // This must be kept in sync with NormalizedScriptEnqueuingControlFlags
113
113
  for (const key of ['strategy', 'in_footer', 'fetchpriority', 'inline', 'position']) {
114
114
  if (key in baseArgs) {
115
- if (key in pathQueryArgs && pathQueryArgs[key] !== baseArgs[key]) {
115
+ if (key in ancestorArgs && ancestorArgs[key] !== baseArgs[key]) {
116
116
  throw (0, shared_1.newWebpackErrorForFile)(`The ${key} values in the registerScriptArgs and the pathQueryParameters for the file conflict`, file);
117
117
  }
118
118
  }
119
- else if (key in pathQueryArgs) {
120
- baseArgs[key] = pathQueryArgs[key];
119
+ else if (key in ancestorArgs) {
120
+ baseArgs[key] = ancestorArgs[key];
121
121
  }
122
122
  }
123
123
  return baseArgs;
124
124
  }
125
- function unpackRegisterScriptArgsFromPathQueryParameters(pathQueryParameters, file, sourceType) {
125
+ function unpackEnqueuingControlFlagsFromPathQueryParameters(pathQueryParameters, file, sourceType) {
126
126
  if (pathQueryParameters === undefined) {
127
- return undefined;
127
+ return {};
128
128
  }
129
- let baseArgs;
130
- const strategy = pathQueryParameters['strategy'];
129
+ const { strategy, in_footer: normalizedInFooter, 'in-footer': alternateInFooter, fetchpriority, position, ...remainder } = pathQueryParameters;
130
+ let flags;
131
131
  switch (strategy) {
132
132
  case 'defer':
133
133
  case 'async':
134
- baseArgs = { strategy };
134
+ flags = { strategy };
135
135
  break;
136
136
  case 'eager':
137
- baseArgs = { strategy: 'eager' };
137
+ flags = { strategy: 'eager' };
138
138
  break;
139
139
  case 'lazy':
140
- baseArgs = { strategy: 'defer', in_footer: true };
140
+ flags = { strategy: 'defer', in_footer: true };
141
141
  break;
142
142
  case 'inline':
143
- baseArgs = { inline: true };
143
+ flags = { inline: true };
144
144
  break;
145
145
  case true:
146
146
  case false:
147
- baseArgs = { in_footer: strategy };
147
+ flags = { in_footer: strategy };
148
148
  break;
149
149
  case null:
150
150
  case undefined:
151
- baseArgs = {};
151
+ flags = {};
152
152
  break;
153
153
  default:
154
154
  throw newInvalidLoadingStrategyError(strategy, file);
155
155
  }
156
- if (baseArgs.inline === undefined && pathQueryParameters['inline'] !== undefined) {
157
- baseArgs.inline = !!pathQueryParameters['inline'];
156
+ if (flags.inline === undefined && pathQueryParameters['inline'] !== undefined) {
157
+ flags.inline = !!pathQueryParameters['inline'];
158
158
  }
159
- const in_footer = pathQueryParameters['in_footer'] ?? pathQueryParameters['in-footer'];
159
+ const in_footer = normalizedInFooter ?? alternateInFooter;
160
160
  if (in_footer !== undefined) {
161
161
  if (!isValidInFooter(in_footer)) {
162
162
  throw newInvalidInFooterError(in_footer, file);
163
163
  }
164
- baseArgs.in_footer = in_footer;
164
+ flags.in_footer = in_footer;
165
165
  }
166
166
  if (pathQueryParameters['fetchpriority'] !== undefined) {
167
167
  if (!isValidFetchPriority(pathQueryParameters['fetchpriority'])) {
168
168
  throw newInvalidFetchPriorityError(pathQueryParameters['fetchpriority'], file);
169
169
  }
170
- if (baseArgs.fetchpriority !== undefined && baseArgs.fetchpriority !== pathQueryParameters['fetchpriority']) {
170
+ if (flags.fetchpriority !== undefined && flags.fetchpriority !== pathQueryParameters['fetchpriority']) {
171
171
  throw (0, shared_1.newWebpackErrorForFile)(`The strategy and fetchpriority values in the ${sourceType} for the file conflict. Got ${strategy} and ${pathQueryParameters['fetchpriority']}`, file);
172
172
  }
173
- baseArgs.fetchpriority = pathQueryParameters['fetchpriority'];
173
+ flags.fetchpriority = pathQueryParameters['fetchpriority'];
174
174
  }
175
- return Object.keys(baseArgs).length > 0 ? baseArgs : undefined;
175
+ if (flags.inline && (pathQueryParameters['position'] === 'before' || pathQueryParameters['position'] === 'after')) {
176
+ flags.position = pathQueryParameters['position'];
177
+ }
178
+ return Object.keys(flags).length > 0 ? { flags, remainder } : { remainder };
176
179
  }
177
180
  /**
178
181
  * This function does a few things:
179
182
  * <ol>
180
- * <li>It extracts inline, position, strategy, in_footer, and fetchpriority from pathQueryParameters</li>
181
- * <li>If inline is true or the strategy is, 'inline', it records the contents of the asset for later injection inline and removes the asset from the compilation output</li>
183
+ * <li>It converts the strategy, in_footer, and fetchpriority flags into a valid script args object</li>
184
+ * <li>If inline is true, it records the contents of the asset and the value of the position flag for later injection and removes the asset from the compilation output</li>
182
185
  * </ol>
183
186
  * @param compilation
184
187
  * @param file
185
- * @param pathQueryParameters
188
+ * @param flags
186
189
  */
187
- function parseScriptArgsObjectFromPathQueryParameters(compilation, file, pathQueryParameters) {
188
- if (pathQueryParameters === undefined) {
190
+ function convertEnqueuingControlFlagsToScriptArgsObject(compilation, file, flags) {
191
+ if (flags === undefined) {
189
192
  return {};
190
193
  }
194
+ const { strategy, in_footer, inline, position, fetchpriority } = flags;
191
195
  const scriptArgsObject = {};
192
196
  let inlinedAsset = undefined;
193
- const strategy = pathQueryParameters['strategy'];
194
197
  switch (strategy) {
195
198
  case 'async':
196
199
  case 'defer':
@@ -203,13 +206,6 @@ function parseScriptArgsObjectFromPathQueryParameters(compilation, file, pathQue
203
206
  scriptArgsObject.strategy = 'defer';
204
207
  scriptArgsObject.in_footer = true;
205
208
  break;
206
- case 'inline':
207
- inlinedAsset = { contents: getAssetFileContents(compilation, file) };
208
- removeFileAndAssetPHP(compilation, file);
209
- if ('inline' in pathQueryParameters) {
210
- compilation.warnings.push((0, shared_1.newWebpackErrorForFile)("The inline parameter should not be set when setting strategy to 'inline'", file));
211
- }
212
- break;
213
209
  case true:
214
210
  case false:
215
211
  scriptArgsObject.in_footer = strategy;
@@ -221,23 +217,20 @@ function parseScriptArgsObjectFromPathQueryParameters(compilation, file, pathQue
221
217
  default:
222
218
  throw newInvalidLoadingStrategyError(strategy, file);
223
219
  }
224
- const in_footer = pathQueryParameters['in_footer'] ?? pathQueryParameters['in-footer'];
225
220
  if (in_footer !== undefined) {
226
221
  if (!isValidInFooter(in_footer)) {
227
222
  throw newInvalidInFooterError(in_footer, file);
228
223
  }
229
224
  scriptArgsObject.in_footer = in_footer;
230
225
  }
231
- if (inlinedAsset === undefined && pathQueryParameters['inline']) {
226
+ if (inline) {
232
227
  inlinedAsset = { contents: getAssetFileContents(compilation, file) };
233
228
  removeFileAndAssetPHP(compilation, file);
234
- }
235
- if (inlinedAsset !== undefined) {
236
- if (pathQueryParameters['position'] !== undefined) {
237
- if (!isValidPositionForInlineStrategy(pathQueryParameters['position'])) {
238
- throw newInvalidPositionForInlineStrategyError(pathQueryParameters['position'], file);
229
+ if (position !== undefined) {
230
+ if (!isValidPositionForInlineStrategy(position)) {
231
+ throw newInvalidPositionForInlineStrategyError(position, file);
239
232
  }
240
- inlinedAsset.position = pathQueryParameters['position'];
233
+ inlinedAsset.position = position;
241
234
  }
242
235
  if (inlinedAsset.position !== 'before' && scriptArgsObject.strategy !== undefined) {
243
236
  compilation.warnings.push((0, shared_1.newWebpackErrorForFile)("Deferred and async scripts that have inlined JS attached in the 'after' position will cause WordPress to convert all of their dependencies to eager scripts at runtime", file));
@@ -246,7 +239,6 @@ function parseScriptArgsObjectFromPathQueryParameters(compilation, file, pathQue
246
239
  scriptArgsObject.in_footer = true;
247
240
  }
248
241
  }
249
- const fetchpriority = pathQueryParameters['fetchpriority'];
250
242
  if (fetchpriority !== undefined) {
251
243
  if (!isValidFetchPriority(fetchpriority)) {
252
244
  throw newInvalidFetchPriorityError(fetchpriority, file);
@@ -7,6 +7,8 @@ const promises_1 = require("node:fs/promises");
7
7
  const node_path_1 = require("node:path");
8
8
  const shared_1 = require("./shared");
9
9
  const common_config_helpers_1 = require("./utils/common-config-helpers");
10
+ const location_encoding_filename_parser_1 = require("./utils/location-encoding-filename-parser");
11
+ const path_query_and_related_helpers_1 = require("./utils/path-query-and-related-helpers");
10
12
  const AdditionalDependencyInjectorPlugin_1 = require("./plugins/AdditionalDependencyInjectorPlugin");
11
13
  const BrowserSyncPlugin_1 = require("./plugins/BrowserSyncPlugin");
12
14
  const dependency_extraction_webpack_plugin_config_builder_1 = require("./plugins/dependency-extraction-webpack-plugin-config-builder");
@@ -25,7 +27,6 @@ const UnifiedLoaderGenerator_1 = require("./plugins/UnifiedLoaderGenerator");
25
27
  const copy_webpack_plugin_1 = __importDefault(require("copy-webpack-plugin"));
26
28
  const fork_ts_checker_webpack_plugin_1 = __importDefault(require("fork-ts-checker-webpack-plugin"));
27
29
  const webpack_remove_empty_scripts_1 = __importDefault(require("webpack-remove-empty-scripts"));
28
- const path_query_and_related_helpers_1 = require("./utils/path-query-and-related-helpers");
29
30
  function testForDuplicatedEntryPaths(sources) {
30
31
  const seenPaths = (0, common_config_helpers_1.groupEntrypointsByAssetFile)(Array.isArray(sources)
31
32
  ? sources.map(s => typeof s === 'string' ? s : s[1].destination)
@@ -164,6 +165,7 @@ function injectSupportForInliningSVGsAsStrings(rules) {
164
165
  });
165
166
  }
166
167
  function buildVerifiedConfig(config) {
168
+ config = applyStandards(config);
167
169
  const { standaloneBlocks = false, stats = 'errors-warnings', variables: rawVariables, verbose = process.argv.includes('--verbose') || process.env['VERBOSE'] === 'true', postcss = {}, externals, assumeGlobalizedPlauditLibraries = true, processTranslationConfigs = true, combineAssetMetadata = true, useWebpackResourceFiltering = true, plainEntrypointsVersion = 1, srcDir = "", useUnifiedLoader = false, includePostInitFallback = false, omitDistDev = false, onlyRunPostCSSOnPCSS = false } = config;
168
170
  let outputDir = config.outputDir ?? "";
169
171
  if (outputDir && useUnifiedLoader && !omitDistDev && (process.env['SERVER_MODE'] ?? 'development') === 'development') {
@@ -200,12 +202,57 @@ function buildVerifiedConfig(config) {
200
202
  }
201
203
  return [src, { ...dest, pathQueryParameters }];
202
204
  };
203
- const rawSources = Array.isArray(config.src)
204
- ? config.src.map(s => normalizeSrcAndDestination([s, { destination: s }]))
205
- : Object.entries(config.src)
205
+ let rawSources;
206
+ if (Array.isArray(config.src)) {
207
+ rawSources = config.src.map(s => normalizeSrcAndDestination([s, { destination: s }]));
208
+ }
209
+ else {
210
+ const configSrc = config.src ?? {};
211
+ // We check for files with location-encoding filenames and add the pertinent ones to the sources
212
+ const dynamicallyIncludedEntrypoints = [];
213
+ if (srcDir) { // This will only work if we have a unified src root
214
+ const dynamicallyIncludedEntrypointsRoot = (0, node_path_1.isAbsolute)(srcDir) ? srcDir : (0, node_path_1.join)(process.cwd(), srcDir);
215
+ using dir = (0, node_fs_1.opendirSync)(dynamicallyIncludedEntrypointsRoot);
216
+ for (let dirent; (dirent = dir.readSync()) !== null;) {
217
+ if (dirent.name in configSrc || `./${dirent.name}` in configSrc) {
218
+ continue;
219
+ }
220
+ if (dirent.isDirectory()) {
221
+ // If it's a directory that does not have an existing config entry, but does correspond to one of the non-plain SourceTypes, assume that it matches that source type
222
+ if ((0, shared_1.isSourceType)(dirent.name) && dirent.name !== "plain" /* SourceType.plain */) {
223
+ dynamicallyIncludedEntrypoints.push([dirent.name, { directoryLayout: dirent.name }]);
224
+ continue;
225
+ }
226
+ using nestedDir = (0, node_fs_1.opendirSync)((0, node_path_1.join)(dynamicallyIncludedEntrypointsRoot, dirent.name));
227
+ for (let nestedDirent; (nestedDirent = nestedDir.readSync()) !== null;) {
228
+ if (!nestedDirent.isFile()) {
229
+ continue;
230
+ }
231
+ const cfgName = (0, node_path_1.join)(dirent.name, nestedDirent.name);
232
+ if (cfgName in configSrc || `./${cfgName}` in configSrc) {
233
+ continue;
234
+ }
235
+ const parsedFilename = (0, location_encoding_filename_parser_1.parseLocationEncodingFilename)(cfgName);
236
+ if (parsedFilename === undefined) {
237
+ continue;
238
+ }
239
+ dynamicallyIncludedEntrypoints.push([cfgName, { locations: parsedFilename.locations, enqueuingFlags: parsedFilename.flags }]);
240
+ }
241
+ }
242
+ else if (dirent.isFile()) {
243
+ const parsedFilename = (0, location_encoding_filename_parser_1.parseLocationEncodingFilename)(dirent.name);
244
+ if (parsedFilename === undefined) {
245
+ continue;
246
+ }
247
+ dynamicallyIncludedEntrypoints.push([dirent.name, { locations: parsedFilename.locations, enqueuingFlags: parsedFilename.flags }]);
248
+ }
249
+ }
250
+ }
251
+ rawSources = [...Object.entries(configSrc), ...dynamicallyIncludedEntrypoints]
206
252
  .map(([k, v]) => {
207
253
  return normalizeSrcAndDestination([k, typeof v === 'boolean' ? {} : (typeof v === 'string' ? { destination: v } : v)]);
208
254
  });
255
+ }
209
256
  let variablesFilePath = undefined;
210
257
  const currentVariables = rawVariables ?? {};
211
258
  if (!rawVariables) {
@@ -223,7 +270,9 @@ function buildVerifiedConfig(config) {
223
270
  // Destination -> source map
224
271
  const allocatedDestinations = {};
225
272
  const partiallyVerifiedSources = rawSources.map(rawSource => {
226
- const { destination, additionalDependencies = [], assumeGlobalizedPlauditLibraries = cfg.assumeGlobalizedPlauditLibraries, bundleAnalyzer = false, directoryLayout, externalize, withLegacyBlocksIn = false, lazyLoader, pathQueryParameters } = rawSource[1];
273
+ const { destination, additionalDependencies = [], assumeGlobalizedPlauditLibraries = cfg.assumeGlobalizedPlauditLibraries, bundleAnalyzer = false, directoryLayout, externalize, withLegacyBlocksIn = false, lazyLoader, pathQueryParameters: rawPathQueryParameters, enqueuingFlags: rawEnqueuingFlags } = rawSource[1];
274
+ const { flags: queryEnqueuingFlags, remainder: pathQueryParameters } = (0, path_query_and_related_helpers_1.unpackEnqueuingControlFlagsFromPathQueryParameters)(rawPathQueryParameters, rawSource[0], 'path query parameters');
275
+ const enqueuingFlags = (0, path_query_and_related_helpers_1.mergeTwoScriptEnqueuingControlFlagSets)(rawSource[0], rawEnqueuingFlags, queryEnqueuingFlags);
227
276
  const normalizedParts = { additionalDependencies, assumeGlobalizedPlauditLibraries, bundleAnalyzer, directoryLayout, externalize, withLegacyBlocksIn, lazyLoader };
228
277
  const locations = typeof rawSource[1].locations === 'string' || typeof rawSource[1].locations === 'function'
229
278
  ? { handle: rawSource[1].locations } : rawSource[1].locations ?? {};
@@ -233,12 +282,12 @@ function buildVerifiedConfig(config) {
233
282
  if (destination !== undefined) {
234
283
  const effectiveDestination = toEffectiveWebpackDestination(destination);
235
284
  allocatedDestinations[effectiveDestination] = rawSource[0]; // We need to pre-populate the allocatedDestinations map with statically declared destinations
236
- return [rawSource[0], { ...normalizedParts, locations, destination, effectiveDestination, staticallyDeclaredDestination: true, pathQueryParameters }];
285
+ return [rawSource[0], { ...normalizedParts, locations, destination, effectiveDestination, staticallyDeclaredDestination: true, pathQueryParameters, enqueuingFlags }];
237
286
  }
238
287
  else {
239
288
  const naiveDestination = deriveNaiveDestinationFromUnverifiedSourceEntry(rawSource, srcPrefixes);
240
289
  const effectiveDestination = toEffectiveWebpackDestination(naiveDestination);
241
- return [rawSource[0], { ...normalizedParts, locations, destination: naiveDestination, effectiveDestination, staticallyDeclaredDestination: false, pathQueryParameters }];
290
+ return [rawSource[0], { ...normalizedParts, locations, destination: naiveDestination, effectiveDestination, staticallyDeclaredDestination: false, pathQueryParameters, enqueuingFlags }];
242
291
  }
243
292
  });
244
293
  const dynamicEffectiveDestinationsWithExpectedNaiveDuplicates = partiallyVerifiedSources
@@ -253,6 +302,25 @@ function buildVerifiedConfig(config) {
253
302
  testForDuplicatedEntryPaths(sources);
254
303
  return cfg.outputDir ? { cfg, sources } : withDerivedOutputDir(cfg, sources);
255
304
  }
305
+ function applyStandards(config) {
306
+ switch (config.standard) {
307
+ case '2026-03-13':
308
+ return {
309
+ useWebpackResourceFiltering: true,
310
+ extensionsVersion: 3,
311
+ plainEntrypointsVersion: 2,
312
+ srcDir: "src",
313
+ outputDir: "dist",
314
+ useUnifiedLoader: true,
315
+ onlyRunPostCSSOnPCSS: true,
316
+ ...config
317
+ };
318
+ case undefined:
319
+ return config;
320
+ default:
321
+ throw `Invalid standard: ${config.standard}`;
322
+ }
323
+ }
256
324
  function toEffectiveWebpackDestination(destination) {
257
325
  const pathParts = (0, node_path_1.parse)(destination);
258
326
  return (0, node_path_1.join)(pathParts.dir, pathParts.name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plaudit/webpack-extensions",
3
- "version": "2.85.3",
3
+ "version": "2.86.0",
4
4
  "license": "SEE LICENSE IN LICENSE.md",
5
5
  "files": [
6
6
  "/build",
@@ -26,7 +26,7 @@
26
26
  "devDependencies": {
27
27
  "@plaudit/gutenberg-api-extensions": "^2.87.0",
28
28
  "@types/browser-sync-webpack-plugin": "^2.2.5",
29
- "@types/node": "^25.3.5",
29
+ "@types/node": "^25.5.0",
30
30
  "@types/postcss-functions": "^4.0.4",
31
31
  "@types/tapable": "^2.3.0",
32
32
  "@types/webpack-sources": "^3.2.3",
@@ -65,7 +65,7 @@
65
65
  "postcss-url": "^10.1.3",
66
66
  "webpack": "^5.105.4",
67
67
  "webpack-remove-empty-scripts": "^1.1.1",
68
- "xml-formatter": "^3.6.7"
68
+ "xml-formatter": "^3.7.0"
69
69
  },
70
70
  "engines": {
71
71
  "node": ">=20"