@bakery-framework/plugin-vue 2.0.0-alpha.12 → 2.0.0-alpha.14
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/compile.ts +41 -9
- package/src/handler.ts +54 -7
- package/src/index.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakery-framework/plugin-vue",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.14",
|
|
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.0.0-alpha.
|
|
46
|
+
"@bakery-framework/core": "^2.0.0-alpha.14"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"bun": ">=1.4.0"
|
package/src/compile.ts
CHANGED
|
@@ -14,17 +14,49 @@ import type {
|
|
|
14
14
|
VuePluginOptions,
|
|
15
15
|
} from './types'
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The **promise**, not the module.
|
|
19
|
+
*
|
|
20
|
+
* `compiler` was assigned after the await, so two requests arriving before the
|
|
21
|
+
* first load finished each started their own `await import()`. Bun's module
|
|
22
|
+
* cache made that harmless rather than correct; memoising the promise is what
|
|
23
|
+
* actually makes it one load, and it is what lets `preloadCompiler()` below
|
|
24
|
+
* overlap with the rest of boot while a request that beats it simply awaits
|
|
25
|
+
* the same thing.
|
|
26
|
+
*/
|
|
27
|
+
let compilerLoad: Promise<typeof import('@vue/compiler-sfc')> | null = null
|
|
18
28
|
|
|
19
29
|
async function loadCompiler() {
|
|
20
|
-
if (!
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
if (!compilerLoad) {
|
|
31
|
+
compilerLoad = import('@vue/compiler-sfc').catch(() => {
|
|
32
|
+
// Cleared so a later attempt can retry - a package installed while the
|
|
33
|
+
// dev server is running should not need a restart to be found.
|
|
34
|
+
compilerLoad = null
|
|
24
35
|
throw new Error('compiler-sfc not available. Run `bun add vue`.')
|
|
25
|
-
}
|
|
36
|
+
})
|
|
26
37
|
}
|
|
27
|
-
return
|
|
38
|
+
return await compilerLoad
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Start loading the compiler now rather than on the first `.vue` request.
|
|
43
|
+
*
|
|
44
|
+
* `@vue/compiler-sfc` is a large module: measured from this repo's example
|
|
45
|
+
* app, **168-173 ms warm and 1,848 ms on a cold filesystem**, and every server
|
|
46
|
+
* process paid it inside the first Vue page it served. Called from the
|
|
47
|
+
* plugin's `onStart`, where it overlaps with the rest of boot.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately not awaited by the caller and deliberately not fatal. An app
|
|
50
|
+
* can register this plugin before it has written a single `.vue` file, and
|
|
51
|
+
* refusing to boot over a package it does not need yet would be worse than
|
|
52
|
+
* the clear error the first request already gets. The rejection is swallowed
|
|
53
|
+
* here and `loadCompiler` raises it properly when something actually needs
|
|
54
|
+
* the compiler.
|
|
55
|
+
*/
|
|
56
|
+
export function preloadCompiler(): void {
|
|
57
|
+
void loadCompiler().catch(() => {
|
|
58
|
+
// See above: absence is reported at the point of use, not at boot.
|
|
59
|
+
})
|
|
28
60
|
}
|
|
29
61
|
|
|
30
62
|
let vuePluginOptions: VuePluginOptions = {}
|
|
@@ -182,8 +214,8 @@ export async function compileTemplateBlock(
|
|
|
182
214
|
export async function compileStyleBlock(
|
|
183
215
|
options: CompileStyleOptions,
|
|
184
216
|
): Promise<SFCStyleCompileResults> {
|
|
185
|
-
await loadCompiler()
|
|
186
|
-
return
|
|
217
|
+
const sfc = await loadCompiler()
|
|
218
|
+
return sfc.compileStyle({
|
|
187
219
|
source: options.style.content,
|
|
188
220
|
filename: 'style.css',
|
|
189
221
|
id: options.id,
|
package/src/handler.ts
CHANGED
|
@@ -173,18 +173,60 @@ function containsRouteFile(dir: string, exts: string[]): boolean {
|
|
|
173
173
|
* intercepted — that is the spelling for linking a raw file out of a
|
|
174
174
|
* catch-all's subtree, and what `docs/plugins/vue.md` prescribes.
|
|
175
175
|
*
|
|
176
|
-
* Computed per page request, so
|
|
177
|
-
* next load without cache ceremony.
|
|
176
|
+
* Computed per page request in development, so a file added or removed is
|
|
177
|
+
* seen on the next load without cache ceremony. **Memoised in production**,
|
|
178
|
+
* where it cannot change: there is no watcher, and the `SIGHUP` handler in
|
|
179
|
+
* `core/init.ts` is a deliberate no-op, so the only way the page tree changes
|
|
180
|
+
* is a restart.
|
|
181
|
+
*
|
|
182
|
+
* Worth the branch because the walk recurses into every sibling directory to
|
|
183
|
+
* ask whether it holds a route file, and it runs on **every** catch-all page
|
|
184
|
+
* request. Measured against directory trees of an app's shape, three rounds
|
|
185
|
+
* each with a CPU-bound control flat at 33 ms:
|
|
186
|
+
*
|
|
187
|
+
* 4 directories, depth 2, 10 claims 0.48 ms
|
|
188
|
+
* 12 directories, depth 3, 24 claims 1.24 ms
|
|
189
|
+
* 30 directories, depth 4, 50 claims 4.44 ms
|
|
190
|
+
*
|
|
191
|
+
* An `LRUCache` rather than a plain map, per convention 6. The key is a
|
|
192
|
+
* resolved disk path that routing produced, not anything a client sends, so
|
|
193
|
+
* it is bounded by the app's own catch-all count either way - but the bound
|
|
194
|
+
* is stated rather than argued.
|
|
195
|
+
*/
|
|
196
|
+
const claimedCache = new LRUCache<
|
|
197
|
+
string,
|
|
198
|
+
{ claimed: string[]; claimedSingle: boolean }
|
|
199
|
+
>(100)
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Test seam (convention 9).
|
|
203
|
+
*
|
|
204
|
+
* **A test process reports `PROD === '1'`**, so this memo is *on* by default
|
|
205
|
+
* under `bun test` — a test that writes into a page directory between two
|
|
206
|
+
* calls is running against the production behaviour whether it meant to or
|
|
207
|
+
* not. Clear it between such calls, or ask for development explicitly with
|
|
208
|
+
* `withEnvFlag('PROD', false, …)`.
|
|
178
209
|
*/
|
|
210
|
+
export function __resetClaimedCache(): void {
|
|
211
|
+
claimedCache.clear()
|
|
212
|
+
}
|
|
213
|
+
|
|
179
214
|
export function claimedBeside(catchAllFile: string): {
|
|
180
215
|
claimed: string[]
|
|
181
216
|
claimedSingle: boolean
|
|
182
217
|
} {
|
|
218
|
+
const resolved = fs.resolve(catchAllFile)
|
|
219
|
+
const memoised = import.meta.env.PROD === '1'
|
|
220
|
+
if (memoised) {
|
|
221
|
+
const hit = claimedCache.get(resolved)
|
|
222
|
+
if (hit) return hit
|
|
223
|
+
}
|
|
224
|
+
|
|
183
225
|
const claimed = new Set<string>()
|
|
184
226
|
let claimedSingle = false
|
|
185
227
|
|
|
186
|
-
const dir =
|
|
187
|
-
const self =
|
|
228
|
+
const dir = resolved.replace(/\/[^/]*$/, '')
|
|
229
|
+
const self = resolved.slice(dir.length + 1)
|
|
188
230
|
// The handler's own table (`['vue']`), read at call time so the two cannot
|
|
189
231
|
// drift apart.
|
|
190
232
|
const exts = VueHandler.config.ext.map(ext => `.${ext}`)
|
|
@@ -195,8 +237,11 @@ export function claimedBeside(catchAllFile: string): {
|
|
|
195
237
|
} catch {
|
|
196
238
|
// Unreadable directory: no visible siblings means nothing extra claimed,
|
|
197
239
|
// and a hard load still routes correctly — the stamp is an optimisation
|
|
198
|
-
// of honesty, not the source of it.
|
|
199
|
-
|
|
240
|
+
// of honesty, not the source of it. Remembered like any other answer, so
|
|
241
|
+
// an unreadable directory does not re-throw on every request.
|
|
242
|
+
const empty = { claimed: [], claimedSingle: false }
|
|
243
|
+
if (memoised) claimedCache.set(resolved, empty)
|
|
244
|
+
return empty
|
|
200
245
|
}
|
|
201
246
|
|
|
202
247
|
for (const entry of entries) {
|
|
@@ -223,7 +268,9 @@ export function claimedBeside(catchAllFile: string): {
|
|
|
223
268
|
if (stem && stem !== name) claimed.add(stem)
|
|
224
269
|
}
|
|
225
270
|
|
|
226
|
-
|
|
271
|
+
const result = { claimed: [...claimed], claimedSingle }
|
|
272
|
+
if (memoised) claimedCache.set(resolved, result)
|
|
273
|
+
return result
|
|
227
274
|
}
|
|
228
275
|
|
|
229
276
|
export class VueHandler extends DynamicHandler {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { definePlugin } from '@bakery-framework/core/plugins'
|
|
2
|
+
import { preloadCompiler } from './compile'
|
|
2
3
|
import { setVuePluginOptions } from './compile'
|
|
3
4
|
import type { VuePluginOptions } from './types'
|
|
4
5
|
import { rewriteVueImports } from './utils'
|
|
@@ -48,6 +49,19 @@ export default function vuePlugin(options?: VuePluginOptions) {
|
|
|
48
49
|
const { setupVue } = await import('./setup')
|
|
49
50
|
await setupVue()
|
|
50
51
|
},
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Start loading `@vue/compiler-sfc` while the rest of boot runs.
|
|
55
|
+
*
|
|
56
|
+
* It is 168-173 ms warm and 1,848 ms on a cold filesystem, and without
|
|
57
|
+
* this every server process paid it inside the first Vue page it served.
|
|
58
|
+
* Not awaited: the load overlaps with everything else here, and a request
|
|
59
|
+
* that arrives before it finishes awaits the same promise rather than
|
|
60
|
+
* starting a second one.
|
|
61
|
+
*/
|
|
62
|
+
onStart() {
|
|
63
|
+
preloadCompiler()
|
|
64
|
+
},
|
|
51
65
|
onCompile(content, path) {
|
|
52
66
|
if (
|
|
53
67
|
!path.endsWith('.ts') &&
|