@10up/wp-vite-plugins 1.0.0-alpha.2 → 1.0.0-alpha.4

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.
@@ -0,0 +1,95 @@
1
+ <?php
2
+ /**
3
+ * HotFile class.
4
+ *
5
+ * @package TenUp\WpViteDevServer
6
+ */
7
+
8
+ declare( strict_types = 1 );
9
+
10
+ namespace TenUp\WpViteDevServer;
11
+
12
+ /**
13
+ * The hot file a running Vite dev server writes next to the bridge
14
+ * (`.wp-vite-dev.json`): where the server is, and which of its plugins PHP
15
+ * has to react to.
16
+ *
17
+ * Its presence is the whole protocol. The dev server writes it on
18
+ * 'listening' and deletes it on exit, so reading it is one `file_exists()`
19
+ * when no server is running.
20
+ */
21
+ final class HotFile {
22
+
23
+ /**
24
+ * Create a hot file from its already-normalized values.
25
+ *
26
+ * @param string $origin Dev-server origin, without a trailing slash.
27
+ * @param string $base Vite's `base`, with a leading and trailing slash.
28
+ * @param string[] $plugins Names of the dev-server plugins PHP reacts to.
29
+ *
30
+ * @phpstan-param non-empty-string $origin
31
+ */
32
+ public function __construct(
33
+ public readonly string $origin,
34
+ public readonly string $base,
35
+ public readonly array $plugins,
36
+ ) {}
37
+
38
+ /**
39
+ * Read and normalize a hot file.
40
+ *
41
+ * @param string $path Absolute path of the hot file.
42
+ * @return self|null Null when the file is missing or has no usable origin.
43
+ */
44
+ public static function read( string $path ): ?self {
45
+ if ( ! file_exists( $path ) ) {
46
+ return null;
47
+ }
48
+
49
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
50
+ $contents = file_get_contents( $path );
51
+ $decoded = is_string( $contents ) ? json_decode( $contents, true ) : null;
52
+
53
+ $origin = is_array( $decoded ) && isset( $decoded['origin'] ) && is_string( $decoded['origin'] ) ? rtrim( $decoded['origin'], '/' ) : '';
54
+
55
+ if ( '' === $origin ) {
56
+ return null;
57
+ }
58
+
59
+ $base = isset( $decoded['base'] ) && is_string( $decoded['base'] ) ? $decoded['base'] : '/';
60
+ $base = '/' . ltrim( $base, '/' );
61
+ if ( ! str_ends_with( $base, '/' ) ) {
62
+ $base .= '/';
63
+ }
64
+
65
+ $plugins = isset( $decoded['plugins'] ) && is_array( $decoded['plugins'] ) ? $decoded['plugins'] : array();
66
+
67
+ return new self(
68
+ $origin,
69
+ $base,
70
+ array_values( array_filter( $plugins, 'is_string' ) )
71
+ );
72
+ }
73
+
74
+ /**
75
+ * The URL the dev server serves a project-relative path at.
76
+ *
77
+ * @param string $path Project-relative path, e.g. an `.asset.php` 'source'.
78
+ * @return string
79
+ *
80
+ * @phpstan-return non-empty-string
81
+ */
82
+ public function url( string $path ): string {
83
+ return $this->origin . $this->base . ltrim( $path, '/' );
84
+ }
85
+
86
+ /**
87
+ * Whether the dev server reported a plugin by name.
88
+ *
89
+ * @param string $name Plugin name, e.g. 'vite:react-refresh'.
90
+ * @return bool
91
+ */
92
+ public function has_plugin( string $name ): bool {
93
+ return in_array( $name, $this->plugins, true );
94
+ }
95
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@10up/wp-vite-plugins",
3
- "version": "1.0.0-alpha.2",
3
+ "version": "1.0.0-alpha.4",
4
4
  "description": "Vite plugins for building modern WordPress projects the 10up way.",
5
5
  "keywords": [
6
6
  "vite",
@@ -18,79 +18,52 @@
18
18
  "license": "GPL-2.0-or-later",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "git+https://github.com/10up/wp-vite-plugins.git"
21
+ "url": "git+https://github.com/10up/wp-tooling.git",
22
+ "directory": "packages/wp-vite-plugins"
22
23
  },
23
24
  "bugs": {
24
- "url": "https://github.com/10up/wp-vite-plugins/issues"
25
+ "url": "https://github.com/10up/wp-tooling/issues"
25
26
  },
26
- "homepage": "https://github.com/10up/wp-vite-plugins#readme",
27
+ "homepage": "https://github.com/10up/wp-tooling/tree/trunk/packages/wp-vite-plugins#readme",
27
28
  "type": "module",
28
29
  "exports": {
29
30
  ".": {
30
31
  "types": "./dist/index.d.ts",
31
32
  "default": "./dist/index.js"
32
33
  },
33
- "./build": {
34
- "types": "./dist/build.d.ts",
35
- "default": "./dist/build.js"
36
- },
37
- "./watch": {
38
- "types": "./dist/watch.d.ts",
39
- "default": "./dist/watch.js"
40
- },
41
34
  "./package.json": "./package.json"
42
35
  },
43
36
  "types": "./dist/index.d.ts",
44
- "bin": {
45
- "wp-vite": "./dist/cli.js"
46
- },
47
37
  "files": [
48
38
  "dist",
49
39
  "README.md",
50
40
  "LICENSE"
51
41
  ],
52
42
  "publishConfig": {
53
- "access": "public",
54
- "tag": "beta"
43
+ "access": "public"
55
44
  },
56
45
  "engines": {
57
- "node": ">=20.11"
46
+ "node": "^22.18.0 || ^24.11.0 || >=26.0.0"
58
47
  },
59
48
  "scripts": {
60
- "build": "tsup",
61
- "dev": "tsup --watch",
62
- "test": "vitest run",
63
- "test:watch": "vitest",
64
- "lint": "eslint .",
65
- "format": "prettier --write \"src/**/*.{ts,js,json}\"",
66
- "typecheck": "tsc --noEmit",
67
- "prepublishOnly": "npm run build",
68
- "changeset": "changeset",
69
- "prepare": "husky"
49
+ "build": "vp pack",
50
+ "dev": "vp pack --watch",
51
+ "test": "vp test run",
52
+ "test:watch": "vp test",
53
+ "prepublishOnly": "npm run build"
70
54
  },
71
55
  "dependencies": {
72
- "es-module-lexer": "^2.1.0",
56
+ "browserslist": "^4.29.5",
57
+ "es-module-lexer": "^3.0.4",
73
58
  "fast-glob": "^3.3.3",
74
- "magic-string": "^0.30.21"
59
+ "lightningcss": "^1.33.0",
60
+ "magic-string": "^1.4.5"
75
61
  },
76
62
  "peerDependencies": {
77
- "vite": "^8.0.0"
63
+ "vite": "^8.0.0 || ^1.0.0"
78
64
  },
79
65
  "devDependencies": {
80
- "@changesets/cli": "^2.27.0",
81
- "@eslint/js": "^9.0.0",
82
- "@types/node": "^22.0.0",
83
- "@typescript-eslint/eslint-plugin": "^8.0.0",
84
- "@typescript-eslint/parser": "^8.0.0",
85
- "eslint": "^9.0.0",
86
- "eslint-config-prettier": "^9.1.0",
87
- "globals": "^17.6.0",
88
- "husky": "^9.1.0",
89
- "lint-staged": "^15.2.0",
90
- "prettier": "^3.3.0",
91
- "tsup": "^8.3.0",
92
- "typescript": "^5.6.0",
93
- "vite": "^8.0.0",
94
- "vitest": "^4.1.8"
66
+ "@arethetypeswrong/core": "^0.18.5",
67
+ "publint": "^0.3.25"
95
68
  }
96
69
  }
