@bakery-framework/plugin-vue 2.4.1 → 2.4.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakery-framework/plugin-vue",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Bakery vue plugin.",
5
5
  "keywords": [
6
6
  "bakery",
@@ -43,7 +43,7 @@
43
43
  "vue": "^3.5.38"
44
44
  },
45
45
  "dependencies": {
46
- "@bakery-framework/core": "^2.4.1"
46
+ "@bakery-framework/core": "^2.4.3"
47
47
  },
48
48
  "engines": {
49
49
  "bun": ">=1.4.0"
package/src/actions.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { Bakery, hostKey } from '@bakery-framework/core/core/bakery'
2
- import { fs, response, toHash } from '@bakery-framework/core/utils'
2
+ import {
3
+ fs,
4
+ requestOrigin,
5
+ response,
6
+ toHash,
7
+ } from '@bakery-framework/core/utils'
3
8
  import type { VueMeta } from './types'
4
9
  import { collectExportedFunctionNames } from './utils'
5
10
 
@@ -33,15 +38,18 @@ export function validateActionRequest(req: Request, url: URL): Response | null {
33
38
  return response.error('Server actions require a JSON body', 415)
34
39
  }
35
40
 
36
- const origin = req.headers.get('origin')
37
- if (origin && origin !== url.origin) {
38
- return response.error('Cross-origin server action rejected', 403)
39
- }
40
-
41
41
  const fetchSite = req.headers.get('sec-fetch-site')
42
42
  if (fetchSite && fetchSite !== 'same-origin' && fetchSite !== 'none') {
43
43
  return response.error('Cross-site server action rejected', 403)
44
44
  }
45
+ // Settled, as in `checkSameOrigin`: behind a proxy that ends TLS, `Origin`
46
+ // names a scheme this server never saw.
47
+ if (fetchSite) return null
48
+
49
+ const origin = req.headers.get('origin')
50
+ if (origin && origin !== requestOrigin(req, url)) {
51
+ return response.error('Cross-origin server action rejected', 403)
52
+ }
45
53
 
46
54
  return null
47
55
  }
package/src/compile.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { compileText } from '@bakery-framework/core/compiler'
2
+ import { Try } from '@bakery-framework/core/utils'
2
3
  import type { SFCStyleCompileResults } from '@vue/compiler-sfc'
3
4
 
4
5
  import type {
@@ -65,6 +66,33 @@ export function setVuePluginOptions(opts?: VuePluginOptions) {
65
66
  if (opts) vuePluginOptions = opts
66
67
  }
67
68
 
69
+ /**
70
+ * A test of whether an event attribute's value is one JavaScript expression,
71
+ * asked the way Vue's template compiler asks it: Babel over the value in
72
+ * parentheses, with the app's `expressionPlugins` and the `typescript` plugin
73
+ * Vue always adds.
74
+ *
75
+ * Vue compiles such a value as written. A function or a member path becomes
76
+ * the handler itself, and any other expression is wrapped as
77
+ * `$event => (value)`. `parseVueFile` asks before it appends a `;` to a
78
+ * multi-line attribute, because the `;` makes Vue compile the value as
79
+ * statements instead.
80
+ */
81
+ export async function loadExpressionCheck(): Promise<
82
+ (value: string) => boolean
83
+ > {
84
+ const { babelParse } = await loadCompiler()
85
+ const plugins = [
86
+ ...(vuePluginOptions.compilerOptions?.expressionPlugins ?? []),
87
+ 'typescript',
88
+ ]
89
+ return value =>
90
+ Try.return(() => {
91
+ const { body } = babelParse(`(${value})`, { plugins }).program
92
+ return body.length === 1 && body[0].type === 'ExpressionStatement'
93
+ }, false)
94
+ }
95
+
68
96
  /**
69
97
  * `'runtime'` unless the app opted into the full build.
70
98
  *
@@ -95,6 +123,13 @@ export function vuePluginOptionsFingerprint(): string {
95
123
  )
96
124
  }
97
125
 
126
+ /**
127
+ * Elements no component resolves. `selectedcontent` belongs to the customizable
128
+ * `<select>`, and Vue's list of HTML tags predates it: compiled as a component,
129
+ * every page holding one warned in development.
130
+ */
131
+ const BUILT_IN_ELEMENTS = ['iconify-icon', 'selectedcontent']
132
+
98
133
  export function resolveIsCustomElement(tag: string): boolean {
99
134
  const ce = vuePluginOptions?.customElements
100
135
  const userFn = vuePluginOptions?.compilerOptions?.isCustomElement
@@ -109,9 +144,7 @@ export function resolveIsCustomElement(tag: string): boolean {
109
144
  if (userFn(tag)) return true
110
145
  }
111
146
 
112
- if (tag === 'iconify-icon') return true
113
-
114
- return false
147
+ return BUILT_IN_ELEMENTS.includes(tag)
115
148
  }
116
149
 
117
150
  export async function parseVue(
@@ -153,7 +186,7 @@ export async function compileScriptBlock(
153
186
  hoistStatic: true,
154
187
  })
155
188
  return {
156
- code: await compileText(result.content),
189
+ code: await compileText(result.content, { file: descriptor.filename }),
157
190
  bindings: result.bindings || {},
158
191
  }
159
192
  }
