@bakery-framework/plugin-vue 1.0.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/src/handler.ts ADDED
@@ -0,0 +1,538 @@
1
+ import { LRUCache } from '@bakery-framework/core/cache/lru'
2
+ import { Bakery, hostKey } from '@bakery-framework/core/core/bakery'
3
+ import type { Handler } from '@bakery-framework/core/handlers'
4
+ import {
5
+ beginPageRoute,
6
+ DynamicErrorHandler,
7
+ DynamicHandler,
8
+ } from '@bakery-framework/core/handlers'
9
+ import { Logger } from '@bakery-framework/core/logger'
10
+ import {
11
+ fs,
12
+ JsonResponseData,
13
+ response,
14
+ toHash,
15
+ } from '@bakery-framework/core/utils'
16
+ import { ETag, injectIfHtml } from '@bakery-framework/core/utils/http'
17
+
18
+ const logger = new Logger('vue')
19
+
20
+ import {
21
+ resolveActionTarget,
22
+ validateActionRequest,
23
+ validateActionTarget,
24
+ } from './actions'
25
+ import { serveVueChunk, VUE_CHUNK_PREFIX } from './chunks'
26
+ import { compileStyleBlock, compileVueFile, parseVue } from './compile'
27
+ import { VUE_HTML_SHELL } from './shell'
28
+ import type { ParsedCacheEntry } from './types'
29
+ import {
30
+ cacheDir,
31
+ collectExportedFunctionNames,
32
+ escapeHtml,
33
+ escapeScriptJson,
34
+ extractServerScripts,
35
+ getServerResponse,
36
+ parsedCache,
37
+ parseVueMeta,
38
+ RX_EXPORT_BRACE,
39
+ RX_EXPORT_HANGING,
40
+ rewriteVueImports,
41
+ VUE_SERVER_DATA_TOKEN,
42
+ } from './utils'
43
+
44
+ function normalizePath(path: string) {
45
+ return path.endsWith('.js') ? path.slice(0, -3) : path
46
+ }
47
+
48
+ const RX_SERVER_DATA_TOKEN = new RegExp(`\\b${VUE_SERVER_DATA_TOKEN}\\b`, 'g')
49
+
50
+ /**
51
+ * Compiled module code with the server-data token still in place. Components
52
+ * with a `<script server>` block get per-request data, so the code cannot be
53
+ * cached on disk with the data baked in — but the expensive compile can be.
54
+ */
55
+ const tokenizedModuleCache = new LRUCache<
56
+ string,
57
+ { lastMod: number; code: string }
58
+ >(500)
59
+
60
+ /**
61
+ * Client-side stub for a server action: calls back into this route over HTTP.
62
+ * Must be POST + JSON to satisfy the CSRF checks in `validateActionRequest`.
63
+ */
64
+ function buildActionStub(fn: string, relPath: string) {
65
+ const query = `?__vue_action=${encodeURIComponent(fn)}&__vue_file=${encodeURIComponent(relPath)}`
66
+ return (
67
+ `const ${fn} = async (...args) => { ` +
68
+ `const res = await fetch(window.location.pathname + '${query}', { ` +
69
+ `method: 'POST', headers: { 'Content-Type': 'application/json' }, ` +
70
+ `body: JSON.stringify({ args }) }); ` +
71
+ `const ct = res.headers.get('content-type') || ''; ` +
72
+ `if (ct.includes('application/json')) { const json = await res.json(); ` +
73
+ `return json.data !== undefined ? json.data : json; } ` +
74
+ `return res; };`
75
+ )
76
+ }
77
+
78
+ export class VueHandler extends DynamicHandler {
79
+ static get config() {
80
+ return {
81
+ ext: ['vue'],
82
+ dir: Bakery.serveRoot,
83
+ }
84
+ }
85
+
86
+ static get cacheDir() {
87
+ return cacheDir
88
+ }
89
+
90
+ static async canHandle(path: string, req: Request) {
91
+ if (path.startsWith(VUE_CHUNK_PREFIX)) return true
92
+ if (path.endsWith('.vue')) return true
93
+ if (path.endsWith('.js')) path = path.slice(0, -3)
94
+ return await super.canHandle(path, req)
95
+ }
96
+
97
+ static handle = sharedHandler
98
+
99
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: SFC parser
100
+ static async parseVueFile(
101
+ id: string,
102
+ diskFile: Bun.BunFile,
103
+ filePath: string,
104
+ lastMod: number,
105
+ ) {
106
+ const cached = parsedCache.get(id)
107
+ if (cached && cached.lastMod === lastMod) return cached
108
+
109
+ const rawText = await diskFile.text()
110
+ const { meta, clean: metaCleaned } = parseVueMeta(rawText)
111
+ const { script: serverScript, clean: withoutServer } =
112
+ extractServerScripts(metaCleaned)
113
+ let cleanContent = withoutServer
114
+
115
+ if (serverScript.trim()) {
116
+ const functionNames = collectExportedFunctionNames(serverScript)
117
+ const dataNames: string[] = []
118
+
119
+ for (const m of serverScript.matchAll(RX_EXPORT_HANGING)) {
120
+ if (!functionNames.includes(m[2])) dataNames.push(m[2])
121
+ }
122
+
123
+ for (const m of serverScript.matchAll(RX_EXPORT_BRACE)) {
124
+ for (const part of m[1].split(',')) {
125
+ const t = part.trim()
126
+ if (!t) continue
127
+ const alias = t.includes(' as ') ? t.split(/\s+as\s+/)[1] : t
128
+ if (!functionNames.includes(alias)) dataNames.push(alias)
129
+ }
130
+ }
131
+
132
+ const scriptInjections: string[] = []
133
+ if (dataNames.length) {
134
+ scriptInjections.push(
135
+ `const { ${dataNames.join(', ')} } = ${VUE_SERVER_DATA_TOKEN};`,
136
+ )
137
+ }
138
+
139
+ if (functionNames.length) {
140
+ const relPath = fs.relative(Bakery.serveRoot, filePath)
141
+ for (const fn of functionNames) {
142
+ scriptInjections.push(buildActionStub(fn, relPath))
143
+ }
144
+ }
145
+
146
+ if (scriptInjections.length) {
147
+ const langMatch = cleanContent.match(
148
+ /<script\s+[^>]*\blang=["']([^"']+)["']/i,
149
+ )
150
+ const langAttr = langMatch ? ` lang="${langMatch[1]}"` : ''
151
+ cleanContent = `<script${langAttr}>\n${scriptInjections.join(
152
+ '\n',
153
+ )}\n</script>\n${cleanContent}`
154
+ }
155
+ }
156
+
157
+ const rxEventAttr =
158
+ /(@[\w.-]+|v-on:[\w.-]+)=("([^"\n]*\n[^"]*)"|'([^'\n]*\n[^']*)')/g
159
+ cleanContent = cleanContent.replace(
160
+ rxEventAttr,
161
+ (match, attr, doubleMatch, innerDouble, innerSingle) => {
162
+ const quote = doubleMatch.startsWith('"') ? '"' : "'"
163
+ const val = innerDouble !== undefined ? innerDouble : innerSingle
164
+ if (!val.includes(';')) {
165
+ return `${attr}=${quote}${val};${quote}`
166
+ }
167
+ return match
168
+ },
169
+ )
170
+
171
+ const { descriptor } = await parseVue({
172
+ content: cleanContent,
173
+ filename: filePath,
174
+ })
175
+ const scopeId = `data-v-${id}`
176
+
177
+ const parsed: ParsedCacheEntry = {
178
+ lastMod,
179
+ serverScript,
180
+ cleanContent,
181
+ scopeId,
182
+ styles: descriptor.styles,
183
+ hasCss: descriptor.styles.length > 0,
184
+ meta,
185
+ }
186
+ parsedCache.set(id, parsed)
187
+ return parsed
188
+ }
189
+
190
+ private static async buildScriptCode(
191
+ id: string,
192
+ routePath: string,
193
+ isRootScript: boolean,
194
+ parsed: ParsedCacheEntry,
195
+ serverDataReplacement?: string,
196
+ ) {
197
+ const { cleanContent, scopeId } = parsed
198
+ const compiled = await compileVueFile({
199
+ content: cleanContent,
200
+ filename: routePath,
201
+ id: scopeId || id,
202
+ isRootScript,
203
+ })
204
+
205
+ if (compiled.errors.length) {
206
+ logger.log(`Compile errors: ${compiled.errors.join(', ')}`, 'error')
207
+ }
208
+
209
+ let code = compiled.code
210
+
211
+ if (code.includes('.vue"') || code.includes(".vue'")) {
212
+ code = rewriteVueImports(code)
213
+ }
214
+
215
+ if (serverDataReplacement !== undefined) {
216
+ code = code.replace(RX_SERVER_DATA_TOKEN, () => serverDataReplacement)
217
+ }
218
+
219
+ if (!isRootScript) {
220
+ for (const style of compiled.styles) {
221
+ if (style.code) {
222
+ code += `;\n(function(){var k='__vu_css_${id}';var s=document.getElementById(k)||(function(){var el=document.createElement('style');el.id=k;document.head.appendChild(el);return el})();s.textContent=${JSON.stringify(style.code)}})()`
223
+ }
224
+ }
225
+ }
226
+
227
+ const sourceURL =
228
+ routePath.replace(/^.*[\\/]/, '').replace(/\?.*$/, '') ||
229
+ (isRootScript ? 'root.vue' : 'module.vue')
230
+ code += `\n//# sourceURL=${sourceURL}`
231
+
232
+ return code
233
+ }
234
+
235
+ static async handleScript(
236
+ id: string,
237
+ routePath: string,
238
+ isRootScript: boolean,
239
+ parsed: ParsedCacheEntry,
240
+ serverValues?: Record<string, any>,
241
+ ) {
242
+ const { lastMod, serverScript } = parsed
243
+ const hasServerScript = Boolean(serverScript?.trim())
244
+
245
+ // Static subcomponent (no server script) or root script: no per-request
246
+ // data, so the built file can live on disk.
247
+ if (!hasServerScript || isRootScript) {
248
+ const dir = fs.resolve(cacheDir, 'js')
249
+ const fileName = `${id}${isRootScript ? '.root' : ''}.js`
250
+ const replacement =
251
+ isRootScript && hasServerScript
252
+ ? '(globalThis.__vue_server || {})'
253
+ : undefined
254
+
255
+ return await fs.getOrCreateCachedFile(dir, fileName, lastMod, () =>
256
+ this.buildScriptCode(id, routePath, isRootScript, parsed, replacement),
257
+ )
258
+ }
259
+
260
+ // Dynamic subcomponent with <script server>: compile once, then splice in
261
+ // this request's data. The response is per-user, so it must not be stored.
262
+ const cached = tokenizedModuleCache.get(id)
263
+ let template = cached?.lastMod === lastMod ? cached.code : null
264
+
265
+ if (template === null) {
266
+ template = await this.buildScriptCode(id, routePath, false, parsed)
267
+ tokenizedModuleCache.set(id, { lastMod, code: template })
268
+ }
269
+
270
+ const replacement = escapeScriptJson(serverValues || {})
271
+ const code = template.replace(RX_SERVER_DATA_TOKEN, () => replacement)
272
+
273
+ return response.type(code, 'text/javascript; charset=utf-8', {
274
+ headers: { 'Cache-Control': 'private, no-store' },
275
+ })
276
+ }
277
+
278
+ static async handleCss(id: string, parsed: ParsedCacheEntry) {
279
+ const { lastMod, scopeId, styles } = parsed
280
+
281
+ const dir = fs.resolve(cacheDir, 'css')
282
+ const fileName = `${id}.css`
283
+
284
+ return await fs.getOrCreateCachedFile(dir, fileName, lastMod, async () => {
285
+ const compiled = await Promise.all(
286
+ styles.map(style => compileStyleBlock({ style, id: scopeId || id })),
287
+ )
288
+
289
+ const css = compiled
290
+ .map(s => s.code)
291
+ .filter(Boolean)
292
+ .join('\n\n')
293
+
294
+ return css || null
295
+ })
296
+ }
297
+
298
+ static async handleHtml(
299
+ id: string,
300
+ params: any,
301
+ routePath: string,
302
+ serverParams: any,
303
+ parsed: ParsedCacheEntry,
304
+ ) {
305
+ const { hasCss, serverScript } = parsed
306
+ const hasServerData =
307
+ Boolean(serverScript) || params?.errorCode !== undefined
308
+
309
+ const payload =
310
+ typeof serverParams === 'object' && serverParams
311
+ ? { ...params, ...serverParams }
312
+ : params?.errorCode !== undefined
313
+ ? params
314
+ : serverParams
315
+
316
+ const serverDecl = hasServerData
317
+ ? `<script>globalThis.__vue_server = ${escapeScriptJson(payload)};</script>`
318
+ : ''
319
+
320
+ let hydrated = VUE_HTML_SHELL.replace(
321
+ '/*__SERVER_VARIABLES__*/',
322
+ () => serverDecl,
323
+ )
324
+
325
+ if (parsed.meta.title) {
326
+ const title = escapeHtml(parsed.meta.title)
327
+ hydrated = hydrated.replace(
328
+ '<title>Vue App</title>',
329
+ () => `<title>${title}</title>`,
330
+ )
331
+ }
332
+
333
+ const prio =
334
+ (hasCss
335
+ ? `<link rel="stylesheet" id="__vu_css_${id}" href="${routePath}?__vue_css=true">\n`
336
+ : '') +
337
+ `<script type="module" src="${routePath}?__vue_script=root"></script>`
338
+
339
+ const htmlRes = await injectIfHtml(hydrated, params, { prio })
340
+ return htmlRes || response.error('Failed to build HTML', 500)
341
+ }
342
+ }
343
+
344
+ export class VueErrorHandler extends DynamicErrorHandler {
345
+ static get config() {
346
+ return {
347
+ ext: ['vue'],
348
+ dir: Bakery.serveRoot,
349
+ include: ['**/error.vue', '**/error-*.vue'],
350
+ }
351
+ }
352
+
353
+ static get cacheDir() {
354
+ return cacheDir
355
+ }
356
+
357
+ static handle = sharedHandler
358
+ }
359
+
360
+ /** A server-script result that is already a complete response, not page data. */
361
+ function asDirectResponse(value: any) {
362
+ if (value instanceof Response) return value
363
+ if (value instanceof JsonResponseData) return value
364
+ if (
365
+ value &&
366
+ typeof value === 'object' &&
367
+ 'arrayBuffer' in value &&
368
+ 'size' in value
369
+ ) {
370
+ return value as Bun.BunFile
371
+ }
372
+ return null
373
+ }
374
+
375
+ /**
376
+ * Serve `value` if the server script already produced a complete response,
377
+ * otherwise `null` so the caller treats it as page data.
378
+ *
379
+ * Both paths through `sharedHandler` — the `__vue_action` call and the ordinary
380
+ * page render — need exactly this, and each carried its own verbatim copy. A
381
+ * `BunFile` goes through `ETag.sendFile` so a conditional request can 304; the
382
+ * `new Response` is the fallback for the un-cacheable case.
383
+ */
384
+ async function serveIfDirect(value: any, req: Request) {
385
+ const direct = asDirectResponse(value)
386
+ if (!direct) return null
387
+ if (direct instanceof Response || direct instanceof JsonResponseData) {
388
+ return direct
389
+ }
390
+ return (await ETag.sendFile(direct, req)) || new Response(direct as any)
391
+ }
392
+
393
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: request handler — one branch per SFC render path
394
+ async function sharedHandler(
395
+ this: typeof DynamicHandler | typeof DynamicErrorHandler,
396
+ path: string,
397
+ req: Request,
398
+ errors?: Handler.Error.Data,
399
+ ) {
400
+ if (path.startsWith(VUE_CHUNK_PREFIX)) {
401
+ return serveVueChunk(path, req)
402
+ }
403
+
404
+ path = normalizePath(path)
405
+ const begun = await beginPageRoute(this, path, errors)
406
+ if (begun instanceof Response) return begun
407
+ const { errorData, info } = begun
408
+ const routePath = `/${info.path}`
409
+
410
+ const diskFile = info.file
411
+ const lastMod = diskFile.lastModified
412
+ const id = toHash(hostKey(info.path))
413
+
414
+ const url = new URL(req.url)
415
+ const vueScriptParam = url.searchParams.get('__vue_script')
416
+ const isCss = url.searchParams.has('__vue_css')
417
+ const accept = req.headers.get('accept') || ''
418
+ const isScript =
419
+ vueScriptParam !== null ||
420
+ accept.includes('text/javascript') ||
421
+ req.headers.get('sec-fetch-dest') === 'script'
422
+
423
+ const parsed = await VueHandler.parseVueFile(
424
+ id,
425
+ diskFile,
426
+ info.file.name!,
427
+ lastMod,
428
+ )
429
+
430
+ // module-only: block page requests (allow script/css imports)
431
+ if (parsed.meta.moduleOnly && !isScript && !isCss) {
432
+ return response.error('Not Found', 404)
433
+ }
434
+
435
+ // page-only: block module imports (allow root scripts, page, and css)
436
+ if (parsed.meta.pageOnly && isScript && vueScriptParam === 'module') {
437
+ return response.error('Not Found', 404)
438
+ }
439
+
440
+ const { serverScript } = parsed
441
+ const params = info.getParams(path) || {}
442
+ const finalParams = errorData ? Object.assign({}, params, errorData) : params
443
+
444
+ const actionName =
445
+ url.searchParams.get('__vue_action') || req.headers.get('x-vue-action')
446
+
447
+ if (actionName) {
448
+ const rejected = validateActionRequest(req, url)
449
+ if (rejected) return rejected
450
+
451
+ const actionFile = url.searchParams.get('__vue_file')
452
+
453
+ let target = { id, lastMod, filePath: diskFile.name, script: serverScript }
454
+ let targetMeta = parsed.meta
455
+
456
+ if (actionFile) {
457
+ const resolved = await resolveActionTarget(actionFile)
458
+ if (!resolved) return response.error('Not Found', 404)
459
+
460
+ const targetParsed = await VueHandler.parseVueFile(
461
+ resolved.id,
462
+ resolved.diskFile,
463
+ resolved.filePath,
464
+ resolved.lastMod,
465
+ )
466
+ target = {
467
+ id: resolved.id,
468
+ lastMod: resolved.lastMod,
469
+ filePath: resolved.filePath,
470
+ script: targetParsed.serverScript,
471
+ }
472
+ targetMeta = targetParsed.meta
473
+ }
474
+
475
+ // The gates above ran against the route. This one runs against whatever
476
+ // `__vue_file` chose, which is the thing that is about to execute — and it
477
+ // runs before the body is read, so a rejected request costs a parse it
478
+ // never needed.
479
+ const denied = validateActionTarget(
480
+ { id: target.id, meta: targetMeta, serverScript: target.script },
481
+ id,
482
+ actionName,
483
+ )
484
+ if (denied) return denied
485
+
486
+ const body = await this.params(req, finalParams)
487
+
488
+ const actionArgs = Array.isArray(body?.args)
489
+ ? body.args
490
+ : Array.isArray(body)
491
+ ? body
492
+ : []
493
+
494
+ const actionResult = await getServerResponse({
495
+ script: target.script,
496
+ id: target.id,
497
+ lastMod: target.lastMod,
498
+ req,
499
+ body,
500
+ filePath: target.filePath,
501
+ actionName,
502
+ actionArgs,
503
+ })
504
+
505
+ const direct = await serveIfDirect(actionResult, req)
506
+ if (direct) return direct
507
+ return response.json.success('OK', actionResult)
508
+ }
509
+
510
+ // Root scripts and stylesheets carry no per-request data — running the server
511
+ // block for them would re-execute every top-level query on each sub-request.
512
+ const needsServerData = !isCss && vueScriptParam !== 'root'
513
+
514
+ if (!needsServerData) {
515
+ if (isCss) return VueHandler.handleCss(id, parsed)
516
+ return VueHandler.handleScript(id, routePath, true, parsed)
517
+ }
518
+
519
+ const body = await this.params(req, finalParams)
520
+ const serverParams = await getServerResponse({
521
+ script: serverScript,
522
+ id,
523
+ lastMod,
524
+ req,
525
+ body,
526
+ filePath: diskFile.name,
527
+ })
528
+
529
+ const direct = await serveIfDirect(serverParams, req)
530
+ if (direct) return direct
531
+
532
+ if (isScript) {
533
+ const serverValues = serverScript.trim() ? serverParams : undefined
534
+ return VueHandler.handleScript(id, routePath, false, parsed, serverValues)
535
+ }
536
+
537
+ return VueHandler.handleHtml(id, finalParams, routePath, serverParams, parsed)
538
+ }
package/src/index.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { definePlugin } from '@bakery-framework/core/plugins'
2
+ import { setVuePluginOptions } from './compile'
3
+ import type { VuePluginOptions } from './types'
4
+ import { rewriteVueImports } from './utils'
5
+
6
+ /**
7
+ * `vuePlugin` is this package's only export, and its parameter type lived in a
8
+ * `.d.ts` reachable through no working specifier — so a consumer could pass
9
+ * options but never name their type. Re-exported here, where the function that
10
+ * takes them is.
11
+ */
12
+ export type { CustomElementsOption, VuePluginOptions } from './types'
13
+
14
+ export default function vuePlugin(options?: VuePluginOptions) {
15
+ if (options) setVuePluginOptions(options)
16
+ return definePlugin({
17
+ name: 'vue',
18
+ async setup() {
19
+ const { setupVue } = await import('./setup')
20
+ await setupVue()
21
+ },
22
+ onCompile(content, path) {
23
+ if (
24
+ !path.endsWith('.ts') &&
25
+ !path.endsWith('.js') &&
26
+ !path.endsWith('.vue')
27
+ )
28
+ return content
29
+
30
+ let modified = content
31
+
32
+ if (modified.includes('.vue"') || modified.includes(".vue'")) {
33
+ modified = rewriteVueImports(modified)
34
+ }
35
+
36
+ return modified
37
+ },
38
+ })
39
+ }
package/src/setup.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { Bakery } from '@bakery-framework/core/core/bakery'
2
+ import { Logger } from '@bakery-framework/core/logger'
3
+ import { VueErrorHandler, VueHandler } from './handler'
4
+ import { initVueVersion, VUE_VERSION } from './utils'
5
+
6
+ const logger = new Logger('vue')
7
+
8
+ function checkDeps() {
9
+ try {
10
+ Bun.resolveSync('vue/package.json', import.meta.dir)
11
+ } catch {
12
+ logger.log('"vue" is not installed. Run `bun add vue`.', 'error')
13
+ process.exit(1)
14
+ }
15
+ }
16
+
17
+ export function setupVue() {
18
+ checkDeps()
19
+ initVueVersion()
20
+ Bakery.handlers.fetch.set(VueHandler, 58)
21
+ Bakery.handlers.error.set(VueErrorHandler, 18)
22
+ Bakery.config.importMap.vue = `/_vue/${VUE_VERSION}.js`
23
+ }
package/src/shell.ts ADDED
@@ -0,0 +1,15 @@
1
+ export const VUE_HTML_SHELL = `
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <title>Vue App</title>
8
+ /*__SERVER_VARIABLES__*/
9
+ </head>
10
+
11
+ <body>
12
+ <div id="app"></div>
13
+ </body>
14
+
15
+ </html>`
package/src/types.d.ts ADDED
@@ -0,0 +1,92 @@
1
+ import type {
2
+ SFCDescriptor,
3
+ SFCStyleBlock,
4
+ SFCStyleCompileResults,
5
+ } from '@vue/compiler-sfc'
6
+
7
+ export interface ParseVueOptions {
8
+ content: string
9
+ filename: string
10
+ }
11
+
12
+ export interface ParseVueResult {
13
+ descriptor: SFCDescriptor
14
+ errors: string[]
15
+ }
16
+
17
+ export interface CompileScriptOptions {
18
+ descriptor: SFCDescriptor
19
+ id: string
20
+ }
21
+
22
+ export interface CompileScriptResult {
23
+ code: string
24
+ bindings: Record<string, any>
25
+ }
26
+
27
+ export interface CompileTemplateOptions {
28
+ descriptor: SFCDescriptor
29
+ id: string
30
+ filename: string
31
+ bindings?: Record<string, any>
32
+ }
33
+
34
+ export interface CompileStyleOptions {
35
+ style: SFCStyleBlock
36
+ id: string
37
+ }
38
+
39
+ export interface AssembleComponentOptions {
40
+ scriptCode: string
41
+ renderCode: string | null
42
+ isRoot: boolean
43
+ scopeId?: string
44
+ }
45
+
46
+ export interface CompileVueFileOptions {
47
+ content: string
48
+ filename: string
49
+ id: string
50
+ isRootScript: boolean
51
+ }
52
+
53
+ export interface CompileVueFileResult {
54
+ code: string
55
+ styles: SFCStyleCompileResults[]
56
+ errors: string[]
57
+ }
58
+
59
+ export interface VueMeta {
60
+ moduleOnly: boolean
61
+ pageOnly: boolean
62
+ title: string | null
63
+ }
64
+
65
+ export interface ParsedCacheEntry {
66
+ lastMod: number
67
+ serverScript: string
68
+ cleanContent: string
69
+ scopeId: string
70
+ /** Style blocks from the parsed descriptor, reused when building the CSS. */
71
+ styles: SFCStyleBlock[]
72
+ hasCss: boolean
73
+ meta: VueMeta
74
+ }
75
+
76
+ export interface ServerResponseOptions {
77
+ script: string
78
+ id: string
79
+ lastMod: number
80
+ req: Request
81
+ body: any
82
+ filePath?: string
83
+ actionName?: string
84
+ actionArgs?: any[]
85
+ }
86
+
87
+ export type CustomElementsOption = string[] | ((tag: string) => boolean)
88
+
89
+ export interface VuePluginOptions {
90
+ customElements?: CustomElementsOption
91
+ compilerOptions?: Record<string, any>
92
+ }