package/dist/build.d.ts DELETED
@@ -1,13 +0,0 @@
1
- import { R as RunnerOptions } from './runner-WUYCS-nx.js';
2
- export { B as BIN_CANDIDATES, D as DEFAULT_BUILD_ARGS, a as DEFAULT_WATCH_ARGS, b as DetectModulePassOptions, L as LinePrefixer, M as ModulePassDetection, O as OutputSink, P as ParsedCliArgs, c as PassName, d as ResolvedBin, e as RunnerName, f as RunnerSignal, g as cleanOutDir, h as createLinePrefixer, i as detectModulePass, p as parseCliArgs, r as resolveViteBin } from './runner-WUYCS-nx.js';
3
-
4
- /** Options for `build()`. Identical to `watch()`'s — see `RunnerOptions`. */
5
- type BuildOptions = RunnerOptions;
6
- /**
7
- * Run both passes in order. Resolves with the process exit code: `0` when
8
- * every pass succeeded, the failing pass' code when one didn't, `128 + signo`
9
- * (`130` for SIGINT) when interrupted.
10
- */
11
- declare function build(options?: BuildOptions): Promise<number>;
12
-
13
- export { type BuildOptions, RunnerOptions, build };
package/dist/build.js DELETED
@@ -1,396 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { existsSync, readFileSync } from 'fs';
3
- import { rm } from 'fs/promises';
4
- import { resolve, join, parse, dirname, isAbsolute, sep } from 'path';
5
-
6
- // src/runner.ts
7
- var DEFAULT_BUILD_ARGS = ["build", "--no-emptyOutDir", "--no-clearScreen"];
8
- var DEFAULT_WATCH_ARGS = ["build", "--watch", "--no-emptyOutDir", "--no-clearScreen"];
9
- var BIN_CANDIDATES = ["vite", "vp"];
10
- function spawnPass(options) {
11
- const { pass, stdout, stderr } = options;
12
- const child = spawn(options.bin, options.argv, {
13
- cwd: options.cwd,
14
- env: {
15
- ...process.env,
16
- ...options.env,
17
- WP_VITE_RUNNER: options.runner,
18
- WP_VITE_PASS: pass,
19
- // Kept for the documented watch-only guard:
20
- // `emptyOutDir: !isModulePass && !process.env.WP_VITE_UNIFIED_WATCH`.
21
- ...options.runner === "watch" ? { WP_VITE_UNIFIED_WATCH: "1" } : {}
22
- },
23
- stdio: ["ignore", "pipe", "pipe"],
24
- // .cmd shims are not directly executable on Windows.
25
- shell: process.platform === "win32"
26
- });
27
- const outPrefixer = createLinePrefixer(`[${pass}] `, (text) => stdout.write(text));
28
- const errPrefixer = createLinePrefixer(`[${pass}] `, (text) => stderr.write(text));
29
- child.stdout?.setEncoding("utf8");
30
- child.stdout?.on("data", (chunk) => outPrefixer.write(chunk));
31
- child.stderr?.setEncoding("utf8");
32
- child.stderr?.on("data", (chunk) => errPrefixer.write(chunk));
33
- const result = new Promise((settle) => {
34
- let settled = false;
35
- const finish = (passResult) => {
36
- if (settled) return;
37
- settled = true;
38
- outPrefixer.flush();
39
- errPrefixer.flush();
40
- settle(passResult);
41
- };
42
- child.on("error", (error) => {
43
- stderr.write(`[wp-vite] the ${pass} pass failed to start: ${error.message}
44
- `);
45
- finish({ pass, code: null, signal: null, error });
46
- });
47
- child.on("close", (code, signal) => finish({ pass, code, signal }));
48
- });
49
- return { pass, child, result };
50
- }
51
- function passArgs(base, pass, modulesMode, extra = []) {
52
- return [...base, ...pass === "modules" ? ["--mode", modulesMode] : [], ...extra];
53
- }
54
- function forwardSignals(handler) {
55
- const bound = ["SIGINT", "SIGTERM"].map(
56
- (signal) => [signal, () => handler(signal)]
57
- );
58
- for (const [signal, listener] of bound) process.on(signal, listener);
59
- return () => {
60
- for (const [signal, listener] of bound) process.off(signal, listener);
61
- };
62
- }
63
- function describeExit(code, signal) {
64
- if (signal) return `signal ${signal}`;
65
- if (code === null) return "unknown cause";
66
- return `exit code ${code}`;
67
- }
68
- var SIGNAL_EXIT_CODES = {
69
- SIGINT: 130,
70
- SIGTERM: 143,
71
- SIGHUP: 129,
72
- SIGQUIT: 131
73
- };
74
- function signalExitCode(signal) {
75
- if (!signal) return 1;
76
- return SIGNAL_EXIT_CODES[signal] ?? 1;
77
- }
78
- function passExitCode(result) {
79
- if (result.error) return 1;
80
- if (result.code !== null) return result.code;
81
- return signalExitCode(result.signal);
82
- }
83
- var VITE_CONFIG_FILES = [
84
- "vite.config.ts",
85
- "vite.config.mts",
86
- "vite.config.cts",
87
- "vite.config.js",
88
- "vite.config.mjs",
89
- "vite.config.cjs"
90
- ];
91
- function detectModulePass(options = {}) {
92
- if (typeof options.modules === "boolean") {
93
- return {
94
- modules: options.modules,
95
- reason: `explicit --${options.modules ? "modules" : "no-modules"} flag`
96
- };
97
- }
98
- const cwd = resolve(options.cwd ?? process.cwd());
99
- const modulesMode = options.modulesMode ?? "modules";
100
- const escapedMode = escapeRegExp(modulesMode);
101
- const scriptMatch = new RegExp(`(?:--mode[=\\s]+|(?:^|\\s)-m\\s+)${escapedMode}(?![\\w-])`);
102
- const packageJson = readJson(join(cwd, "package.json"));
103
- const scripts = packageJson?.scripts;
104
- if (scripts && typeof scripts === "object") {
105
- for (const [name, command] of Object.entries(scripts)) {
106
- if (typeof command === "string" && scriptMatch.test(command)) {
107
- return {
108
- modules: true,
109
- reason: `package.json script "${name}" runs --mode ${modulesMode}`
110
- };
111
- }
112
- }
113
- }
114
- const configMatch = new RegExp(`(['"\`])${escapedMode}\\1`);
115
- for (const fileName of VITE_CONFIG_FILES) {
116
- const filePath = join(cwd, fileName);
117
- const source = readFileIfExists(filePath);
118
- if (source === null) continue;
119
- if (configMatch.test(source)) {
120
- return { modules: true, reason: `${fileName} mentions the "${modulesMode}" mode` };
121
- }
122
- return {
123
- modules: false,
124
- reason: `${fileName} never mentions the "${modulesMode}" mode`
125
- };
126
- }
127
- return {
128
- modules: false,
129
- reason: `no package.json script or Vite config mentions the "${modulesMode}" mode`
130
- };
131
- }
132
- function resolveViteBin(cwd = process.cwd(), candidates = BIN_CANDIDATES) {
133
- const searched = [];
134
- for (const binDir of nodeModulesBinDirs(cwd)) {
135
- for (const name of candidates) {
136
- for (const fileName of binFileNames(name)) {
137
- const candidatePath = join(binDir, fileName);
138
- searched.push(candidatePath);
139
- if (existsSync(candidatePath)) return { name, path: candidatePath };
140
- }
141
- }
142
- }
143
- throw new Error(
144
- `[wp-vite] could not find a Vite binary (${candidates.join(" or ")}) in any node_modules/.bin above ${cwd}. Install vite in the package, or pass --bin <name|path>. Looked at:
145
- ${searched.join("\n ")}`
146
- );
147
- }
148
- function resolveBin(bin, cwd) {
149
- if (!bin) return resolveViteBin(cwd).path;
150
- if (isAbsolute(bin) || bin.includes("/") || bin.includes("\\")) return bin;
151
- try {
152
- return resolveViteBin(cwd, [bin]).path;
153
- } catch {
154
- return bin;
155
- }
156
- }
157
- function nodeModulesBinDirs(from) {
158
- const dirs = [];
159
- let current = resolve(from);
160
- const { root } = parse(current);
161
- while (true) {
162
- dirs.push(join(current, "node_modules", ".bin"));
163
- if (current === root) break;
164
- const parent = dirname(current);
165
- if (parent === current) break;
166
- current = parent;
167
- }
168
- return dirs;
169
- }
170
- function binFileNames(name) {
171
- return process.platform === "win32" ? [`${name}.cmd`, name] : [name];
172
- }
173
- async function cleanOutDir(outDir = "dist", cwd = process.cwd()) {
174
- const base = resolve(cwd);
175
- const target = isAbsolute(outDir) ? resolve(outDir) : resolve(base, outDir);
176
- if (target === parse(target).root || target === base || !target.startsWith(base + sep)) {
177
- throw new Error(
178
- `[wp-vite] refusing to clean ${target}: the out dir must be inside ${base}`
179
- );
180
- }
181
- await rm(target, { recursive: true, force: true });
182
- return target;
183
- }
184
- var ESCAPE = String.fromCharCode(27);
185
- var ERASE_SEQUENCE = new RegExp(`^(?:${ESCAPE}\\[[0-2]?[KJ])+`);
186
- function createLinePrefixer(prefix, write) {
187
- let buffer = "";
188
- const emit = (line) => {
189
- const withoutCrlf = line.endsWith("\r") ? line.slice(0, -1) : line;
190
- const rendered = withoutCrlf.split("\r").pop() ?? "";
191
- const content = rendered.replace(ERASE_SEQUENCE, "");
192
- write(content.length === 0 ? "\n" : `${prefix}${content}
193
- `);
194
- };
195
- return {
196
- write(chunk) {
197
- buffer += chunk;
198
- const lines = buffer.split("\n");
199
- buffer = lines.pop() ?? "";
200
- for (const line of lines) emit(line);
201
- },
202
- flush() {
203
- if (buffer.length === 0) return;
204
- const pending = buffer;
205
- buffer = "";
206
- emit(pending);
207
- }
208
- };
209
- }
210
- function parseCliArgs(argv) {
211
- const parsed = {
212
- command: void 0,
213
- options: {},
214
- help: false,
215
- version: false,
216
- unknown: []
217
- };
218
- const passthrough = [];
219
- let index = 0;
220
- for (; index < argv.length; index += 1) {
221
- const arg = argv[index];
222
- if (arg === "--") {
223
- passthrough.push(...argv.slice(index + 1));
224
- break;
225
- }
226
- if (!arg.startsWith("-")) {
227
- if (parsed.command === void 0) parsed.command = arg;
228
- else parsed.unknown.push(arg);
229
- continue;
230
- }
231
- const equalsAt = arg.indexOf("=");
232
- const flag = equalsAt === -1 ? arg : arg.slice(0, equalsAt);
233
- const inlineValue = equalsAt === -1 ? void 0 : arg.slice(equalsAt + 1);
234
- const takeValue = () => {
235
- if (inlineValue !== void 0) return inlineValue;
236
- const next = argv[index + 1];
237
- if (next === void 0 || next.startsWith("-")) return void 0;
238
- index += 1;
239
- return next;
240
- };
241
- switch (flag) {
242
- case "-h":
243
- case "--help":
244
- parsed.help = true;
245
- break;
246
- case "-v":
247
- case "--version":
248
- parsed.version = true;
249
- break;
250
- case "--out-dir":
251
- case "--outDir": {
252
- const value = takeValue();
253
- if (value === void 0) parsed.unknown.push(`${flag} (missing value)`);
254
- else parsed.options.outDir = value;
255
- break;
256
- }
257
- case "--modules-mode":
258
- case "--modulesMode": {
259
- const value = takeValue();
260
- if (value === void 0) parsed.unknown.push(`${flag} (missing value)`);
261
- else parsed.options.modulesMode = value;
262
- break;
263
- }
264
- case "--bin": {
265
- const value = takeValue();
266
- if (value === void 0) parsed.unknown.push(`${flag} (missing value)`);
267
- else parsed.options.bin = value;
268
- break;
269
- }
270
- case "--cwd": {
271
- const value = takeValue();
272
- if (value === void 0) parsed.unknown.push(`${flag} (missing value)`);
273
- else parsed.options.cwd = value;
274
- break;
275
- }
276
- case "--modules":
277
- parsed.options.modules = true;
278
- break;
279
- case "--no-modules":
280
- parsed.options.modules = false;
281
- break;
282
- case "--clean":
283
- parsed.options.clean = true;
284
- break;
285
- case "--no-clean":
286
- parsed.options.clean = false;
287
- break;
288
- default:
289
- parsed.unknown.push(flag);
290
- }
291
- }
292
- if (passthrough.length > 0) parsed.options.args = passthrough;
293
- return parsed;
294
- }
295
- async function prepareRun(options, runner) {
296
- const cwd = resolve(options.cwd ?? process.cwd());
297
- const outDir = options.outDir ?? "dist";
298
- const modulesMode = options.modulesMode ?? "modules";
299
- const stdout = options.stdout ?? process.stdout;
300
- const stderr = options.stderr ?? process.stderr;
301
- const bin = resolveBin(options.bin, cwd);
302
- const detection = detectModulePass({ cwd, modulesMode, modules: options.modules });
303
- const passes = detection.modules ? ["script", "modules"] : ["script"];
304
- if (options.clean !== false) {
305
- const cleaned = await cleanOutDir(outDir, cwd);
306
- stdout.write(`[wp-vite] cleaned ${cleaned}
307
- `);
308
- }
309
- stdout.write(
310
- `[wp-vite] module pass ${detection.modules ? "enabled" : "disabled"} \u2014 ${detection.reason}; ${"building"} ${passes.join(" then ")} via ${bin}
311
- `
312
- );
313
- return { cwd, bin, modulesMode, passes, stdout, stderr };
314
- }
315
- function escapeRegExp(value) {
316
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
317
- }
318
- function readFileIfExists(filePath) {
319
- try {
320
- return readFileSync(filePath, "utf8");
321
- } catch {
322
- return null;
323
- }
324
- }
325
- function readJson(filePath) {
326
- const source = readFileIfExists(filePath);
327
- if (source === null) return null;
328
- try {
329
- return JSON.parse(source);
330
- } catch {
331
- return null;
332
- }
333
- }
334
-
335
- // src/build.ts
336
- async function build(options = {}) {
337
- const { cwd, bin, modulesMode, passes, stdout, stderr } = await prepareRun(options);
338
- let current;
339
- let interrupted = false;
340
- let interruptCode = 0;
341
- const disposeSignals = options.handleSignals === false ? () => {
342
- } : forwardSignals((signal) => {
343
- if (interrupted) return;
344
- interrupted = true;
345
- interruptCode = signalExitCode(signal);
346
- if (current && current.exitCode === null && current.signalCode === null) {
347
- current.kill(signal);
348
- }
349
- });
350
- try {
351
- for (const [index, pass] of passes.entries()) {
352
- if (interrupted) {
353
- stderr.write(`[wp-vite] interrupted \u2014 skipping the ${pass} pass
354
- `);
355
- return interruptCode;
356
- }
357
- const spawned = spawnPass({
358
- pass,
359
- runner: "build",
360
- bin,
361
- cwd,
362
- argv: passArgs(
363
- options.buildArgs ?? DEFAULT_BUILD_ARGS,
364
- pass,
365
- modulesMode,
366
- options.args ?? []
367
- ),
368
- env: options.env,
369
- stdout,
370
- stderr
371
- });
372
- current = spawned.child;
373
- const result = await spawned.result;
374
- current = void 0;
375
- const code = passExitCode(result);
376
- if (code === 0) continue;
377
- const skipped = passes.slice(index + 1);
378
- stderr.write(
379
- `[wp-vite] the ${pass} pass failed (${describeExit(result.code, result.signal)})` + (skipped.length > 0 ? ` \u2014 skipping the ${skipped.join(" and ")} pass` : "") + "\n"
380
- );
381
- return code;
382
- }
383
- stdout.write(
384
- passes.length > 1 ? `[wp-vite] built ${passes.join(" + ")} into one dist
385
- ` : `[wp-vite] built the ${passes[0]} pass
386
- `
387
- );
388
- return 0;
389
- } finally {
390
- disposeSignals();
391
- }
392
- }
393
-
394
- export { BIN_CANDIDATES, DEFAULT_BUILD_ARGS, DEFAULT_WATCH_ARGS, build, cleanOutDir, createLinePrefixer, detectModulePass, parseCliArgs, resolveViteBin };
395
- //# sourceMappingURL=build.js.map
396
- //# sourceMappingURL=build.js.map