what-compiler 0.13.4 → 0.13.6
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/dist/babel-plugin.min.js +1 -1
- package/dist/index.min.js +21 -15
- package/dist/vite-plugin.min.js +21 -15
- package/package.json +2 -2
- package/src/babel-plugin.js +376 -147
- package/src/vite-plugin.js +111 -10
package/src/vite-plugin.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import path from 'path';
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
12
13
|
import { transformSync } from '@babel/core';
|
|
13
14
|
import whatBabelPlugin from './babel-plugin.js';
|
|
14
15
|
import { generateRoutesModule } from './file-router.js';
|
|
@@ -57,15 +58,16 @@ export function jsxPreserveConfig({ rolldownVersion, viteVersion } = {}) {
|
|
|
57
58
|
: { esbuild: { jsx: 'preserve' } };
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
async function detectViteVersion(root) {
|
|
62
|
+
// A workspace-linked compiler can resolve a different Vite than its consumer.
|
|
63
|
+
// Multiple apps may also build with different versions in the same process.
|
|
64
|
+
try {
|
|
65
|
+
const require = createRequire(path.resolve(root || process.cwd(), 'package.json'));
|
|
66
|
+
return require('vite/package.json').version || '';
|
|
67
|
+
} catch {
|
|
68
|
+
// Keep programmatic/global Vite use working when the app has no local copy.
|
|
69
|
+
return import('vite').then((vite) => vite.version || '').catch(() => '');
|
|
67
70
|
}
|
|
68
|
-
return viteVersionPromise;
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
// Pattern: exported function starting with uppercase = component
|
|
@@ -73,6 +75,84 @@ const COMPONENT_EXPORT_RE = /export\s+(?:default\s+)?function\s+([A-Z]\w*)/;
|
|
|
73
75
|
// Pattern: files that are likely signal/store/utility files
|
|
74
76
|
const UTILITY_FILE_RE = /(?:store|signal|state|context|util|helper|lib|config)\b/i;
|
|
75
77
|
|
|
78
|
+
// --- SSR guard --------------------------------------------------------------
|
|
79
|
+
//
|
|
80
|
+
// A module that what-compiler lowers cannot run on a server, and the failures it
|
|
81
|
+
// produces without this guard both name the wrong thing.
|
|
82
|
+
//
|
|
83
|
+
// The lowering emits a module-scope `const _tmpl$0 = _$template("<div>...")`,
|
|
84
|
+
// and `_$template` calls `document.createElement('template')` EAGERLY. So the
|
|
85
|
+
// module throws `ReferenceError: document is not defined` at IMPORT time, before
|
|
86
|
+
// any render function runs, with a stack that points into what-core rather than
|
|
87
|
+
// at the file the developer wrote. If a DOM shim happens to exist, the component
|
|
88
|
+
// instead returns a cloned Element and what-server's assertSafeTag reports
|
|
89
|
+
// ERR_COMPILED_JSX_IN_SSR.
|
|
90
|
+
//
|
|
91
|
+
// Both are runtime failures for something decidable at build time: if Vite is
|
|
92
|
+
// transforming this module for the SSR environment and the transform emitted
|
|
93
|
+
// DOM-building code, the resulting bundle cannot work. Fail here, name the file,
|
|
94
|
+
// and name the two configurations that DO server-render, rather than emitting a
|
|
95
|
+
// bundle whose only possible behaviour is to crash.
|
|
96
|
+
//
|
|
97
|
+
// This is a guard, not a feature. It does not make compiled JSX server-render;
|
|
98
|
+
// what-compiler has no hydratable/SSR codegen target. See
|
|
99
|
+
// docs/SSR-COMPILED-JSX-SCOPING.md for the seams and the staged plan.
|
|
100
|
+
|
|
101
|
+
// The two compiler-generated locals that make a module client-only.
|
|
102
|
+
//
|
|
103
|
+
// _$template — hoisted to module scope and calls
|
|
104
|
+
// document.createElement('template') EAGERLY, so the
|
|
105
|
+
// module throws at import time on a server.
|
|
106
|
+
// _$createComponent — runs the component and builds its DOM at call time.
|
|
107
|
+
//
|
|
108
|
+
// Deliberately NOT in this list: _$componentVNode, which is _$createComponent
|
|
109
|
+
// stopping one step short of createDOM (what-core render.js). A module whose
|
|
110
|
+
// only JSX is the argument of a hydrate() call emits that and nothing else, and
|
|
111
|
+
// it is import-safe, so flagging it would be wrong. The other helpers
|
|
112
|
+
// (_$insert, _$spread, _$setProp, ...) are all reached only THROUGH a template,
|
|
113
|
+
// so they add no cases and would only widen the blast radius.
|
|
114
|
+
//
|
|
115
|
+
// The `_$` prefix is compiler-generated and never written by hand, which is what
|
|
116
|
+
// makes matching on the name safe.
|
|
117
|
+
const DOM_BUILDING_LOCALS = /\b_\$(?:template|createComponent)\b/;
|
|
118
|
+
|
|
119
|
+
function buildsDom(outputCode) {
|
|
120
|
+
return DOM_BUILDING_LOCALS.test(outputCode);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ssrGuardError(id) {
|
|
124
|
+
// ERROR_CODES.COMPILED_JSX_IN_SSR
|
|
125
|
+
return Object.assign(
|
|
126
|
+
new Error(
|
|
127
|
+
`[what-compiler] ${id} is being compiled for the server, but what-compiler's ` +
|
|
128
|
+
'JSX output is client-only. It lowers JSX to module-scope _$template() calls ' +
|
|
129
|
+
'that run document.createElement() at import time, so this module throws ' +
|
|
130
|
+
'"document is not defined" the moment a server imports it.\n\n' +
|
|
131
|
+
'Server-rendered views have two supported spellings:\n' +
|
|
132
|
+
' 1. Author them with h() from what-framework.\n' +
|
|
133
|
+
' 2. Compile them with the automatic JSX runtime instead of what-compiler ' +
|
|
134
|
+
'(jsxImportSource: "what-framework"), which emits h() calls that ' +
|
|
135
|
+
'renderToString and renderToHydratableString understand.\n\n' +
|
|
136
|
+
'If a DOM already exists in this process on purpose, set ssrGuard: false ' +
|
|
137
|
+
'on the plugin. The likeliest reason is a test runner: Vitest applies an ' +
|
|
138
|
+
"SSR transform under `environment: 'node'`, so a component test that " +
|
|
139
|
+
'shims a DOM itself lands here. Note that with the guard off the result ' +
|
|
140
|
+
'is a full client render, not SSR.'
|
|
141
|
+
),
|
|
142
|
+
{ code: 'ERR_COMPILED_JSX_IN_SSR', id, plugin: 'vite-plugin-what' },
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Vite reports "this transform is for the server" in two ways depending on its
|
|
147
|
+
// major: the third `transform` argument (`{ ssr: true }`) on every version, and
|
|
148
|
+
// the Environment API (`this.environment.name === 'ssr'`) from Vite 6. Read both
|
|
149
|
+
// — a plugin that checked only one would silently stop guarding on the other.
|
|
150
|
+
function isSsrTransform(transformOptions, ctx) {
|
|
151
|
+
if (transformOptions && transformOptions.ssr) return true;
|
|
152
|
+
const env = ctx && ctx.environment;
|
|
153
|
+
return !!(env && env.name === 'ssr');
|
|
154
|
+
}
|
|
155
|
+
|
|
76
156
|
export default function whatVitePlugin(options = {}) {
|
|
77
157
|
const {
|
|
78
158
|
// File extensions to process
|
|
@@ -92,6 +172,10 @@ export default function whatVitePlugin(options = {}) {
|
|
|
92
172
|
// against package sources instead — needed e.g. in a monorepo where
|
|
93
173
|
// workspace-linked dist/ output may be stale or absent. See config() below.
|
|
94
174
|
prodBundles = true,
|
|
175
|
+
// Refuse to lower JSX for a module in the SSR graph. See ssrGuardError().
|
|
176
|
+
// Set to false only if you have installed a DOM in the server process and
|
|
177
|
+
// accept that the render is a full client render, not SSR.
|
|
178
|
+
ssrGuard = true,
|
|
95
179
|
} = options;
|
|
96
180
|
|
|
97
181
|
let rootDir = '';
|
|
@@ -182,7 +266,7 @@ export default function whatVitePlugin(options = {}) {
|
|
|
182
266
|
},
|
|
183
267
|
|
|
184
268
|
// Transform JSX files
|
|
185
|
-
transform(code, id) {
|
|
269
|
+
transform(code, id, transformOptions) {
|
|
186
270
|
const cleanId = id.replace(/[?#].*$/, '');
|
|
187
271
|
const hasJsx = patternMatches(include, cleanId);
|
|
188
272
|
const isScriptModule = SCRIPT_MODULE_RE.test(cleanId);
|
|
@@ -198,6 +282,8 @@ export default function whatVitePlugin(options = {}) {
|
|
|
198
282
|
// they contain no JSX.
|
|
199
283
|
if (!hasJsx && !hasServerActions) return null;
|
|
200
284
|
|
|
285
|
+
const guardSsr = ssrGuard && isSsrTransform(transformOptions, this);
|
|
286
|
+
|
|
201
287
|
try {
|
|
202
288
|
const result = transformSync(code, {
|
|
203
289
|
filename: id,
|
|
@@ -227,6 +313,16 @@ export default function whatVitePlugin(options = {}) {
|
|
|
227
313
|
|
|
228
314
|
let outputCode = result.code;
|
|
229
315
|
|
|
316
|
+
// Decided from the OUTPUT, not the filename. `.jsx` in the include
|
|
317
|
+
// pattern says the file MAY contain JSX, not that any was lowered, and a
|
|
318
|
+
// .js/.ts module compiled here purely for its server-action metadata
|
|
319
|
+
// emits nothing DOM-building at all. Both would be false positives.
|
|
320
|
+
// buildsDom() asks the only question that matters: did this transform
|
|
321
|
+
// emit code that constructs DOM?
|
|
322
|
+
if (guardSsr && buildsDom(outputCode)) {
|
|
323
|
+
throw ssrGuardError(cleanId);
|
|
324
|
+
}
|
|
325
|
+
|
|
230
326
|
// HMR: append hot boundary code for component files in dev mode
|
|
231
327
|
if (hot && isDevMode && !production) {
|
|
232
328
|
const isComponentFile = isComponentModule(code, id);
|
|
@@ -241,6 +337,11 @@ export default function whatVitePlugin(options = {}) {
|
|
|
241
337
|
map: result.map
|
|
242
338
|
};
|
|
243
339
|
} catch (error) {
|
|
340
|
+
// The SSR guard is a verdict on a transform that SUCCEEDED, not a Babel
|
|
341
|
+
// failure. Passing it through the enrichment below would log "[what]
|
|
342
|
+
// Error transforming <file>" over it, which says the compile broke when
|
|
343
|
+
// it did not, and buries the part the developer has to read.
|
|
344
|
+
if (error && error.code === 'ERR_COMPILED_JSX_IN_SSR') throw error;
|
|
244
345
|
// Enrich Babel errors with file context for the error overlay
|
|
245
346
|
error.plugin = 'vite-plugin-what';
|
|
246
347
|
if (!error.id) error.id = id;
|
|
@@ -297,7 +398,7 @@ export default function whatVitePlugin(options = {}) {
|
|
|
297
398
|
// jsxPreserveConfig picks the right option key for the running version.
|
|
298
399
|
const jsxPreserve = jsxPreserveConfig({
|
|
299
400
|
rolldownVersion: this?.meta?.rolldownVersion,
|
|
300
|
-
viteVersion: await detectViteVersion(),
|
|
401
|
+
viteVersion: await detectViteVersion(config.root),
|
|
301
402
|
});
|
|
302
403
|
return {
|
|
303
404
|
...(useProdCondition ? { resolve: { conditions: ['production'] } } : {}),
|