@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 +2 -2
- package/src/actions.ts +14 -6
- package/src/compile.ts +50 -11
- package/src/handler.ts +15 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakery-framework/plugin-vue",
|
|
3
|
-
"version": "2.4.
|
|
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.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
367
|
+
return `(tag) => { try { return (${ce.toString()})(tag) || ${builtIn}.includes(tag) } catch { return ${builtIn}.includes(tag) } }`
|
|
331
368
|
}
|
|
332
369
|
|
|
333
|
-
const tags = [
|
|
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
|