what-compiler 0.8.4 → 0.11.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/dist/babel-plugin.js +539 -87
- package/dist/babel-plugin.js.map +2 -2
- package/dist/babel-plugin.min.js +1 -1
- package/dist/babel-plugin.min.js.map +3 -3
- package/dist/file-router.js +78 -2
- package/dist/file-router.js.map +2 -2
- package/dist/file-router.min.js +3 -2
- package/dist/file-router.min.js.map +3 -3
- package/dist/index.js +595 -93
- package/dist/index.js.map +2 -2
- package/dist/index.min.js +6 -6
- package/dist/index.min.js.map +3 -3
- package/dist/vite-plugin.js +595 -93
- package/dist/vite-plugin.js.map +2 -2
- package/dist/vite-plugin.min.js +6 -6
- package/dist/vite-plugin.min.js.map +3 -3
- package/package.json +2 -2
- package/src/babel-plugin.js +898 -105
- package/src/file-router.js +104 -2
- package/src/vite-plugin.js +60 -3
package/src/file-router.js
CHANGED
|
@@ -206,6 +206,20 @@ export function extractPageConfig(source) {
|
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Detect which named exports a page module declares. Functions (loader,
|
|
211
|
+
* getStaticPaths) cannot live in `export const page` (JSON-only), so the codegen
|
|
212
|
+
* imports them as live bindings instead. \b anchors avoid false positives like
|
|
213
|
+
* `loaderState` or `getStaticPathsHelper`.
|
|
214
|
+
*/
|
|
215
|
+
export function detectPageExports(source) {
|
|
216
|
+
return {
|
|
217
|
+
hasLoader: /export\s+(?:async\s+)?(?:const|let|var|function)\s+loader\b/.test(source),
|
|
218
|
+
hasGetStaticPaths: /export\s+(?:async\s+)?(?:const|let|var|function)\s+getStaticPaths\b/.test(source),
|
|
219
|
+
hasPageConfig: /export\s+const\s+page\b/.test(source),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
209
223
|
/**
|
|
210
224
|
* Generate the virtual routes module source code.
|
|
211
225
|
* This is what gets imported as 'virtual:what-routes'.
|
|
@@ -231,11 +245,13 @@ export function generateRoutesModule(pagesDir, rootDir) {
|
|
|
231
245
|
const relPath = toImportPath(page.filePath, rootDir);
|
|
232
246
|
imports.push(`import ${varName} from '${relPath}';`);
|
|
233
247
|
|
|
234
|
-
// Read file to extract page config
|
|
248
|
+
// Read file to extract page config + detect loader/getStaticPaths exports
|
|
235
249
|
let pageConfig = { mode: 'client' };
|
|
250
|
+
let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
|
|
236
251
|
try {
|
|
237
252
|
const source = fs.readFileSync(page.filePath, 'utf-8');
|
|
238
253
|
pageConfig = extractPageConfig(source);
|
|
254
|
+
detected = detectPageExports(source);
|
|
239
255
|
} catch {}
|
|
240
256
|
|
|
241
257
|
// Find matching layout (closest parent)
|
|
@@ -246,6 +262,7 @@ export function generateRoutesModule(pagesDir, rootDir) {
|
|
|
246
262
|
component: varName,
|
|
247
263
|
mode: pageConfig.mode || 'client',
|
|
248
264
|
layout: layoutVar || null,
|
|
265
|
+
hasLoader: detected.hasLoader,
|
|
249
266
|
};
|
|
250
267
|
|
|
251
268
|
routeEntries.push(entry);
|
|
@@ -272,7 +289,7 @@ export function generateRoutesModule(pagesDir, rootDir) {
|
|
|
272
289
|
'',
|
|
273
290
|
'export const routes = [',
|
|
274
291
|
...routeEntries.map(r =>
|
|
275
|
-
` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ''} },`
|
|
292
|
+
` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'${r.layout ? `, layout: ${r.layout}` : ''}${r.hasLoader ? ', hasLoader: true' : ''} },`
|
|
276
293
|
),
|
|
277
294
|
'];',
|
|
278
295
|
'',
|
|
@@ -293,6 +310,91 @@ export function generateRoutesModule(pagesDir, rootDir) {
|
|
|
293
310
|
return lines.join('\n');
|
|
294
311
|
}
|
|
295
312
|
|
|
313
|
+
/**
|
|
314
|
+
* Generate the SERVER routes module ('virtual:what-routes/server'). Same routes
|
|
315
|
+
* as the client module PLUS live bindings for loader / getStaticPaths / page so
|
|
316
|
+
* the deploy adapter can run them. The client module (above) deliberately omits
|
|
317
|
+
* these so server loaders are never bundled into the browser.
|
|
318
|
+
*/
|
|
319
|
+
export function generateServerRoutesModule(pagesDir, rootDir) {
|
|
320
|
+
const { pages, layouts, apiRoutes } = scanPages(pagesDir);
|
|
321
|
+
|
|
322
|
+
const imports = [];
|
|
323
|
+
const routeEntries = [];
|
|
324
|
+
|
|
325
|
+
const layoutMap = new Map();
|
|
326
|
+
layouts.forEach((layout, i) => {
|
|
327
|
+
const varName = `_layout${i}`;
|
|
328
|
+
imports.push(`import ${varName} from '${toImportPath(layout.filePath, rootDir)}';`);
|
|
329
|
+
layoutMap.set(layout.urlPrefix, varName);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
pages.forEach((page, i) => {
|
|
333
|
+
const def = `_page${i}`;
|
|
334
|
+
const ns = `_page${i}_ns`;
|
|
335
|
+
const relPath = toImportPath(page.filePath, rootDir);
|
|
336
|
+
|
|
337
|
+
let pageConfig = { mode: 'client' };
|
|
338
|
+
let detected = { hasLoader: false, hasGetStaticPaths: false, hasPageConfig: false };
|
|
339
|
+
try {
|
|
340
|
+
const source = fs.readFileSync(page.filePath, 'utf-8');
|
|
341
|
+
pageConfig = extractPageConfig(source);
|
|
342
|
+
detected = detectPageExports(source);
|
|
343
|
+
} catch {}
|
|
344
|
+
|
|
345
|
+
const needsNs = detected.hasLoader || detected.hasGetStaticPaths || detected.hasPageConfig;
|
|
346
|
+
if (needsNs) {
|
|
347
|
+
imports.push(`import ${def}, * as ${ns} from '${relPath}';`);
|
|
348
|
+
} else {
|
|
349
|
+
imports.push(`import ${def} from '${relPath}';`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
routeEntries.push({
|
|
353
|
+
path: page.routePath,
|
|
354
|
+
component: def,
|
|
355
|
+
ns,
|
|
356
|
+
mode: pageConfig.mode || 'client',
|
|
357
|
+
layout: findLayout(page.routePath, layoutMap) || null,
|
|
358
|
+
...detected,
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
const apiEntries = [];
|
|
363
|
+
apiRoutes.forEach((route, i) => {
|
|
364
|
+
const varName = `_api${i}`;
|
|
365
|
+
imports.push(`import * as ${varName} from '${toImportPath(route.filePath, rootDir)}';`);
|
|
366
|
+
apiEntries.push({ path: route.routePath, handlers: varName });
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const routeLine = (r) =>
|
|
370
|
+
` { path: '${r.path}', component: ${r.component}, mode: '${r.mode}'` +
|
|
371
|
+
`${r.layout ? `, layout: ${r.layout}` : ''}` +
|
|
372
|
+
`${r.hasLoader ? `, loader: ${r.ns}.loader` : ''}` +
|
|
373
|
+
`${r.hasGetStaticPaths ? `, getStaticPaths: ${r.ns}.getStaticPaths` : ''}` +
|
|
374
|
+
`${r.hasPageConfig ? `, page: ${r.ns}.page` : ''} },`;
|
|
375
|
+
|
|
376
|
+
const lines = [
|
|
377
|
+
'// Auto-generated by What Framework file router (server)',
|
|
378
|
+
'// Do not edit — changes will be overwritten',
|
|
379
|
+
'',
|
|
380
|
+
...imports,
|
|
381
|
+
'',
|
|
382
|
+
'export const routes = [',
|
|
383
|
+
...routeEntries.map(routeLine),
|
|
384
|
+
'];',
|
|
385
|
+
'',
|
|
386
|
+
'export const apiRoutes = [',
|
|
387
|
+
...apiEntries.map(r => ` { path: '${r.path}', handlers: ${r.handlers} },`),
|
|
388
|
+
'];',
|
|
389
|
+
'',
|
|
390
|
+
'export const pageModes = {',
|
|
391
|
+
...routeEntries.map(r => ` '${r.path}': '${r.mode}',`),
|
|
392
|
+
'};',
|
|
393
|
+
];
|
|
394
|
+
|
|
395
|
+
return lines.join('\n');
|
|
396
|
+
}
|
|
397
|
+
|
|
296
398
|
/**
|
|
297
399
|
* Convert absolute file path to a root-relative import path.
|
|
298
400
|
*/
|
package/src/vite-plugin.js
CHANGED
|
@@ -36,6 +36,11 @@ export default function whatVitePlugin(options = {}) {
|
|
|
36
36
|
pages = 'src/pages',
|
|
37
37
|
// HMR: enabled by default in dev, disabled in production
|
|
38
38
|
hot = !production,
|
|
39
|
+
// Resolve the `production` exports condition (dist/*.min.js — pre-minified,
|
|
40
|
+
// dev warnings compiled out) during `vite build`. Set to false to build
|
|
41
|
+
// against package sources instead — needed e.g. in a monorepo where
|
|
42
|
+
// workspace-linked dist/ output may be stale or absent. See config() below.
|
|
43
|
+
prodBundles = true,
|
|
39
44
|
} = options;
|
|
40
45
|
|
|
41
46
|
let rootDir = '';
|
|
@@ -105,6 +110,13 @@ export default function whatVitePlugin(options = {}) {
|
|
|
105
110
|
const result = transformSync(code, {
|
|
106
111
|
filename: id,
|
|
107
112
|
sourceMaps,
|
|
113
|
+
// Hermetic transform (SPRINT v0.11 C7): never load the project's
|
|
114
|
+
// babel.config.js/.babelrc. A user's React preset or unrelated
|
|
115
|
+
// plugins corrupting What's JSX output is a debugging nightmare —
|
|
116
|
+
// and scanning the disk for config files on every transform is
|
|
117
|
+
// wasted I/O in dev.
|
|
118
|
+
configFile: false,
|
|
119
|
+
babelrc: false,
|
|
108
120
|
plugins: [
|
|
109
121
|
[whatBabelPlugin, { production }]
|
|
110
122
|
],
|
|
@@ -165,15 +177,60 @@ export default function whatVitePlugin(options = {}) {
|
|
|
165
177
|
},
|
|
166
178
|
|
|
167
179
|
// Configure for development
|
|
168
|
-
config(config, { mode }) {
|
|
180
|
+
config(config, { mode, command }) {
|
|
181
|
+
// SPRINT v0.11 C7: make the `production` exports condition reachable.
|
|
182
|
+
// what-framework/what-core ship pre-minified production bundles behind
|
|
183
|
+
// the `production` condition in their exports maps, but Vite's default
|
|
184
|
+
// resolve conditions never include `production` — so production builds
|
|
185
|
+
// silently shipped the dev source (larger, with dev-only warnings).
|
|
186
|
+
//
|
|
187
|
+
// Guard rationale (documented choice):
|
|
188
|
+
// - Only during `vite build` in production mode — dev always uses src
|
|
189
|
+
// so the dev server, HMR, and devtools see un-minified modules.
|
|
190
|
+
// - Opt-out via `what({ prodBundles: false })` — in a monorepo with
|
|
191
|
+
// workspace-linked packages, dist/ can be stale (or missing before
|
|
192
|
+
// the first `npm run build`), and resolving `production` there would
|
|
193
|
+
// bundle outdated framework code. Apps installing from npm always
|
|
194
|
+
// have dist/ in sync with the published package, so the default is on.
|
|
195
|
+
// - `resolve.conditions` is ADDITIVE in Vite (extra conditions on top
|
|
196
|
+
// of the defaults), so import/browser/default resolution for other
|
|
197
|
+
// packages is unaffected.
|
|
198
|
+
const useProdCondition = command === 'build' && mode === 'production' && prodBundles;
|
|
169
199
|
return {
|
|
200
|
+
...(useProdCondition ? { resolve: { conditions: ['production'] } } : {}),
|
|
170
201
|
esbuild: {
|
|
171
202
|
// Preserve JSX so our babel plugin handles it -- don't let esbuild transform it
|
|
172
203
|
jsx: 'preserve',
|
|
173
204
|
},
|
|
174
205
|
optimizeDeps: {
|
|
175
|
-
//
|
|
176
|
-
|
|
206
|
+
// Exclude framework packages from Vite's dependency pre-bundling.
|
|
207
|
+
//
|
|
208
|
+
// Bug class this prevents — "dual module instance":
|
|
209
|
+
// The compiler emits `import { ... } from 'what-framework/render'`
|
|
210
|
+
// (a subpath resolved to the source file). Meanwhile user code
|
|
211
|
+
// imports `'what-framework'` (the package entry). If Vite
|
|
212
|
+
// pre-bundles `'what-framework'` into an esbuild chunk under
|
|
213
|
+
// node_modules/.vite, those two import paths resolve to two
|
|
214
|
+
// *different* module instances. Module-scoped state — the
|
|
215
|
+
// `componentStack` used by createComponent, effect ownership,
|
|
216
|
+
// the signal subscriber registry — is duplicated, so a signal
|
|
217
|
+
// created in user code never notifies effects created via the
|
|
218
|
+
// compiler-emitted path, and `getCurrentComponent()` returns
|
|
219
|
+
// undefined inside components mounted through compiler output.
|
|
220
|
+
//
|
|
221
|
+
// Why `exclude` is the right knob:
|
|
222
|
+
// `include` would force pre-bundling of the package entry, which
|
|
223
|
+
// does not resolve the subpath import the compiler emits — so the
|
|
224
|
+
// split persists. Using `exclude` tells Vite to skip the optimizer
|
|
225
|
+
// for these packages and serve them via the normal module graph,
|
|
226
|
+
// where both the package entry and the `/render` subpath share
|
|
227
|
+
// a single ESM module record.
|
|
228
|
+
//
|
|
229
|
+
// Regression symptom if this is removed:
|
|
230
|
+
// Components mount but lifecycle hooks (onMount, onCleanup) and
|
|
231
|
+
// shared store state silently no-op; effects don't re-run on
|
|
232
|
+
// signal writes from user code; SSR/CSR hydration mismatches.
|
|
233
|
+
exclude: ['what-framework', 'what-core', 'what-compiler', 'what-router'],
|
|
177
234
|
}
|
|
178
235
|
};
|
|
179
236
|
}
|