@stratawp/vite-plugin 0.2.0 → 0.3.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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/index.d.ts +114 -10
- package/dist/index.js +381 -322
- package/package.json +15 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
GNU GENERAL PUBLIC LICENSE
|
|
2
|
+
Version 3, 29 June 2007
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2024 Jon Imms
|
|
5
|
+
|
|
6
|
+
This program is free software: you can redistribute it and/or modify
|
|
7
|
+
it under the terms of the GNU General Public License as published by
|
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
(at your option) any later version.
|
|
10
|
+
|
|
11
|
+
This program is distributed in the hope that it will be useful,
|
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
GNU General Public License for more details.
|
|
15
|
+
|
|
16
|
+
You should have received a copy of the GNU General Public License
|
|
17
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
For the full GPL-3.0 license text, see: https://www.gnu.org/licenses/gpl-3.0.txt
|
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ export default defineConfig({
|
|
|
84
84
|
### Block Auto-Discovery
|
|
85
85
|
|
|
86
86
|
The plugin scans your `src/blocks` directory for `block.json` files and automatically:
|
|
87
|
+
|
|
87
88
|
1. Discovers all blocks
|
|
88
89
|
2. Generates PHP registration code
|
|
89
90
|
3. Watches for changes during development
|
|
@@ -92,6 +93,7 @@ The plugin scans your `src/blocks` directory for `block.json` files and automati
|
|
|
92
93
|
### PHP Hot Reload
|
|
93
94
|
|
|
94
95
|
When you modify PHP template files:
|
|
96
|
+
|
|
95
97
|
1. Plugin detects the change
|
|
96
98
|
2. Sends reload signal to browser
|
|
97
99
|
3. Page refreshes automatically
|
|
@@ -100,6 +102,7 @@ When you modify PHP template files:
|
|
|
100
102
|
### Asset Manifest
|
|
101
103
|
|
|
102
104
|
Generates a WordPress-compatible manifest that includes:
|
|
105
|
+
|
|
103
106
|
- File paths and hashes
|
|
104
107
|
- CSS dependencies
|
|
105
108
|
- WordPress script dependencies (wp-element, wp-blocks, etc.)
|
package/dist/index.d.ts
CHANGED
|
@@ -28,6 +28,20 @@ interface StrataWPPluginOptions {
|
|
|
28
28
|
* Performance optimization options
|
|
29
29
|
*/
|
|
30
30
|
performance?: PerformanceOptions;
|
|
31
|
+
/**
|
|
32
|
+
* Update notification options for dev server
|
|
33
|
+
*/
|
|
34
|
+
updateNotification?: UpdateNotificationOptions;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Update Notification Options
|
|
38
|
+
*/
|
|
39
|
+
interface UpdateNotificationOptions {
|
|
40
|
+
/**
|
|
41
|
+
* Enable update notifications in dev server
|
|
42
|
+
* @default true
|
|
43
|
+
*/
|
|
44
|
+
enabled?: boolean;
|
|
31
45
|
}
|
|
32
46
|
/**
|
|
33
47
|
* Block Options
|
|
@@ -184,15 +198,10 @@ interface PerformanceOptions {
|
|
|
184
198
|
*/
|
|
185
199
|
criticalCSS?: boolean | CriticalCSSOptions;
|
|
186
200
|
/**
|
|
187
|
-
*
|
|
188
|
-
* @default true
|
|
189
|
-
*/
|
|
190
|
-
lazyLoading?: boolean | LazyLoadingOptions;
|
|
191
|
-
/**
|
|
192
|
-
* Asset preloading options
|
|
201
|
+
* Build-time image optimization (sharp + webp + svgo)
|
|
193
202
|
* @default true
|
|
194
203
|
*/
|
|
195
|
-
|
|
204
|
+
images?: boolean | ImageOptions;
|
|
196
205
|
}
|
|
197
206
|
/**
|
|
198
207
|
* Critical CSS Options
|
|
@@ -262,6 +271,46 @@ interface LazyLoadingOptions {
|
|
|
262
271
|
*/
|
|
263
272
|
placeholder?: 'blur' | 'color' | 'none';
|
|
264
273
|
}
|
|
274
|
+
/**
|
|
275
|
+
* Image Pipeline Options
|
|
276
|
+
*/
|
|
277
|
+
interface ImageOptions {
|
|
278
|
+
/**
|
|
279
|
+
* Enable the build-time image pipeline
|
|
280
|
+
* @default true
|
|
281
|
+
*/
|
|
282
|
+
enabled?: boolean;
|
|
283
|
+
/**
|
|
284
|
+
* Source images directory (relative to project root)
|
|
285
|
+
* @default 'src/images'
|
|
286
|
+
*/
|
|
287
|
+
src?: string;
|
|
288
|
+
/**
|
|
289
|
+
* Output directory (relative to project root)
|
|
290
|
+
* @default 'dist/images'
|
|
291
|
+
*/
|
|
292
|
+
dest?: string;
|
|
293
|
+
/**
|
|
294
|
+
* Emit sibling .webp for jpg/png. Kept for backwards compatibility:
|
|
295
|
+
* `false` removes 'webp' from `formats`.
|
|
296
|
+
* @default true
|
|
297
|
+
*/
|
|
298
|
+
webp?: boolean;
|
|
299
|
+
/**
|
|
300
|
+
* Sibling formats emitted next to jpg/png sources.
|
|
301
|
+
* @default ['avif', 'webp']
|
|
302
|
+
*/
|
|
303
|
+
formats?: Array<'avif' | 'webp'>;
|
|
304
|
+
/**
|
|
305
|
+
* Per-format quality (0-100)
|
|
306
|
+
*/
|
|
307
|
+
quality?: {
|
|
308
|
+
jpeg?: number;
|
|
309
|
+
png?: number;
|
|
310
|
+
avif?: number;
|
|
311
|
+
webp?: number;
|
|
312
|
+
};
|
|
313
|
+
}
|
|
265
314
|
/**
|
|
266
315
|
* Preload Options
|
|
267
316
|
*/
|
|
@@ -309,6 +358,10 @@ declare function strataWPPhpHmr(options?: PhpHmrOptions): Plugin;
|
|
|
309
358
|
*
|
|
310
359
|
* Ensures assets are output in WordPress-friendly structure
|
|
311
360
|
* and can be properly enqueued by WordPress.
|
|
361
|
+
*
|
|
362
|
+
* Also handles the src/icons/ directory:
|
|
363
|
+
* - Build: copies to dist/icons/ with CSS url() path normalization
|
|
364
|
+
* - Dev: watches for changes and triggers full page reload
|
|
312
365
|
*/
|
|
313
366
|
declare function strataWPAssets(options?: AssetOptions): Plugin;
|
|
314
367
|
|
|
@@ -322,8 +375,6 @@ declare function strataWPDesignSystem(options?: DesignSystemOptions): Plugin;
|
|
|
322
375
|
|
|
323
376
|
/**
|
|
324
377
|
* Performance Orchestrator
|
|
325
|
-
*
|
|
326
|
-
* Combines all performance plugins with unified configuration
|
|
327
378
|
*/
|
|
328
379
|
|
|
329
380
|
declare function strataWPPerformance(options?: PerformanceOptions): Plugin[];
|
|
@@ -342,6 +393,59 @@ declare const strataWPTailwindPreset: Partial<any>;
|
|
|
342
393
|
*/
|
|
343
394
|
declare const strataWPUnoPreset: any;
|
|
344
395
|
|
|
396
|
+
/**
|
|
397
|
+
* block.json -> TypeScript attribute-type codegen.
|
|
398
|
+
*
|
|
399
|
+
* Given a parsed block.json, emits a deterministic `block-attributes.ts` file
|
|
400
|
+
* exporting an interface that describes the block's attributes. The block's
|
|
401
|
+
* `edit.tsx` consumes this interface so the editor props stay in lockstep with
|
|
402
|
+
* block.json, and a drift gate (`scripts/check-types-drift.mjs`) diffs the
|
|
403
|
+
* committed output to keep them in sync.
|
|
404
|
+
*
|
|
405
|
+
* JSON -> TS type map:
|
|
406
|
+
* string -> string
|
|
407
|
+
* number | integer -> number
|
|
408
|
+
* boolean -> boolean
|
|
409
|
+
* array -> unknown[]
|
|
410
|
+
* object -> Record<string, unknown>
|
|
411
|
+
* (missing/unknown) -> unknown
|
|
412
|
+
*
|
|
413
|
+
* Optionality: an attribute with a `default` is REQUIRED; an attribute without
|
|
414
|
+
* a `default` is optional (`?`).
|
|
415
|
+
*/
|
|
416
|
+
/**
|
|
417
|
+
* A parsed block.json (only the fields the codegen needs).
|
|
418
|
+
*
|
|
419
|
+
* `attributes` is intentionally `Record<string, unknown>` so the scanned
|
|
420
|
+
* `BlockMetadata` (which types attributes loosely) is assignable; each entry is
|
|
421
|
+
* narrowed to {@link BlockAttributeSchema} internally.
|
|
422
|
+
*/
|
|
423
|
+
interface BlockJsonInput {
|
|
424
|
+
name?: string;
|
|
425
|
+
attributes?: Record<string, unknown>;
|
|
426
|
+
}
|
|
427
|
+
/** A single attribute schema entry from block.json's `attributes` map. */
|
|
428
|
+
interface BlockAttributeSchema {
|
|
429
|
+
type?: string;
|
|
430
|
+
default?: unknown;
|
|
431
|
+
[key: string]: unknown;
|
|
432
|
+
}
|
|
433
|
+
/** The result of generating a block's attribute types. */
|
|
434
|
+
interface GeneratedBlockTypes {
|
|
435
|
+
/** Always `block-attributes.ts` — written next to the block's block.json. */
|
|
436
|
+
fileName: string;
|
|
437
|
+
/** Hand-emitted TypeScript source for the attribute interface. */
|
|
438
|
+
content: string;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Generate the `block-attributes.ts` source for a single block.
|
|
442
|
+
*
|
|
443
|
+
* The output is hand-emitted to be byte-identical to the repo prettier config
|
|
444
|
+
* (no semicolons, single quotes, 2-space indent, LF line endings, trailing
|
|
445
|
+
* newline) without a runtime prettier dependency.
|
|
446
|
+
*/
|
|
447
|
+
declare function generateBlockAttributeTypes(blockJson: BlockJsonInput): GeneratedBlockTypes;
|
|
448
|
+
|
|
345
449
|
/**
|
|
346
450
|
* StrataWP Vite Plugin
|
|
347
451
|
*
|
|
@@ -353,4 +457,4 @@ declare const strataWPUnoPreset: any;
|
|
|
353
457
|
*/
|
|
354
458
|
declare function strataWP(options?: StrataWPPluginOptions): Plugin[];
|
|
355
459
|
|
|
356
|
-
export { type AssetOptions, type BlockMetadata, type BlockOptions, type CriticalCSSOptions, type DesignSystemOptions, type LazyLoadingOptions, type ManifestEntry, type ManifestOptions, type PerformanceOptions, type PhpHmrOptions, type PreloadOptions, type StrataWPPluginOptions, type WordPressManifest, strataWP, strataWPAssets, strataWPBlocks, strataWPDesignSystem, strataWPManifest, strataWPPerformance, strataWPPhpHmr, strataWPTailwindPreset, strataWPUnoPreset };
|
|
460
|
+
export { type AssetOptions, type BlockAttributeSchema, type BlockJsonInput, type BlockMetadata, type BlockOptions, type CriticalCSSOptions, type DesignSystemOptions, type GeneratedBlockTypes, type ImageOptions, type LazyLoadingOptions, type ManifestEntry, type ManifestOptions, type PerformanceOptions, type PhpHmrOptions, type PreloadOptions, type StrataWPPluginOptions, type UpdateNotificationOptions, type WordPressManifest, generateBlockAttributeTypes, strataWP, strataWPAssets, strataWPBlocks, strataWPDesignSystem, strataWPManifest, strataWPPerformance, strataWPPhpHmr, strataWPTailwindPreset, strataWPUnoPreset };
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,58 @@ import fg from "fast-glob";
|
|
|
10
10
|
import { readFile, writeFile } from "fs/promises";
|
|
11
11
|
import { join, dirname, relative } from "path";
|
|
12
12
|
import pc from "picocolors";
|
|
13
|
+
|
|
14
|
+
// src/plugins/block-types.ts
|
|
15
|
+
function mapJsonTypeToTs(jsonType) {
|
|
16
|
+
switch (jsonType) {
|
|
17
|
+
case "string":
|
|
18
|
+
return "string";
|
|
19
|
+
case "number":
|
|
20
|
+
case "integer":
|
|
21
|
+
return "number";
|
|
22
|
+
case "boolean":
|
|
23
|
+
return "boolean";
|
|
24
|
+
case "array":
|
|
25
|
+
return "unknown[]";
|
|
26
|
+
case "object":
|
|
27
|
+
return "Record<string, unknown>";
|
|
28
|
+
default:
|
|
29
|
+
return "unknown";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function pascalCaseFromBlockName(blockName) {
|
|
33
|
+
const slug = blockName.includes("/") ? blockName.split("/").pop() : blockName;
|
|
34
|
+
return slug.split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
35
|
+
}
|
|
36
|
+
function generateBlockAttributeTypes(blockJson) {
|
|
37
|
+
const blockName = blockJson.name ?? "block";
|
|
38
|
+
const interfaceName = `${pascalCaseFromBlockName(blockName)}Attributes`;
|
|
39
|
+
const attributes = blockJson.attributes ?? {};
|
|
40
|
+
const sortedKeys = Object.keys(attributes).sort();
|
|
41
|
+
const members = sortedKeys.map((key) => {
|
|
42
|
+
const schema = attributes[key] ?? {};
|
|
43
|
+
const tsType = mapJsonTypeToTs(typeof schema.type === "string" ? schema.type : void 0);
|
|
44
|
+
const optional = Object.prototype.hasOwnProperty.call(schema, "default") ? "" : "?";
|
|
45
|
+
return ` ${key}${optional}: ${tsType}`;
|
|
46
|
+
});
|
|
47
|
+
const body = members.length > 0 ? `{
|
|
48
|
+
${members.join("\n")}
|
|
49
|
+
}` : "{}";
|
|
50
|
+
const content = `/**
|
|
51
|
+
* AUTO-GENERATED by @stratawp/vite-plugin from block.json \u2014 do not edit.
|
|
52
|
+
*
|
|
53
|
+
* Regenerate by building the theme (\`pnpm build\`). The committed copy is
|
|
54
|
+
* verified by the \`contracts:types:check\` drift gate.
|
|
55
|
+
*
|
|
56
|
+
* Block: ${blockName}
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
export interface ${interfaceName} ${body}
|
|
60
|
+
`;
|
|
61
|
+
return { fileName: "block-attributes.ts", content };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/plugins/blocks.ts
|
|
13
65
|
function strataWPBlocks(options = {}) {
|
|
14
66
|
const {
|
|
15
67
|
dir = "src/blocks",
|
|
@@ -29,6 +81,7 @@ function strataWPBlocks(options = {}) {
|
|
|
29
81
|
cwd: rootDir,
|
|
30
82
|
absolute: true
|
|
31
83
|
});
|
|
84
|
+
blockJsonFiles.sort();
|
|
32
85
|
blocks = [];
|
|
33
86
|
for (const blockJsonPath of blockJsonFiles) {
|
|
34
87
|
try {
|
|
@@ -59,6 +112,29 @@ function strataWPBlocks(options = {}) {
|
|
|
59
112
|
const outputPath = join(rootDir, "inc/blocks-generated.php");
|
|
60
113
|
await writeFile(outputPath, phpCode, "utf-8");
|
|
61
114
|
console.log(pc.green(` \u2713 Generated block registration: inc/blocks-generated.php`));
|
|
115
|
+
let typesWritten = 0;
|
|
116
|
+
for (const block of blocks) {
|
|
117
|
+
const blockData = block;
|
|
118
|
+
try {
|
|
119
|
+
const { fileName, content } = generateBlockAttributeTypes(block);
|
|
120
|
+
const typesPath = join(rootDir, blockData._dir, fileName);
|
|
121
|
+
let existing = null;
|
|
122
|
+
try {
|
|
123
|
+
existing = await readFile(typesPath, "utf-8");
|
|
124
|
+
} catch {
|
|
125
|
+
existing = null;
|
|
126
|
+
}
|
|
127
|
+
if (existing !== content) {
|
|
128
|
+
await writeFile(typesPath, content, "utf-8");
|
|
129
|
+
typesWritten++;
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
this.warn(`Failed to generate attribute types for ${block.name}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (typesWritten > 0) {
|
|
136
|
+
console.log(pc.green(` \u2713 Generated block attribute types: ${typesWritten} file(s)`));
|
|
137
|
+
}
|
|
62
138
|
},
|
|
63
139
|
// Hot reload blocks during development
|
|
64
140
|
configureServer(server) {
|
|
@@ -105,11 +181,7 @@ import { writeFile as writeFile2 } from "fs/promises";
|
|
|
105
181
|
import { join as join2, dirname as dirname2 } from "path";
|
|
106
182
|
import { mkdir } from "fs/promises";
|
|
107
183
|
function strataWPManifest(options = {}) {
|
|
108
|
-
const {
|
|
109
|
-
enabled = true,
|
|
110
|
-
output = "dist/.vite/manifest.json",
|
|
111
|
-
wordpress = true
|
|
112
|
-
} = options;
|
|
184
|
+
const { enabled = true, output = "dist/.vite/manifest.json", wordpress = true } = options;
|
|
113
185
|
let rootDir;
|
|
114
186
|
return {
|
|
115
187
|
name: "stratawp:manifest",
|
|
@@ -173,11 +245,7 @@ function extractWordPressDependencies(code) {
|
|
|
173
245
|
import { watch } from "chokidar";
|
|
174
246
|
import pc2 from "picocolors";
|
|
175
247
|
function strataWPPhpHmr(options = {}) {
|
|
176
|
-
const {
|
|
177
|
-
enabled = true,
|
|
178
|
-
watch: patterns = ["**/*.php", "theme.json"],
|
|
179
|
-
debounce = 100
|
|
180
|
-
} = options;
|
|
248
|
+
const { enabled = true, watch: patterns = ["**/*.php", "theme.json"], debounce = 100 } = options;
|
|
181
249
|
let server;
|
|
182
250
|
let watcher = null;
|
|
183
251
|
let timeout = null;
|
|
@@ -191,11 +259,11 @@ function strataWPPhpHmr(options = {}) {
|
|
|
191
259
|
persistent: true,
|
|
192
260
|
ignoreInitial: true
|
|
193
261
|
});
|
|
194
|
-
watcher.on("change", (
|
|
262
|
+
watcher.on("change", (path6) => {
|
|
195
263
|
if (timeout) clearTimeout(timeout);
|
|
196
264
|
timeout = setTimeout(() => {
|
|
197
265
|
console.log(pc2.cyan(`
|
|
198
|
-
\u2692\uFE0F PHP file changed: ${pc2.dim(
|
|
266
|
+
\u2692\uFE0F PHP file changed: ${pc2.dim(path6)}`));
|
|
199
267
|
console.log(pc2.cyan(" Reloading page...\n"));
|
|
200
268
|
server.ws.send({
|
|
201
269
|
type: "full-reload",
|
|
@@ -203,14 +271,14 @@ function strataWPPhpHmr(options = {}) {
|
|
|
203
271
|
});
|
|
204
272
|
}, debounce);
|
|
205
273
|
});
|
|
206
|
-
watcher.on("add", (
|
|
274
|
+
watcher.on("add", (path6) => {
|
|
207
275
|
console.log(pc2.green(`
|
|
208
|
-
\u2692\uFE0F New PHP file: ${pc2.dim(
|
|
276
|
+
\u2692\uFE0F New PHP file: ${pc2.dim(path6)}
|
|
209
277
|
`));
|
|
210
278
|
});
|
|
211
|
-
watcher.on("unlink", (
|
|
279
|
+
watcher.on("unlink", (path6) => {
|
|
212
280
|
console.log(pc2.yellow(`
|
|
213
|
-
\u2692\uFE0F PHP file removed: ${pc2.dim(
|
|
281
|
+
\u2692\uFE0F PHP file removed: ${pc2.dim(path6)}
|
|
214
282
|
`));
|
|
215
283
|
});
|
|
216
284
|
server.httpServer?.once("listening", () => {
|
|
@@ -233,11 +301,28 @@ function strataWPPhpHmr(options = {}) {
|
|
|
233
301
|
}
|
|
234
302
|
|
|
235
303
|
// src/plugins/assets.ts
|
|
304
|
+
import fs from "fs";
|
|
305
|
+
import path from "path";
|
|
306
|
+
function copyIconsDir(srcDir, destDir) {
|
|
307
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
308
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
309
|
+
for (const entry of entries) {
|
|
310
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
311
|
+
const destPath = path.join(destDir, entry.name);
|
|
312
|
+
if (entry.isDirectory()) {
|
|
313
|
+
copyIconsDir(srcPath, destPath);
|
|
314
|
+
} else if (entry.name.endsWith(".css")) {
|
|
315
|
+
let content = fs.readFileSync(srcPath, "utf-8");
|
|
316
|
+
content = content.replace(/url\(\s*['"]?\.\.\/fonts\//g, "url(./fonts/");
|
|
317
|
+
fs.writeFileSync(destPath, content, "utf-8");
|
|
318
|
+
} else {
|
|
319
|
+
fs.copyFileSync(srcPath, destPath);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
236
323
|
function strataWPAssets(options = {}) {
|
|
237
|
-
const {
|
|
238
|
-
|
|
239
|
-
baseUrl
|
|
240
|
-
} = options;
|
|
324
|
+
const { publicDir = "dist", baseUrl } = options;
|
|
325
|
+
let rootDir;
|
|
241
326
|
return {
|
|
242
327
|
name: "stratawp:assets",
|
|
243
328
|
config() {
|
|
@@ -257,19 +342,43 @@ function strataWPAssets(options = {}) {
|
|
|
257
342
|
sourcemap: true
|
|
258
343
|
}
|
|
259
344
|
};
|
|
345
|
+
},
|
|
346
|
+
configResolved(resolvedConfig) {
|
|
347
|
+
rootDir = resolvedConfig.root;
|
|
348
|
+
},
|
|
349
|
+
writeBundle() {
|
|
350
|
+
const iconsSrc = path.resolve(rootDir, "src/icons");
|
|
351
|
+
const iconsDest = path.resolve(rootDir, publicDir, "icons");
|
|
352
|
+
if (!fs.existsSync(iconsSrc)) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
copyIconsDir(iconsSrc, iconsDest);
|
|
356
|
+
},
|
|
357
|
+
configureServer(server) {
|
|
358
|
+
const iconsSrc = path.resolve(rootDir, "src/icons");
|
|
359
|
+
if (!fs.existsSync(iconsSrc)) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
server.watcher.add(iconsSrc);
|
|
363
|
+
server.watcher.on("change", (filePath) => {
|
|
364
|
+
if (filePath.startsWith(iconsSrc)) {
|
|
365
|
+
server.ws.send({ type: "full-reload", path: "*" });
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
server.watcher.on("add", (filePath) => {
|
|
369
|
+
if (filePath.startsWith(iconsSrc)) {
|
|
370
|
+
server.ws.send({ type: "full-reload", path: "*" });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
260
373
|
}
|
|
261
374
|
};
|
|
262
375
|
}
|
|
263
376
|
|
|
264
377
|
// src/plugins/design-system.ts
|
|
265
|
-
import
|
|
266
|
-
import
|
|
378
|
+
import path2 from "path";
|
|
379
|
+
import fs2 from "fs";
|
|
267
380
|
function strataWPDesignSystem(options = {}) {
|
|
268
|
-
const {
|
|
269
|
-
enabled = false,
|
|
270
|
-
framework = "none",
|
|
271
|
-
wordpressPresets = true
|
|
272
|
-
} = options;
|
|
381
|
+
const { enabled = false, framework = "none", wordpressPresets = true } = options;
|
|
273
382
|
if (!enabled || framework === "none") {
|
|
274
383
|
return {
|
|
275
384
|
name: "stratawp:design-system",
|
|
@@ -284,11 +393,11 @@ function strataWPDesignSystem(options = {}) {
|
|
|
284
393
|
},
|
|
285
394
|
async config() {
|
|
286
395
|
const root = process.cwd();
|
|
287
|
-
const tailwindConfig =
|
|
288
|
-
const tailwindConfigTs =
|
|
289
|
-
const unoConfig =
|
|
290
|
-
const hasTailwindConfig =
|
|
291
|
-
const hasUnoConfig =
|
|
396
|
+
const tailwindConfig = path2.join(root, "tailwind.config.js");
|
|
397
|
+
const tailwindConfigTs = path2.join(root, "tailwind.config.ts");
|
|
398
|
+
const unoConfig = path2.join(root, "uno.config.ts");
|
|
399
|
+
const hasTailwindConfig = fs2.existsSync(tailwindConfig) || fs2.existsSync(tailwindConfigTs);
|
|
400
|
+
const hasUnoConfig = fs2.existsSync(unoConfig);
|
|
292
401
|
if (framework === "tailwind" && !hasTailwindConfig) {
|
|
293
402
|
console.warn(
|
|
294
403
|
"\u26A0\uFE0F Tailwind CSS enabled but no config file found. Run: stratawp design-system:setup tailwind"
|
|
@@ -336,10 +445,10 @@ function strataWPDesignSystem(options = {}) {
|
|
|
336
445
|
},
|
|
337
446
|
async transform(code, id) {
|
|
338
447
|
if (wordpressPresets && id.endsWith(".css") && (framework === "tailwind" || framework === "unocss")) {
|
|
339
|
-
const themeJsonPath =
|
|
340
|
-
if (
|
|
448
|
+
const themeJsonPath = path2.join(process.cwd(), "theme.json");
|
|
449
|
+
if (fs2.existsSync(themeJsonPath)) {
|
|
341
450
|
try {
|
|
342
|
-
const themeJson = JSON.parse(
|
|
451
|
+
const themeJson = JSON.parse(fs2.readFileSync(themeJsonPath, "utf-8"));
|
|
343
452
|
if (themeJson.settings?.color?.palette) {
|
|
344
453
|
let injectedVars = "\n/* WordPress Color Variables */\n:root {\n";
|
|
345
454
|
themeJson.settings.color.palette.forEach((color) => {
|
|
@@ -359,8 +468,8 @@ function strataWPDesignSystem(options = {}) {
|
|
|
359
468
|
}
|
|
360
469
|
|
|
361
470
|
// src/plugins/critical-css.ts
|
|
362
|
-
import
|
|
363
|
-
import
|
|
471
|
+
import path3 from "path";
|
|
472
|
+
import fs3 from "fs";
|
|
364
473
|
function strataWPCriticalCSS(options = {}) {
|
|
365
474
|
const {
|
|
366
475
|
enabled = true,
|
|
@@ -381,9 +490,9 @@ function strataWPCriticalCSS(options = {}) {
|
|
|
381
490
|
apply: "build",
|
|
382
491
|
async closeBundle() {
|
|
383
492
|
const root = process.cwd();
|
|
384
|
-
const distDir =
|
|
385
|
-
const templatesDir =
|
|
386
|
-
const outputDir =
|
|
493
|
+
const distDir = path3.join(root, "dist");
|
|
494
|
+
const templatesDir = path3.join(root, "templates");
|
|
495
|
+
const outputDir = path3.join(root, output);
|
|
387
496
|
let critical;
|
|
388
497
|
try {
|
|
389
498
|
critical = __require("critical");
|
|
@@ -393,11 +502,11 @@ function strataWPCriticalCSS(options = {}) {
|
|
|
393
502
|
);
|
|
394
503
|
return;
|
|
395
504
|
}
|
|
396
|
-
if (!
|
|
397
|
-
|
|
505
|
+
if (!fs3.existsSync(outputDir)) {
|
|
506
|
+
fs3.mkdirSync(outputDir, { recursive: true });
|
|
398
507
|
}
|
|
399
508
|
console.log("\u{1F4E6} Extracting critical CSS...");
|
|
400
|
-
const cssFiles =
|
|
509
|
+
const cssFiles = fs3.readdirSync(path3.join(distDir, "css")).filter((f) => f.endsWith(".css")).map((f) => path3.join(distDir, "css", f));
|
|
401
510
|
if (cssFiles.length === 0) {
|
|
402
511
|
console.warn(" No CSS files found to extract critical CSS from");
|
|
403
512
|
return;
|
|
@@ -405,8 +514,8 @@ function strataWPCriticalCSS(options = {}) {
|
|
|
405
514
|
const mainCss = cssFiles[0];
|
|
406
515
|
const criticalResults = {};
|
|
407
516
|
for (const template of templates) {
|
|
408
|
-
const templatePath =
|
|
409
|
-
if (!
|
|
517
|
+
const templatePath = path3.join(templatesDir, `${template}.html`);
|
|
518
|
+
if (!fs3.existsSync(templatePath)) {
|
|
410
519
|
console.warn(` Template ${template}.html not found, skipping`);
|
|
411
520
|
continue;
|
|
412
521
|
}
|
|
@@ -422,8 +531,8 @@ function strataWPCriticalCSS(options = {}) {
|
|
|
422
531
|
});
|
|
423
532
|
if (result.css && result.css.length > minSize) {
|
|
424
533
|
criticalResults[template] = result.css;
|
|
425
|
-
const criticalPath =
|
|
426
|
-
|
|
534
|
+
const criticalPath = path3.join(outputDir, `${template}-critical.css`);
|
|
535
|
+
fs3.writeFileSync(criticalPath, result.css);
|
|
427
536
|
console.log(` \u2713 ${template}: ${(result.css.length / 1024).toFixed(2)}KB`);
|
|
428
537
|
}
|
|
429
538
|
} catch (error) {
|
|
@@ -435,9 +544,9 @@ function strataWPCriticalCSS(options = {}) {
|
|
|
435
544
|
};
|
|
436
545
|
}
|
|
437
546
|
async function generatePHPLoader(root, criticalResults, inline) {
|
|
438
|
-
const incDir =
|
|
439
|
-
if (!
|
|
440
|
-
|
|
547
|
+
const incDir = path3.join(root, "inc");
|
|
548
|
+
if (!fs3.existsSync(incDir)) {
|
|
549
|
+
fs3.mkdirSync(incDir, { recursive: true });
|
|
441
550
|
}
|
|
442
551
|
let phpContent = `<?php
|
|
443
552
|
/**
|
|
@@ -496,308 +605,247 @@ function load_critical_css(): void {
|
|
|
496
605
|
// Hook into wp_head with high priority
|
|
497
606
|
add_action('wp_head', __NAMESPACE__ . '\\load_critical_css', 1);
|
|
498
607
|
`;
|
|
499
|
-
|
|
608
|
+
fs3.writeFileSync(path3.join(incDir, "critical-css-generated.php"), phpContent);
|
|
500
609
|
console.log(" \u2713 Generated inc/critical-css-generated.php");
|
|
501
610
|
}
|
|
502
611
|
|
|
503
|
-
// src/plugins/
|
|
504
|
-
import
|
|
505
|
-
import
|
|
506
|
-
|
|
612
|
+
// src/plugins/images.ts
|
|
613
|
+
import path4 from "path";
|
|
614
|
+
import { stat, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile3, copyFile } from "fs/promises";
|
|
615
|
+
import fg2 from "fast-glob";
|
|
616
|
+
import sharp from "sharp";
|
|
617
|
+
import { optimize as svgoOptimize } from "svgo";
|
|
618
|
+
function strataWPImages(options = {}) {
|
|
507
619
|
const {
|
|
508
620
|
enabled = true,
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
621
|
+
src = "src/images",
|
|
622
|
+
dest = "dist/images",
|
|
623
|
+
webp = true,
|
|
624
|
+
quality = {}
|
|
512
625
|
} = options;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
626
|
+
const formats = (options.formats ?? ["avif", "webp"]).filter(
|
|
627
|
+
(format) => !(format === "webp" && webp === false)
|
|
628
|
+
);
|
|
629
|
+
const jpegQuality = quality.jpeg ?? 75;
|
|
630
|
+
const pngQuality = quality.png ?? 80;
|
|
631
|
+
const siblingQuality = { avif: quality.avif ?? 50, webp: quality.webp ?? 75 };
|
|
632
|
+
let root = process.cwd();
|
|
519
633
|
return {
|
|
520
|
-
name: "stratawp:
|
|
634
|
+
name: "stratawp:images",
|
|
521
635
|
apply: "build",
|
|
522
|
-
config
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
636
|
+
configResolved(config) {
|
|
637
|
+
root = config.root;
|
|
638
|
+
},
|
|
639
|
+
async closeBundle() {
|
|
640
|
+
if (!enabled) {
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const srcRoot = path4.resolve(root, src);
|
|
644
|
+
const destRoot = path4.resolve(root, dest);
|
|
645
|
+
const files = await fg2("**/*.{jpg,jpeg,png,gif,svg}", {
|
|
646
|
+
cwd: srcRoot,
|
|
647
|
+
caseSensitiveMatch: false,
|
|
648
|
+
onlyFiles: true
|
|
649
|
+
});
|
|
650
|
+
for (const rel of files) {
|
|
651
|
+
const srcFile = path4.join(srcRoot, rel);
|
|
652
|
+
const destFile = path4.join(destRoot, rel);
|
|
653
|
+
const ext = path4.extname(rel).toLowerCase();
|
|
654
|
+
await mkdir2(path4.dirname(destFile), { recursive: true });
|
|
655
|
+
if (await isNewer(srcFile, destFile)) {
|
|
656
|
+
try {
|
|
657
|
+
if (ext === ".svg") {
|
|
658
|
+
const code = await readFile2(srcFile, "utf8");
|
|
659
|
+
const result = svgoOptimize(code, {
|
|
660
|
+
multipass: true,
|
|
661
|
+
plugins: [
|
|
662
|
+
{ name: "preset-default", params: { overrides: { removeViewBox: false } } }
|
|
663
|
+
]
|
|
664
|
+
});
|
|
665
|
+
await writeFile3(destFile, result.data, "utf8");
|
|
666
|
+
} else if (ext === ".jpg" || ext === ".jpeg") {
|
|
667
|
+
await sharp(srcFile, { sequentialRead: true }).rotate().jpeg({ quality: jpegQuality, mozjpeg: true, progressive: true }).toFile(destFile);
|
|
668
|
+
} else if (ext === ".png") {
|
|
669
|
+
await sharp(srcFile, { sequentialRead: true }).rotate().png({ quality: pngQuality, compressionLevel: 9, adaptiveFiltering: true }).toFile(destFile);
|
|
670
|
+
} else {
|
|
671
|
+
await copyFile(srcFile, destFile);
|
|
537
672
|
}
|
|
673
|
+
} catch {
|
|
674
|
+
await copyFile(srcFile, destFile);
|
|
538
675
|
}
|
|
539
|
-
}
|
|
676
|
+
}
|
|
677
|
+
if (ext === ".jpg" || ext === ".jpeg" || ext === ".png") {
|
|
678
|
+
for (const format of formats) {
|
|
679
|
+
const siblingFile = destFile.replace(/\.[^.]+$/i, `.${format}`);
|
|
680
|
+
if (!await isNewer(srcFile, siblingFile)) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
const pipeline = sharp(srcFile, { sequentialRead: true });
|
|
685
|
+
const buffer = await (format === "avif" ? pipeline.avif({ quality: siblingQuality.avif }) : pipeline.webp({ quality: siblingQuality.webp })).toBuffer();
|
|
686
|
+
await writeFile3(siblingFile, buffer);
|
|
687
|
+
} catch (error) {
|
|
688
|
+
console.warn(
|
|
689
|
+
`[stratawp:images] skipped ${format} for ${rel}: ${error instanceof Error ? error.message : String(error)}`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
540
694
|
}
|
|
541
|
-
return {};
|
|
542
|
-
},
|
|
543
|
-
async closeBundle() {
|
|
544
|
-
const root = process.cwd();
|
|
545
|
-
await generatePHPUtilities(root, images, css);
|
|
546
695
|
}
|
|
547
696
|
};
|
|
548
697
|
}
|
|
549
|
-
async function
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
698
|
+
async function isNewer(src, dest) {
|
|
699
|
+
try {
|
|
700
|
+
const [s, d] = await Promise.all([stat(src), stat(dest)]);
|
|
701
|
+
return s.mtimeMs > d.mtimeMs;
|
|
702
|
+
} catch {
|
|
703
|
+
return true;
|
|
553
704
|
}
|
|
554
|
-
let phpContent = `<?php
|
|
555
|
-
/**
|
|
556
|
-
* Lazy Loading Utilities
|
|
557
|
-
*
|
|
558
|
-
* Auto-generated by StrataWP
|
|
559
|
-
* DO NOT EDIT MANUALLY
|
|
560
|
-
*/
|
|
561
|
-
|
|
562
|
-
namespace StrataWP;
|
|
563
|
-
|
|
564
|
-
`;
|
|
565
|
-
if (images !== "none") {
|
|
566
|
-
phpContent += `/**
|
|
567
|
-
* Add loading="lazy" to images
|
|
568
|
-
*/
|
|
569
|
-
function add_lazy_loading_to_images( $content ) {
|
|
570
|
-
if ( is_admin() || is_feed() ) {
|
|
571
|
-
return $content;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Add loading="lazy" to img tags that don't have it
|
|
575
|
-
$content = preg_replace(
|
|
576
|
-
'/<img(?![^>]*loading=)([^>]*)>/i',
|
|
577
|
-
'<img loading="lazy"$1>',
|
|
578
|
-
$content
|
|
579
|
-
);
|
|
580
|
-
|
|
581
|
-
return $content;
|
|
582
705
|
}
|
|
583
706
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
707
|
+
// src/plugins/performance.ts
|
|
708
|
+
function strataWPPerformance(options = {}) {
|
|
709
|
+
const plugins = [];
|
|
710
|
+
if (options.criticalCSS === true || typeof options.criticalCSS === "object") {
|
|
711
|
+
const criticalOptions = typeof options.criticalCSS === "object" ? options.criticalCSS : { enabled: true };
|
|
712
|
+
plugins.push(strataWPCriticalCSS(criticalOptions));
|
|
588
713
|
}
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
*/
|
|
593
|
-
function lazy_load_css( $html, $handle, $href, $media ) {
|
|
594
|
-
// Skip critical CSS
|
|
595
|
-
if ( strpos( $handle, 'critical' ) !== false ) {
|
|
596
|
-
return $html;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Change media to "print" and add onload to switch to "all"
|
|
600
|
-
$html = str_replace(
|
|
601
|
-
"media='{$media}'",
|
|
602
|
-
"media='print' onload=\\"this.media='all'; this.onload=null;\\"",
|
|
603
|
-
$html
|
|
604
|
-
);
|
|
605
|
-
|
|
606
|
-
// Add noscript fallback
|
|
607
|
-
$noscript = sprintf(
|
|
608
|
-
'<noscript><link rel="stylesheet" href="%s" media="%s"></noscript>',
|
|
609
|
-
esc_url( $href ),
|
|
610
|
-
esc_attr( $media )
|
|
611
|
-
);
|
|
612
|
-
|
|
613
|
-
return $html . $noscript;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Uncomment to enable CSS lazy loading
|
|
617
|
-
// add_filter( 'style_loader_tag', __NAMESPACE__ . '\\lazy_load_css', 10, 4 );
|
|
618
|
-
|
|
619
|
-
`;
|
|
714
|
+
if (options.images !== false) {
|
|
715
|
+
const imageOptions = typeof options.images === "object" ? options.images : {};
|
|
716
|
+
plugins.push(strataWPImages(imageOptions));
|
|
620
717
|
}
|
|
621
|
-
|
|
622
|
-
* Preconnect to external domains
|
|
623
|
-
*/
|
|
624
|
-
function add_resource_hints( $urls, $relation_type ) {
|
|
625
|
-
if ( 'preconnect' === $relation_type ) {
|
|
626
|
-
$urls[] = array(
|
|
627
|
-
'href' => 'https://fonts.googleapis.com',
|
|
628
|
-
'crossorigin',
|
|
629
|
-
);
|
|
630
|
-
$urls[] = array(
|
|
631
|
-
'href' => 'https://fonts.gstatic.com',
|
|
632
|
-
'crossorigin',
|
|
633
|
-
);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
return $urls;
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
add_filter( 'wp_resource_hints', __NAMESPACE__ . '\\add_resource_hints', 10, 2 );
|
|
640
|
-
`;
|
|
641
|
-
fs3.writeFileSync(path3.join(incDir, "lazy-loading-generated.php"), phpContent);
|
|
642
|
-
console.log(" \u2713 Generated inc/lazy-loading-generated.php");
|
|
718
|
+
return plugins;
|
|
643
719
|
}
|
|
644
720
|
|
|
645
|
-
// src/
|
|
646
|
-
import path4 from "path";
|
|
721
|
+
// src/utils/update-checker.ts
|
|
647
722
|
import fs4 from "fs";
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
apply: "build",
|
|
663
|
-
async closeBundle() {
|
|
664
|
-
const root = process.cwd();
|
|
665
|
-
await generatePHPPreloader(root, assets, strategy);
|
|
723
|
+
import path5 from "path";
|
|
724
|
+
import os from "os";
|
|
725
|
+
import semver from "semver";
|
|
726
|
+
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
727
|
+
var STRATAWP_SCOPE = "@stratawp/";
|
|
728
|
+
var REGISTRY_URL = "https://registry.npmjs.org";
|
|
729
|
+
function getCachePath() {
|
|
730
|
+
return path5.join(os.homedir(), ".stratawp", "update-cache.json");
|
|
731
|
+
}
|
|
732
|
+
function loadCache() {
|
|
733
|
+
try {
|
|
734
|
+
const cachePath = getCachePath();
|
|
735
|
+
if (!fs4.existsSync(cachePath)) {
|
|
736
|
+
return null;
|
|
666
737
|
}
|
|
667
|
-
|
|
738
|
+
const cacheContent = fs4.readFileSync(cachePath, "utf-8");
|
|
739
|
+
return JSON.parse(cacheContent);
|
|
740
|
+
} catch {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
668
743
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
744
|
+
function saveCache(cache) {
|
|
745
|
+
try {
|
|
746
|
+
const cachePath = getCachePath();
|
|
747
|
+
const cacheDir = path5.dirname(cachePath);
|
|
748
|
+
if (!fs4.existsSync(cacheDir)) {
|
|
749
|
+
fs4.mkdirSync(cacheDir, { recursive: true });
|
|
750
|
+
}
|
|
751
|
+
fs4.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
|
|
752
|
+
} catch {
|
|
673
753
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
*/
|
|
687
|
-
function preload_assets(): void {
|
|
688
|
-
$preloads = array();
|
|
689
|
-
|
|
690
|
-
`;
|
|
691
|
-
if (assets.includes("fonts")) {
|
|
692
|
-
phpContent += ` // Preload fonts
|
|
693
|
-
$font_dir = get_template_directory() . '/dist/fonts';
|
|
694
|
-
$font_url = get_template_directory_uri() . '/dist/fonts';
|
|
695
|
-
|
|
696
|
-
if ( is_dir( $font_dir ) ) {
|
|
697
|
-
$fonts = glob( $font_dir . '/*.{woff2,woff}', GLOB_BRACE );
|
|
698
|
-
foreach ( $fonts as $font ) {
|
|
699
|
-
$font_name = basename( $font );
|
|
700
|
-
$preloads[] = sprintf(
|
|
701
|
-
'<link rel="preload" href="%s/%s" as="font" type="font/%s" crossorigin>',
|
|
702
|
-
esc_url( $font_url ),
|
|
703
|
-
esc_attr( $font_name ),
|
|
704
|
-
esc_attr( pathinfo( $font, PATHINFO_EXTENSION ) )
|
|
705
|
-
);
|
|
706
|
-
}
|
|
754
|
+
}
|
|
755
|
+
async function fetchLatestVersion(packageName) {
|
|
756
|
+
try {
|
|
757
|
+
const encodedName = packageName.replace("/", "%2F");
|
|
758
|
+
const url = `${REGISTRY_URL}/${encodedName}`;
|
|
759
|
+
const response = await fetch(url, {
|
|
760
|
+
headers: {
|
|
761
|
+
Accept: "application/json"
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
if (!response.ok) {
|
|
765
|
+
return null;
|
|
707
766
|
}
|
|
708
|
-
|
|
709
|
-
|
|
767
|
+
const data = await response.json();
|
|
768
|
+
return data["dist-tags"]?.latest || null;
|
|
769
|
+
} catch {
|
|
770
|
+
return null;
|
|
710
771
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
esc_url( $critical_url ),
|
|
724
|
-
esc_attr( $template_name )
|
|
725
|
-
);
|
|
772
|
+
}
|
|
773
|
+
function readInstalledPackages(packageJsonPath) {
|
|
774
|
+
if (!fs4.existsSync(packageJsonPath)) {
|
|
775
|
+
return {};
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
const packageJson = JSON.parse(fs4.readFileSync(packageJsonPath, "utf-8"));
|
|
779
|
+
const installed = {};
|
|
780
|
+
if (packageJson.dependencies) {
|
|
781
|
+
for (const [name, version] of Object.entries(packageJson.dependencies)) {
|
|
782
|
+
if (name.startsWith(STRATAWP_SCOPE) && typeof version === "string") {
|
|
783
|
+
installed[name] = version.replace(/^[\^~>=<]+/, "");
|
|
726
784
|
}
|
|
785
|
+
}
|
|
727
786
|
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
phpContent += ` // Preload critical JavaScript
|
|
733
|
-
$manifest_file = get_template_directory() . '/dist/.vite/manifest.json';
|
|
734
|
-
|
|
735
|
-
if ( file_exists( $manifest_file ) ) {
|
|
736
|
-
$manifest = json_decode( file_get_contents( $manifest_file ), true );
|
|
737
|
-
|
|
738
|
-
foreach ( $manifest as $entry ) {
|
|
739
|
-
if ( isset( $entry['isEntry'] ) && $entry['isEntry'] ) {
|
|
740
|
-
$preloads[] = sprintf(
|
|
741
|
-
'<link rel="modulepreload" href="%s/%s">',
|
|
742
|
-
esc_url( get_template_directory_uri() . '/dist' ),
|
|
743
|
-
esc_attr( $entry['file'] )
|
|
744
|
-
);
|
|
745
|
-
break; // Only preload main entry
|
|
746
|
-
}
|
|
787
|
+
if (packageJson.devDependencies) {
|
|
788
|
+
for (const [name, version] of Object.entries(packageJson.devDependencies)) {
|
|
789
|
+
if (name.startsWith(STRATAWP_SCOPE) && typeof version === "string") {
|
|
790
|
+
installed[name] = version.replace(/^[\^~>=<]+/, "");
|
|
747
791
|
}
|
|
792
|
+
}
|
|
748
793
|
}
|
|
749
|
-
|
|
750
|
-
|
|
794
|
+
return installed;
|
|
795
|
+
} catch {
|
|
796
|
+
return {};
|
|
751
797
|
}
|
|
752
|
-
phpContent += ` // Output preload tags
|
|
753
|
-
foreach ( $preloads as $preload ) {
|
|
754
|
-
echo $preload . "
|
|
755
|
-
";
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
add_action( 'wp_head', __NAMESPACE__ . '\\preload_assets', 2 );
|
|
760
|
-
|
|
761
|
-
/**
|
|
762
|
-
* DNS prefetch for external resources
|
|
763
|
-
*/
|
|
764
|
-
function dns_prefetch(): void {
|
|
765
|
-
$domains = array(
|
|
766
|
-
'//fonts.googleapis.com',
|
|
767
|
-
'//fonts.gstatic.com',
|
|
768
|
-
);
|
|
769
|
-
|
|
770
|
-
foreach ( $domains as $domain ) {
|
|
771
|
-
printf(
|
|
772
|
-
'<link rel="dns-prefetch" href="%s">' . "
|
|
773
|
-
",
|
|
774
|
-
esc_url( $domain )
|
|
775
|
-
);
|
|
776
|
-
}
|
|
777
798
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
// src/plugins/performance.ts
|
|
786
|
-
function strataWPPerformance(options = {}) {
|
|
787
|
-
const plugins = [];
|
|
788
|
-
if (options.criticalCSS !== false) {
|
|
789
|
-
const criticalOptions = typeof options.criticalCSS === "object" ? options.criticalCSS : { enabled: true };
|
|
790
|
-
plugins.push(strataWPCriticalCSS(criticalOptions));
|
|
791
|
-
}
|
|
792
|
-
if (options.lazyLoading !== false) {
|
|
793
|
-
const lazyOptions = typeof options.lazyLoading === "object" ? options.lazyLoading : { enabled: true };
|
|
794
|
-
plugins.push(strataWPLazyLoading(lazyOptions));
|
|
795
|
-
}
|
|
796
|
-
if (options.preload !== false) {
|
|
797
|
-
const preloadOptions = typeof options.preload === "object" ? options.preload : { enabled: true };
|
|
798
|
-
plugins.push(strataWPPreload(preloadOptions));
|
|
799
|
+
async function checkForUpdates(root) {
|
|
800
|
+
const packageJsonPath = path5.join(root, "package.json");
|
|
801
|
+
const installedPackages = readInstalledPackages(packageJsonPath);
|
|
802
|
+
const packageNames = Object.keys(installedPackages);
|
|
803
|
+
if (packageNames.length === 0) {
|
|
804
|
+
return null;
|
|
799
805
|
}
|
|
800
|
-
|
|
806
|
+
const cache = loadCache();
|
|
807
|
+
const newCache = {
|
|
808
|
+
packages: cache?.packages || {},
|
|
809
|
+
lastCheck: Date.now()
|
|
810
|
+
};
|
|
811
|
+
const updates = [];
|
|
812
|
+
await Promise.all(
|
|
813
|
+
packageNames.map(async (name) => {
|
|
814
|
+
const current = installedPackages[name];
|
|
815
|
+
let latest = null;
|
|
816
|
+
const cached = cache?.packages[name];
|
|
817
|
+
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
818
|
+
latest = cached.version;
|
|
819
|
+
} else {
|
|
820
|
+
latest = await fetchLatestVersion(name);
|
|
821
|
+
if (latest) {
|
|
822
|
+
newCache.packages[name] = {
|
|
823
|
+
version: latest,
|
|
824
|
+
fetchedAt: Date.now()
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
if (latest && semver.lt(current, latest)) {
|
|
829
|
+
updates.push({ name, current, latest });
|
|
830
|
+
}
|
|
831
|
+
})
|
|
832
|
+
);
|
|
833
|
+
saveCache(newCache);
|
|
834
|
+
return updates.length > 0 ? updates : null;
|
|
835
|
+
}
|
|
836
|
+
function checkForUpdatesAsync(logger, root) {
|
|
837
|
+
checkForUpdates(root).then((updates) => {
|
|
838
|
+
if (updates && updates.length > 0) {
|
|
839
|
+
logger.info("");
|
|
840
|
+
logger.info(" \x1B[33m\u{1F4E6} Updates available:\x1B[0m");
|
|
841
|
+
for (const update of updates) {
|
|
842
|
+
logger.info(` ${update.name}: ${update.current} -> \x1B[32m${update.latest}\x1B[0m`);
|
|
843
|
+
}
|
|
844
|
+
logger.info(" Run `stratawp update` to update");
|
|
845
|
+
logger.info("");
|
|
846
|
+
}
|
|
847
|
+
}).catch(() => {
|
|
848
|
+
});
|
|
801
849
|
}
|
|
802
850
|
|
|
803
851
|
// src/integrations/tailwind-preset.ts
|
|
@@ -986,9 +1034,8 @@ function strataWP(options = {}) {
|
|
|
986
1034
|
framework: "none"
|
|
987
1035
|
},
|
|
988
1036
|
performance = {
|
|
989
|
-
criticalCSS:
|
|
990
|
-
|
|
991
|
-
preload: true
|
|
1037
|
+
criticalCSS: false,
|
|
1038
|
+
images: true
|
|
992
1039
|
}
|
|
993
1040
|
} = options;
|
|
994
1041
|
const plugins = [
|
|
@@ -1029,6 +1076,14 @@ function strataWPCore(_options) {
|
|
|
1029
1076
|
return "fonts/[name].[hash][extname]";
|
|
1030
1077
|
}
|
|
1031
1078
|
return "assets/[name].[hash][extname]";
|
|
1079
|
+
},
|
|
1080
|
+
manualChunks: (id) => {
|
|
1081
|
+
if (id.includes("@wordpress/")) {
|
|
1082
|
+
return "wordpress";
|
|
1083
|
+
}
|
|
1084
|
+
if (id.includes("node_modules")) {
|
|
1085
|
+
return "vendor";
|
|
1086
|
+
}
|
|
1032
1087
|
}
|
|
1033
1088
|
}
|
|
1034
1089
|
}
|
|
@@ -1052,17 +1107,21 @@ function strataWPCore(_options) {
|
|
|
1052
1107
|
configureServer(server) {
|
|
1053
1108
|
server.httpServer?.once("listening", () => {
|
|
1054
1109
|
setTimeout(() => {
|
|
1055
|
-
const { logger } = config;
|
|
1110
|
+
const { logger, root } = config;
|
|
1056
1111
|
logger.info("\n \u26A1 StrataWP Dev Server Ready!\n");
|
|
1057
1112
|
logger.info(" WordPress integration active");
|
|
1058
1113
|
logger.info(" Block auto-discovery enabled");
|
|
1059
1114
|
logger.info(" PHP HMR watching for changes\n");
|
|
1115
|
+
if (_options.updateNotification?.enabled !== false) {
|
|
1116
|
+
checkForUpdatesAsync(logger, root);
|
|
1117
|
+
}
|
|
1060
1118
|
}, 100);
|
|
1061
1119
|
});
|
|
1062
1120
|
}
|
|
1063
1121
|
};
|
|
1064
1122
|
}
|
|
1065
1123
|
export {
|
|
1124
|
+
generateBlockAttributeTypes,
|
|
1066
1125
|
strataWP,
|
|
1067
1126
|
strataWPAssets,
|
|
1068
1127
|
strataWPBlocks,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stratawp/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Vite plugin for WordPress theme development with StrataWP",
|
|
5
5
|
"author": "Jon Imms",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -33,26 +33,32 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"dist"
|
|
35
35
|
],
|
|
36
|
-
"scripts": {
|
|
37
|
-
"dev": "tsup --watch",
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"test": "vitest",
|
|
40
|
-
"lint": "eslint src",
|
|
41
|
-
"clean": "rm -rf dist"
|
|
42
|
-
},
|
|
43
36
|
"dependencies": {
|
|
44
37
|
"chokidar": "^3.5.3",
|
|
45
38
|
"fast-glob": "^3.3.2",
|
|
46
39
|
"picocolors": "^1.0.0",
|
|
40
|
+
"semver": "^7.5.4",
|
|
41
|
+
"sharp": "^0.33.5",
|
|
42
|
+
"svgo": "^3.3.2",
|
|
47
43
|
"vite": "^5.0.10"
|
|
48
44
|
},
|
|
49
45
|
"devDependencies": {
|
|
50
46
|
"@types/node": "^20.10.5",
|
|
47
|
+
"@types/semver": "^7.5.8",
|
|
48
|
+
"@vitest/coverage-v8": "^1.0.4",
|
|
51
49
|
"tsup": "^8.0.1",
|
|
52
50
|
"typescript": "^5.3.3",
|
|
53
51
|
"vitest": "^1.0.4"
|
|
54
52
|
},
|
|
55
53
|
"peerDependencies": {
|
|
56
54
|
"vite": "^5.0.0"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"dev": "tsup --watch",
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:coverage": "vitest run --coverage",
|
|
62
|
+
"clean": "rm -rf dist"
|
|
57
63
|
}
|
|
58
|
-
}
|
|
64
|
+
}
|