@@ -238,7 +271,7 @@ export async function compileTemplateBlock(
238
271
  // guarantee, so it is the one written down.
239
272
  let code = result.code
240
273
  code = code.replace(/^export\s+/m, '')
241
- code = await compileText(code)
274
+ code = await compileText(code, { file: filename })
242
275
  return { code, errors }
243
276
  }
244
277
 
@@ -258,10 +291,13 @@ export async function compileStyleBlock(
258
291
  export function assembleComponent(options: AssembleComponentOptions): string {
259
292
  const { scriptCode, renderCode, isRoot, scopeId, layoutRoute } = options
260
293
  const COMPONENT_VAR = '__sfc__'
261
- let output = scriptCode.replace(
262
- /\bexport\s+default\s*/,
263
- `const ${COMPONENT_VAR} = `,
264
- )
294
+ // A plain `<script>` with no `export default` has nothing to rewrite, and
295
+ // everything below hangs off the variable all the same. Measured: the module
296
+ // answered 200 and died with `ReferenceError: __sfc__ is not defined`, on a
297
+ // blank page, with `--build` exiting 0.
298
+ let output = /\bexport\s+default\b/.test(scriptCode)
299
+ ? scriptCode.replace(/\bexport\s+default\s*/, `const ${COMPONENT_VAR} = `)
300
+ : `${scriptCode}\nconst ${COMPONENT_VAR} = {};`
265
301
 
266
302
  if (renderCode) {
267
303
  // No export strip here, deliberately. `compileTemplateBlock` has already
@@ -325,12 +361,15 @@ export function assembleComponent(options: AssembleComponentOptions): string {
325
361
  */
326
362
  function buildRuntimeCustomElementCheck(): string {
327
363
  const ce = vuePluginOptions.customElements
364
+ const builtIn = JSON.stringify(BUILT_IN_ELEMENTS)
328
365
 
329
366
  if (typeof ce === 'function') {
330
- return `(tag) => { try { return (${ce.toString()})(tag) || tag === 'iconify-icon' } catch { return tag === 'iconify-icon' } }`
367
+ return `(tag) => { try { return (${ce.toString()})(tag) || ${builtIn}.includes(tag) } catch { return ${builtIn}.includes(tag) } }`
331
368
  }
332
369
 
333
- const tags = [...new Set([...(Array.isArray(ce) ? ce : []), 'iconify-icon'])]
370
+ const tags = [
371
+ ...new Set([...(Array.isArray(ce) ? ce : []), ...BUILT_IN_ELEMENTS]),
372
+ ]
334
373
  return `(tag) => ${JSON.stringify(tags)}.includes(tag)`
335
374
  }
336
375
 
package/src/handler.ts CHANGED
@@ -30,6 +30,7 @@ import { serveVueChunk, VUE_CHUNK_PREFIX } from './chunks'
30
30
  import {
31
31
  compileStyleBlock,
32
32
  compileVueFile,
33
+ loadExpressionCheck,
33
34
  parseVue,
34
35
  vueBuildVariant,
35
36
  } from './compile'
@@ -496,6 +497,19 @@ export class VueHandler extends DynamicHandler {
496
497
  }
497
498
  }
498
499
 
500
+ // A multi-line event attribute with no `;` gets one. Vue reads a value
501
+ // with no `;` as one expression, so two statements on two lines (as a
502
+ // formatter with `semi: false` writes them) came out wrapped in
503
+ // parentheses and failed to compile; with the `;`, Vue compiles the value
504
+ // as statements.
505
+ //
506
+ // Only when the value is not already one expression, which Vue compiles
507
+ // as written. With a `;` appended, an arrow handler broken across lines
508
+ // was emitted as `(d) => {…};` inside the render function's props and
509
+ // the module did not parse, a `function (e) {…}` failed Vue's own
510
+ // compile, and a member path (`saveDraft`) compiled to a statement that
511
+ // reads the function and never calls it.
512
+ const isExpression = await loadExpressionCheck()
499
513
  const rxEventAttr =
500
514
  /(@[\w.-]+|v-on:[\w.-]+)=("([^"\n]*\n[^"]*)"|'([^'\n]*\n[^']*)')/g
501
515
  cleanContent = cleanContent.replace(
@@ -503,7 +517,7 @@ export class VueHandler extends DynamicHandler {
503
517
  (match, attr, doubleMatch, innerDouble, innerSingle) => {
504
518
  const quote = doubleMatch.startsWith('"') ? '"' : "'"
505
519
  const val = innerDouble !== undefined ? innerDouble : innerSingle
506
- if (!val.includes(';')) {
520
+ if (!val.includes(';') && !isExpression(val)) {
507
521
  return `${attr}=${quote}${val};${quote}`
508
522
  }
509
523
  return match