flamefront 0.1.0-alpha.0 → 0.1.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/README.md +35 -620
- package/package.json +45 -22
- package/src/cli.ts +16 -1
- package/src/entry.ts +49 -0
- package/src/fetch.ts +280 -0
- package/src/fragment-client.ts +127 -87
- package/src/fragment-hydration-client.tsx +81 -0
- package/src/fragment-protocol.ts +3 -3
- package/src/fragment.tsx +656 -0
- package/src/glob.ts +294 -0
- package/src/identifier-prefix.ts +5 -0
- package/src/index.ts +569 -49
- package/src/lifecycle.ts +7 -6
- package/src/octane-client-core.ts +10 -3
- package/src/octane-client.ts +12 -6
- package/src/octane-compiler.d.ts +19 -0
- package/src/octane-default-renderer.tsx +124 -0
- package/src/octane-router-document.ts +19 -2
- package/src/{octane.ts → octane.tsx} +152 -145
- package/src/output.ts +48 -0
- package/src/remix-route-data.ts +28 -20
- package/src/remix-router.ts +10 -4
- package/src/route-data-client.ts +22 -12
- package/src/route-prefetch.ts +19 -12
- package/src/server.ts +122 -24
- package/src/srvx.ts +43 -183
- package/src/typegen.ts +207 -0
- package/src/virtual-remix-routes.d.ts +16 -1
- package/src/vite.ts +364 -27
- package/src/fragment.ts +0 -260
package/src/vite.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs"
|
|
2
2
|
import path from "node:path"
|
|
3
3
|
import { pathToFileURL } from "node:url"
|
|
4
|
+
import { compile as compileOctane } from "octane/compiler"
|
|
5
|
+
import type { Features, HastPluginList, MdastPluginList } from "satteri"
|
|
6
|
+
import vitePluginSatteri, { type MdxOptions } from "vite-plugin-satteri"
|
|
4
7
|
import type {
|
|
5
8
|
AppDefinition,
|
|
6
9
|
GeneratedHydration,
|
|
@@ -9,23 +12,56 @@ import type {
|
|
|
9
12
|
RouteConfig,
|
|
10
13
|
RouteDefinition,
|
|
11
14
|
} from "./index.ts"
|
|
12
|
-
import { generate, parse } from "./babel.ts"
|
|
15
|
+
import { generate, parse, traverse, type Babel } from "./babel.ts"
|
|
16
|
+
import { expandGlob, globDirectory, setGlobRoot } from "./glob.ts"
|
|
13
17
|
import { removeExports } from "./remove-exports.ts"
|
|
18
|
+
import { writeRouteImportMap } from "./typegen.ts"
|
|
19
|
+
import {
|
|
20
|
+
resolveFlamefrontOutput,
|
|
21
|
+
type FlamefrontOutputOptions,
|
|
22
|
+
type ResolvedFlamefrontOutput,
|
|
23
|
+
} from "./output.ts"
|
|
24
|
+
|
|
25
|
+
export type {
|
|
26
|
+
FlamefrontAdapter,
|
|
27
|
+
FlamefrontOutputOptions,
|
|
28
|
+
FlamefrontTarget,
|
|
29
|
+
ResolvedFlamefrontOutput,
|
|
30
|
+
} from "./output.ts"
|
|
14
31
|
|
|
15
32
|
export const remixRoutesId = "virtual:flamefront/remix-routes"
|
|
16
33
|
const resolvedRemixRoutesId = `\0${remixRoutesId}`
|
|
17
34
|
|
|
18
35
|
export const serverRoutesId = "virtual:flamefront/server-routes"
|
|
19
36
|
const resolvedServerRoutesId = `\0${serverRoutesId}`
|
|
37
|
+
|
|
38
|
+
export const serverEntryId = "virtual:flamefront/server-entry"
|
|
39
|
+
const resolvedServerEntryId = `\0${serverEntryId}`
|
|
20
40
|
const hydrationRouteId = "/@flamefront/hydration-route.tsrx"
|
|
21
41
|
const resolvedHydrationRoutePrefix = `${hydrationRouteId}?`
|
|
42
|
+
const markdownRouteId = "/@flamefront/markdown-route.tsrx"
|
|
43
|
+
const resolvedMarkdownRoutePrefix = `${markdownRouteId}?`
|
|
22
44
|
const SERVER_ONLY_ROUTE_EXPORTS = ["loader"] as const
|
|
23
45
|
const serverFilePattern = /\.server(?:\.[cm]?[jt]sx?|\.tsrx)$/
|
|
24
46
|
const serverDirectoryPattern = /\/\.server\//
|
|
25
47
|
|
|
26
|
-
export interface FlamefrontOptions {
|
|
48
|
+
export interface FlamefrontOptions extends FlamefrontOutputOptions {
|
|
27
49
|
/** Project-root route manifest module. */
|
|
28
50
|
readonly routes?: string
|
|
51
|
+
/** Built-in Markdown and MDX compiler configuration. */
|
|
52
|
+
readonly markdown?: false | MarkdownOptions
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface MarkdownOptions {
|
|
56
|
+
/** Parser feature toggles. GFM and frontmatter are enabled by default. */
|
|
57
|
+
readonly features?: Features
|
|
58
|
+
/** MDAST plugins shared by Markdown and MDX entries. */
|
|
59
|
+
readonly mdastPlugins?: MdastPluginList
|
|
60
|
+
/** HAST plugins shared by Markdown and MDX entries. */
|
|
61
|
+
readonly hastPlugins?: HastPluginList
|
|
62
|
+
/** MDX compiler options; its JSX import source is always `octane`. */
|
|
63
|
+
readonly mdx?:
|
|
64
|
+
boolean | Omit<MdxOptions, "jsxImportSource" | "jsx" | "jsxRuntime">
|
|
29
65
|
}
|
|
30
66
|
|
|
31
67
|
function quote(value: string): string {
|
|
@@ -61,10 +97,24 @@ function hydrationComponentId(
|
|
|
61
97
|
return `${hydrationRouteId}?${parameters}`
|
|
62
98
|
}
|
|
63
99
|
|
|
100
|
+
function markdownComponentId(entry: string): string {
|
|
101
|
+
const parameters = new URLSearchParams({
|
|
102
|
+
entry,
|
|
103
|
+
"flamefront-markdown": "1",
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
return `${markdownRouteId}?${parameters}`
|
|
107
|
+
}
|
|
108
|
+
|
|
64
109
|
function browserRouteModuleId(routeDefinition: RouteDefinition): string {
|
|
110
|
+
const componentEntry =
|
|
111
|
+
routeDefinition.content === "markdown"
|
|
112
|
+
? markdownComponentId(routeDefinition.entry)
|
|
113
|
+
: routeDefinition.entry
|
|
114
|
+
|
|
65
115
|
return generatesHydrationBoundary(routeDefinition)
|
|
66
|
-
? hydrationComponentId(
|
|
67
|
-
:
|
|
116
|
+
? hydrationComponentId(componentEntry, routeDefinition.hydration)
|
|
117
|
+
: componentEntry
|
|
68
118
|
}
|
|
69
119
|
|
|
70
120
|
function generatedRouteId(kind: "layout" | "route", location: string): string {
|
|
@@ -99,7 +149,7 @@ function generatedRouteMetadata(
|
|
|
99
149
|
parent,
|
|
100
150
|
path: config.path,
|
|
101
151
|
render: config.render,
|
|
102
|
-
navigation: config.render === "
|
|
152
|
+
navigation: config.render === "client" ? "router" : "fragment",
|
|
103
153
|
hydration: config.hydration,
|
|
104
154
|
}
|
|
105
155
|
}
|
|
@@ -117,7 +167,7 @@ function generateRoutePreloaders(routeTree: readonly RouteConfig[]): string {
|
|
|
117
167
|
continue
|
|
118
168
|
}
|
|
119
169
|
|
|
120
|
-
if (config.render
|
|
170
|
+
if (config.render !== "client") {
|
|
121
171
|
continue
|
|
122
172
|
}
|
|
123
173
|
|
|
@@ -155,33 +205,35 @@ function lazyRoute(
|
|
|
155
205
|
): string {
|
|
156
206
|
const { entry } = routeDefinition
|
|
157
207
|
const browserLoader =
|
|
158
|
-
routeDefinition.render === "
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
208
|
+
routeDefinition.render === "client" ? "loadRouteData" : "loadRouteFragment"
|
|
209
|
+
const browserLoaderExpression =
|
|
210
|
+
routeDefinition.render === "client"
|
|
211
|
+
? `(args) => ${browserLoader}(args, ${JSON.stringify(routing)})`
|
|
212
|
+
: `(args) => ${browserLoader}(args, ${JSON.stringify(routing)}, ${quote(routeDefinition.render)})`
|
|
162
213
|
|
|
163
|
-
if (routeDefinition.render
|
|
214
|
+
if (routeDefinition.render !== "client") {
|
|
164
215
|
const componentId = browserRouteModuleId(routeDefinition)
|
|
165
216
|
|
|
166
|
-
return `async () => { if (import.meta.env.SSR) { const [routeModule, componentModule] = await Promise.all([import(${quote(entry)}), import(${quote(componentId)})]); return { Component: createRouteBoundary(componentModule.default, ${JSON.stringify(metadata)}), loader: routeModule.loader }; } const componentModule = await import(${quote(componentId)}); return { Component:
|
|
217
|
+
return `async () => { if (import.meta.env.SSR) { const [routeModule, componentModule] = await Promise.all([import(${quote(entry)}), import(${quote(componentId)})]); return { Component: createRouteBoundary(componentModule.default, ${JSON.stringify(metadata)}), loader: routeModule.loader }; } const componentModule = await import(${quote(componentId)}); return { Component: createRouteFragmentRoute({ metadata: ${JSON.stringify(metadata)}, routing: ${JSON.stringify(routing)}, policy: ${quote(routeDefinition.render)}, fallbackComponent: componentModule.default }), loader: ${browserLoaderExpression} }; }`
|
|
167
218
|
}
|
|
168
219
|
|
|
169
220
|
if (routeDefinition.render === "client") {
|
|
170
|
-
|
|
171
|
-
|
|
221
|
+
if (routeDefinition.content === "markdown") {
|
|
222
|
+
const componentId = browserRouteModuleId(routeDefinition)
|
|
172
223
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
224
|
+
return `async () => { if (import.meta.env.SSR) return {}; const componentModule = await import(${quote(componentId)}); return { Component: createRouteBoundary(componentModule.default, ${JSON.stringify(metadata)}), loader: ${browserLoaderExpression} }; }`
|
|
225
|
+
}
|
|
176
226
|
|
|
177
|
-
|
|
227
|
+
return `async () => { if (import.meta.env.SSR) return {}; const routeModule = await import(${quote(entry)}); return { Component: createRouteBoundary(routeModule.default, ${JSON.stringify(metadata)}), loader: ${browserLoaderExpression} }; }`
|
|
228
|
+
}
|
|
178
229
|
|
|
179
|
-
|
|
230
|
+
throw new Error(`Unsupported route render mode ${routeDefinition.render}.`)
|
|
180
231
|
}
|
|
181
232
|
|
|
182
233
|
function collectRouteMetadata(
|
|
183
234
|
routeTree: readonly RouteConfig[],
|
|
184
235
|
shell: string,
|
|
236
|
+
shellHydration: AppDefinition["shellHydration"],
|
|
185
237
|
): readonly GeneratedRouteMetadata[] {
|
|
186
238
|
const rootId = "flamefront:shell:root"
|
|
187
239
|
const metadata: GeneratedRouteMetadata[] = [
|
|
@@ -191,6 +243,7 @@ function collectRouteMetadata(
|
|
|
191
243
|
kind: "shell",
|
|
192
244
|
entry: shell,
|
|
193
245
|
navigation: "router",
|
|
246
|
+
hydration: shellHydration,
|
|
194
247
|
},
|
|
195
248
|
]
|
|
196
249
|
|
|
@@ -243,12 +296,19 @@ function generateConfigs(
|
|
|
243
296
|
}
|
|
244
297
|
|
|
245
298
|
export function generateRemixRoutes(
|
|
246
|
-
app: Pick<
|
|
299
|
+
app: Pick<
|
|
300
|
+
AppDefinition,
|
|
301
|
+
"shell" | "shellHydration" | "routeTree" | "routing"
|
|
302
|
+
>,
|
|
247
303
|
): string {
|
|
248
304
|
const rootId = "flamefront:shell:root"
|
|
249
|
-
const routeMetadata = collectRouteMetadata(
|
|
305
|
+
const routeMetadata = collectRouteMetadata(
|
|
306
|
+
app.routeTree,
|
|
307
|
+
app.shell,
|
|
308
|
+
app.shellHydration,
|
|
309
|
+
)
|
|
250
310
|
|
|
251
|
-
return `// Generated by Flamefront.\nimport Shell from ${quote(app.shell)};\nimport { RouterDocument } from 'flamefront/octane/router-document';\nimport { createRouteBoundary,
|
|
311
|
+
return `// Generated by Flamefront.\nimport Shell from ${quote(app.shell)};\nimport { RouterDocument } from 'flamefront/octane/router-document';\nimport { createRouteBoundary, createRouteFragmentRoute } from 'flamefront/fragment';\nimport { loadRouteData, loadRouteFragment } from 'flamefront/remix-router/data';\n\nexport { RouterDocument };\nexport const routing = ${JSON.stringify(app.routing)};\nexport const routeMetadata = ${JSON.stringify(routeMetadata)};\n\nexport const routes = [\n\t{\n\t\tid: ${quote(rootId)},\n\t\tComponent: createRouteBoundary(Shell, ${JSON.stringify(routeMetadata[0])}),\n\t\thandle: { flamefront: ${JSON.stringify(routeMetadata[0])} },\n\t\tchildren: [\n${generateConfigs(app.routeTree, app.routing, 3)}\n\t\t],\n\t},\n];\n\n${generateRoutePreloaders(app.routeTree)}`
|
|
252
312
|
}
|
|
253
313
|
|
|
254
314
|
/** Generate the server-only route-module importer used by loader endpoints. */
|
|
@@ -263,6 +323,15 @@ export function generateServerRoutes(
|
|
|
263
323
|
return `// Generated by Flamefront.\nconst routeModules = {\n${imports}\n};\n\nexport async function importRoute(entry) {\n\tconst importModule = routeModules[entry];\n\tif (!importModule) throw new Error(\`No Vite route module was generated for \${entry}.\`);\n\treturn importModule();\n}\n`
|
|
264
324
|
}
|
|
265
325
|
|
|
326
|
+
/** Generate the server-entry import selected by `flamefront({...})`. */
|
|
327
|
+
export function generateServerEntry(output: ResolvedFlamefrontOutput): string {
|
|
328
|
+
if (output.adapter === "srvx") {
|
|
329
|
+
return `// Generated by Flamefront.\nexport { createSrvxServerEntry as createServerEntry } from "flamefront/srvx";\n`
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return `// Generated by Flamefront.\nexport { createFetchServerEntry as createServerEntry } from "flamefront/fetch";\n`
|
|
333
|
+
}
|
|
334
|
+
|
|
266
335
|
function hydrationStrategy(hydration: GeneratedHydration | "none"): {
|
|
267
336
|
readonly importName: string
|
|
268
337
|
readonly expression: string
|
|
@@ -297,6 +366,11 @@ function hydrationStrategy(hydration: GeneratedHydration | "none"): {
|
|
|
297
366
|
}
|
|
298
367
|
}
|
|
299
368
|
|
|
369
|
+
/** Generate the component adapter used when a route entry exports HTML. */
|
|
370
|
+
export function generateMarkdownRoute(entry: string): string {
|
|
371
|
+
return `import html from ${quote(entry)};\n\nexport default function MarkdownRoute() @{\n\t<div dangerouslySetInnerHTML={{ __html: html }} />\n}\n`
|
|
372
|
+
}
|
|
373
|
+
|
|
300
374
|
export function generateHydrationRoute(
|
|
301
375
|
entry: string,
|
|
302
376
|
hydration: GeneratedHydration | "none",
|
|
@@ -316,7 +390,9 @@ interface ResolveOptions extends TransformOptions {
|
|
|
316
390
|
}
|
|
317
391
|
|
|
318
392
|
interface PluginContext {
|
|
319
|
-
readonly environment?: {
|
|
393
|
+
readonly environment?: {
|
|
394
|
+
readonly config?: { readonly command?: string; readonly consumer?: string }
|
|
395
|
+
}
|
|
320
396
|
resolve(
|
|
321
397
|
id: string,
|
|
322
398
|
importer: string | undefined,
|
|
@@ -346,6 +422,139 @@ function cleanModuleId(id: string): string {
|
|
|
346
422
|
return id.split("?", 1)[0].replaceAll("\\", "/")
|
|
347
423
|
}
|
|
348
424
|
|
|
425
|
+
interface ManifestGlobTransform {
|
|
426
|
+
readonly code: string
|
|
427
|
+
readonly map: null
|
|
428
|
+
readonly directories: readonly string[]
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
interface TextReplacement {
|
|
432
|
+
readonly end: number
|
|
433
|
+
readonly start: number
|
|
434
|
+
readonly text: string
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function staticStringArgument(
|
|
438
|
+
argument: Babel.Node | undefined,
|
|
439
|
+
): string | undefined {
|
|
440
|
+
if (!argument) {
|
|
441
|
+
return undefined
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (argument.type === "StringLiteral") {
|
|
445
|
+
return argument.value
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (
|
|
449
|
+
argument.type === "TemplateLiteral" &&
|
|
450
|
+
argument.expressions.length === 0
|
|
451
|
+
) {
|
|
452
|
+
return argument.quasis[0]?.value.cooked ?? ""
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return undefined
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function transformManifestGlobs(
|
|
459
|
+
source: string,
|
|
460
|
+
id: string,
|
|
461
|
+
root: string,
|
|
462
|
+
): ManifestGlobTransform | null {
|
|
463
|
+
const ast = parse(source, {
|
|
464
|
+
sourceFilename: id,
|
|
465
|
+
sourceType: "module",
|
|
466
|
+
plugins: ["typescript", "jsx"],
|
|
467
|
+
})
|
|
468
|
+
const globBindings = new Set<string>()
|
|
469
|
+
|
|
470
|
+
traverse(ast, {
|
|
471
|
+
ImportDeclaration(path) {
|
|
472
|
+
if (path.node.source.value !== "flamefront") {
|
|
473
|
+
return
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
for (const specifier of path.node.specifiers) {
|
|
477
|
+
if (specifier.type !== "ImportSpecifier") {
|
|
478
|
+
continue
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const imported = specifier.imported
|
|
482
|
+
const importedName =
|
|
483
|
+
imported.type === "Identifier" ? imported.name : imported.value
|
|
484
|
+
|
|
485
|
+
if (importedName === "glob") {
|
|
486
|
+
globBindings.add(specifier.local.name)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
if (globBindings.size === 0) {
|
|
493
|
+
return null
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const replacements: TextReplacement[] = []
|
|
497
|
+
const directories = new Set<string>()
|
|
498
|
+
|
|
499
|
+
traverse(ast, {
|
|
500
|
+
CallExpression(path) {
|
|
501
|
+
const callee = path.node.callee
|
|
502
|
+
|
|
503
|
+
if (callee.type !== "Identifier" || !globBindings.has(callee.name)) {
|
|
504
|
+
return
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
const argument = path.node.arguments[0]
|
|
508
|
+
const pattern = staticStringArgument(argument)
|
|
509
|
+
|
|
510
|
+
if (pattern === undefined) {
|
|
511
|
+
throw new TypeError(
|
|
512
|
+
`flamefront glob() in ${id} requires a string-literal pattern.`,
|
|
513
|
+
)
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (
|
|
517
|
+
typeof argument?.start !== "number" ||
|
|
518
|
+
typeof argument.end !== "number"
|
|
519
|
+
) {
|
|
520
|
+
throw new TypeError(`flamefront glob() in ${id} has no source range.`)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const files = expandGlob(root, pattern)
|
|
524
|
+
|
|
525
|
+
replacements.push({
|
|
526
|
+
start: argument.start,
|
|
527
|
+
end: argument.end,
|
|
528
|
+
text: JSON.stringify(files),
|
|
529
|
+
})
|
|
530
|
+
directories.add(globDirectory(root, pattern))
|
|
531
|
+
},
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
if (replacements.length === 0) {
|
|
535
|
+
return null
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const code = [...replacements]
|
|
539
|
+
.sort((left, right) => right.start - left.start)
|
|
540
|
+
.reduce(
|
|
541
|
+
(result, replacement) =>
|
|
542
|
+
`${result.slice(0, replacement.start)}${replacement.text}${result.slice(replacement.end)}`,
|
|
543
|
+
source,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
return { code, map: null, directories: [...directories] }
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function isPathWithinDirectory(directory: string, candidate: string): boolean {
|
|
550
|
+
const relative = path.relative(directory, candidate)
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
relative === "" ||
|
|
554
|
+
(!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
555
|
+
)
|
|
556
|
+
}
|
|
557
|
+
|
|
349
558
|
function isServerEnvironment(
|
|
350
559
|
context: PluginContext,
|
|
351
560
|
options?: TransformOptions,
|
|
@@ -474,10 +683,12 @@ export function removeServerRouteExports(source: string, id = "route.js") {
|
|
|
474
683
|
}
|
|
475
684
|
|
|
476
685
|
export function flamefront(options: FlamefrontOptions = {}) {
|
|
686
|
+
const output = resolveFlamefrontOutput(options)
|
|
477
687
|
let root = process.cwd()
|
|
478
688
|
let serverBuild = false
|
|
479
689
|
let appPromise: Promise<AppDefinition> | undefined
|
|
480
690
|
let manifestRevision = 0
|
|
691
|
+
let manifestGlobDirectories: readonly string[] = []
|
|
481
692
|
const manifestId = options.routes ?? "/src/app.ts"
|
|
482
693
|
const manifestPath = () =>
|
|
483
694
|
path.resolve(
|
|
@@ -488,6 +699,7 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
488
699
|
const manifestUrl = new URL(pathToFileURL(manifestPath()))
|
|
489
700
|
|
|
490
701
|
manifestUrl.searchParams.set("flamefront", String(manifestRevision))
|
|
702
|
+
setGlobRoot(root)
|
|
491
703
|
appPromise ??= import(manifestUrl.href).then((module) => {
|
|
492
704
|
const app = module.app ?? module.default
|
|
493
705
|
|
|
@@ -507,6 +719,8 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
507
719
|
new Set(
|
|
508
720
|
(await loadRoutes()).map((route) => resolveRouteEntry(root, route.entry)),
|
|
509
721
|
)
|
|
722
|
+
const generateTypes = async () =>
|
|
723
|
+
writeRouteImportMap(await loadApp(), { root })
|
|
510
724
|
const configureRoot = (config: { readonly root: string }) => {
|
|
511
725
|
root = config.root
|
|
512
726
|
}
|
|
@@ -515,7 +729,22 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
515
729
|
name: "flamefront:framework-modules",
|
|
516
730
|
enforce: "pre" as const,
|
|
517
731
|
configResolved: configureRoot,
|
|
518
|
-
|
|
732
|
+
async buildStart() {
|
|
733
|
+
await generateTypes()
|
|
734
|
+
},
|
|
735
|
+
transform(source: string, id: string) {
|
|
736
|
+
if (cleanModuleId(id) !== manifestPath()) {
|
|
737
|
+
return null
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
const transformed = transformManifestGlobs(source, id, root)
|
|
741
|
+
|
|
742
|
+
manifestGlobDirectories = transformed?.directories ?? []
|
|
743
|
+
return transformed
|
|
744
|
+
? { code: transformed.code, map: transformed.map }
|
|
745
|
+
: null
|
|
746
|
+
},
|
|
747
|
+
async handleHotUpdate(context: {
|
|
519
748
|
file: string
|
|
520
749
|
server: {
|
|
521
750
|
moduleGraph: {
|
|
@@ -524,13 +753,39 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
524
753
|
}
|
|
525
754
|
}
|
|
526
755
|
}) {
|
|
527
|
-
|
|
756
|
+
const manifestChanged = context.file === manifestPath()
|
|
757
|
+
const globChanged = manifestGlobDirectories.some((directory) =>
|
|
758
|
+
isPathWithinDirectory(directory, context.file),
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
if (!manifestChanged && !globChanged) {
|
|
528
762
|
return
|
|
529
763
|
}
|
|
530
764
|
|
|
531
765
|
manifestRevision += 1
|
|
532
766
|
appPromise = undefined
|
|
533
|
-
|
|
767
|
+
try {
|
|
768
|
+
await generateTypes()
|
|
769
|
+
} catch {
|
|
770
|
+
// Keep the previous declarations while an edited manifest is invalid.
|
|
771
|
+
// Vite will report the manifest error when the virtual route modules
|
|
772
|
+
// are requested, but a stale type file must not block editing.
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
if (globChanged) {
|
|
776
|
+
const manifestModule =
|
|
777
|
+
context.server.moduleGraph.getModuleById(manifestPath())
|
|
778
|
+
|
|
779
|
+
if (manifestModule) {
|
|
780
|
+
context.server.moduleGraph.invalidateModule(manifestModule)
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
for (const moduleId of [
|
|
785
|
+
resolvedRemixRoutesId,
|
|
786
|
+
resolvedServerRoutesId,
|
|
787
|
+
resolvedServerEntryId,
|
|
788
|
+
]) {
|
|
534
789
|
const generatedModule =
|
|
535
790
|
context.server.moduleGraph.getModuleById(moduleId)
|
|
536
791
|
|
|
@@ -553,6 +808,14 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
553
808
|
return resolvedServerRoutesId
|
|
554
809
|
}
|
|
555
810
|
|
|
811
|
+
if (id === serverEntryId) {
|
|
812
|
+
return resolvedServerEntryId
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
if (id === "flamefront/entry") {
|
|
816
|
+
return resolvedServerEntryId
|
|
817
|
+
}
|
|
818
|
+
|
|
556
819
|
if (
|
|
557
820
|
id === "./hydration-route.tsrx?octane-hydrate=0" &&
|
|
558
821
|
importer?.startsWith(resolvedHydrationRoutePrefix)
|
|
@@ -569,6 +832,10 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
569
832
|
return id
|
|
570
833
|
}
|
|
571
834
|
|
|
835
|
+
if (id.startsWith(resolvedMarkdownRoutePrefix)) {
|
|
836
|
+
return id
|
|
837
|
+
}
|
|
838
|
+
|
|
572
839
|
if (
|
|
573
840
|
resolveOptions.scan ||
|
|
574
841
|
isServerEnvironment(this, resolveOptions) ||
|
|
@@ -619,6 +886,10 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
619
886
|
return generateServerRoutes(await loadApp())
|
|
620
887
|
}
|
|
621
888
|
|
|
889
|
+
if (id === resolvedServerEntryId) {
|
|
890
|
+
return generateServerEntry(output)
|
|
891
|
+
}
|
|
892
|
+
|
|
622
893
|
if (id.startsWith(resolvedHydrationRoutePrefix)) {
|
|
623
894
|
const parameters = new URLSearchParams(id.slice(id.indexOf("?") + 1))
|
|
624
895
|
const entry = parameters.get("entry")
|
|
@@ -636,6 +907,17 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
636
907
|
)
|
|
637
908
|
}
|
|
638
909
|
|
|
910
|
+
if (id.startsWith(resolvedMarkdownRoutePrefix)) {
|
|
911
|
+
const parameters = new URLSearchParams(id.slice(id.indexOf("?") + 1))
|
|
912
|
+
const entry = parameters.get("entry")
|
|
913
|
+
|
|
914
|
+
if (!entry) {
|
|
915
|
+
throw new TypeError("Flamefront Markdown route is missing its entry.")
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
return generateMarkdownRoute(entry)
|
|
919
|
+
}
|
|
920
|
+
|
|
639
921
|
return null
|
|
640
922
|
},
|
|
641
923
|
}
|
|
@@ -689,5 +971,60 @@ export function flamefront(options: FlamefrontOptions = {}) {
|
|
|
689
971
|
},
|
|
690
972
|
}
|
|
691
973
|
|
|
692
|
-
|
|
974
|
+
const markdownPlugin =
|
|
975
|
+
options.markdown === false
|
|
976
|
+
? undefined
|
|
977
|
+
: vitePluginSatteri({
|
|
978
|
+
...(options.markdown ?? {}),
|
|
979
|
+
features: {
|
|
980
|
+
gfm: true,
|
|
981
|
+
frontmatter: true,
|
|
982
|
+
...options.markdown?.features,
|
|
983
|
+
},
|
|
984
|
+
mdx:
|
|
985
|
+
options.markdown?.mdx === false
|
|
986
|
+
? false
|
|
987
|
+
: {
|
|
988
|
+
...(typeof options.markdown?.mdx === "object"
|
|
989
|
+
? options.markdown.mdx
|
|
990
|
+
: {}),
|
|
991
|
+
jsx: true,
|
|
992
|
+
jsxImportSource: "octane",
|
|
993
|
+
jsxRuntime: "automatic",
|
|
994
|
+
},
|
|
995
|
+
})
|
|
996
|
+
|
|
997
|
+
const mdxCompilerPlugin =
|
|
998
|
+
options.markdown === false || options.markdown?.mdx === false
|
|
999
|
+
? undefined
|
|
1000
|
+
: {
|
|
1001
|
+
name: "flamefront:markdown-mdx",
|
|
1002
|
+
async transform(
|
|
1003
|
+
this: PluginContext,
|
|
1004
|
+
source: string,
|
|
1005
|
+
id: string,
|
|
1006
|
+
transformOptions?: TransformOptions,
|
|
1007
|
+
): Promise<{ code: string; map: object | null } | null> {
|
|
1008
|
+
if (!cleanModuleId(id).endsWith(".mdx")) {
|
|
1009
|
+
return null
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const server = isServerEnvironment(this, transformOptions)
|
|
1013
|
+
const command = this.environment?.config?.command
|
|
1014
|
+
const compiled = compileOctane(source, id, {
|
|
1015
|
+
dev: command === "serve",
|
|
1016
|
+
hmr: command === "serve" && !server ? "vite" : false,
|
|
1017
|
+
mode: server ? "server" : "client",
|
|
1018
|
+
})
|
|
1019
|
+
|
|
1020
|
+
return { code: compiled.code, map: compiled.map }
|
|
1021
|
+
},
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return [
|
|
1025
|
+
frameworkModulesPlugin,
|
|
1026
|
+
routeModulePlugin,
|
|
1027
|
+
markdownPlugin,
|
|
1028
|
+
mdxCompilerPlugin,
|
|
1029
|
+
] as const
|
|
693
1030
|
}
|