@useavalon/vue 0.1.3 → 0.1.4
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/dist/client/hmr-adapter.js +1 -0
- package/{client/hydration.ts → dist/client/hydration.js} +76 -124
- package/dist/client/index.js +1 -0
- package/dist/mod.js +1 -0
- package/dist/server/css-extractor.js +1 -0
- package/dist/server/renderer.js +1 -0
- package/dist/types.js +1 -0
- package/package.json +13 -12
- package/client/hmr-adapter.ts +0 -226
- package/client/index.ts +0 -7
- package/mod.ts +0 -59
- package/server/css-extractor.ts +0 -175
- package/server/renderer.ts +0 -116
- package/types.ts +0 -80
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{BaseFrameworkAdapter as e}from"@useavalon/avalon/client/hmr";export class VueHMRAdapter extends e{name=`vue`;apps=new WeakMap;componentIds=new WeakMap;canHandle(e){if(!e)return!1;if(typeof e==`function`)return!0;if(typeof e!=`object`)return!1;let t=e;return`setup`in t||`data`in t||`render`in t||`template`in t||`props`in t||`computed`in t||`methods`in t||`components`in t||`emits`in t||`mounted`in t||`created`in t||`beforeMount`in t||`beforeCreate`in t||`__vccOpts`in t}preserveState(e){try{let t=super.preserveState(e);if(!t)return null;let n=e.dataset.props?JSON.parse(e.dataset.props):{},r=this.extractComponentName(e.dataset.src||``),i=this.captureReactiveData(e);return{...t,framework:`vue`,data:{componentName:r,capturedProps:n,reactiveData:i}}}catch(e){return console.warn(`Failed to preserve Vue state:`,e),null}}async update(e,t,n){if(!this.canHandle(t))throw Error(`Component is not a valid Vue component`);let r=t;try{let{createApp:t}=await import(`vue`),i=this.apps.get(e),a=this.componentIds.get(e),o=globalThis.__VUE_HMR_RUNTIME__;if(o&&a)try{if(o.reload(a,r),i)return}catch(e){console.warn(`Vue HMR runtime reload failed, falling back to full remount:`,e)}if(i)try{i.unmount()}catch(e){console.warn(`Failed to unmount existing Vue app:`,e)}let s=t(r,n);s.config.errorHandler=(e,t,n)=>{console.error(`Vue component error during HMR:`,e,n)},s.mount(e,!0),this.apps.set(e,s);let c=e.dataset.src||``,l=this.generateComponentId(c);this.componentIds.set(e,l),o&&o.createRecord(l,r),e.dataset.hydrated=`true`,e.dataset.hydrationStatus=`success`}catch(t){throw console.error(`Vue HMR update failed:`,t),e.dataset.hydrationStatus=`error`,t}}restoreState(e,t){try{super.restoreState(e,t)}catch(e){console.warn(`Failed to restore Vue state:`,e)}}handleError(e,t){console.error(`Vue HMR error:`,t),super.handleError(e,t);let n=e.querySelector(`.hmr-error-indicator`);if(n){let e=t.message,r=``;e.includes(`reactive`)||e.includes(`ref`)?r=` (Hint: Check reactive state usage - refs must be accessed with .value)`:e.includes(`render`)?r=` (Hint: Check component render function or template for errors)`:e.includes(`hydration`)||e.includes(`mismatch`)?r=` (Hint: Server and client render must match)`:e.includes(`setup`)&&(r=` (Hint: Check setup function - it should return render function or object)`),n.textContent=`Vue HMR Error: ${e}${r}`}}extractComponentName(e){return(e.split(`/`).at(-1)??``).replace(/\.(vue|tsx?|jsx?)$/,``)}generateComponentId(e){return e.replaceAll(/[^a-zA-Z0-9]/g,`_`)}captureReactiveData(e){try{let t=e.__vueParentComponent;if(t&&typeof t==`object`){let e=t.data;if(e&&typeof e==`object`)return{...e}}}catch(e){console.debug(`Could not capture Vue reactive data:`,e)}}unmount(e){let t=this.apps.get(e);if(t)try{t.unmount(),this.apps.delete(e),this.componentIds.delete(e)}catch(e){console.warn(`Failed to unmount Vue app:`,e)}}}export const vueAdapter=new VueHMRAdapter;
|
|
@@ -1,124 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
function shouldHydrate(element, condition) {
|
|
79
|
-
if (!condition || condition === 'on:client') {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (condition === 'on:visible') {
|
|
84
|
-
return new Promise((resolve) => {
|
|
85
|
-
const observer = new IntersectionObserver((entries) => {
|
|
86
|
-
if (entries[0].isIntersecting) {
|
|
87
|
-
observer.disconnect();
|
|
88
|
-
resolve(true);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
observer.observe(element);
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (condition === 'on:interaction') {
|
|
96
|
-
return new Promise((resolve) => {
|
|
97
|
-
const events = ['click', 'mouseenter', 'focusin', 'touchstart'];
|
|
98
|
-
const handler = () => {
|
|
99
|
-
events.forEach(e => element.removeEventListener(e, handler));
|
|
100
|
-
resolve(true);
|
|
101
|
-
};
|
|
102
|
-
events.forEach(e => element.addEventListener(e, handler, { once: true }));
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (condition === 'on:idle') {
|
|
107
|
-
return new Promise((resolve) => {
|
|
108
|
-
if ('requestIdleCallback' in window) {
|
|
109
|
-
requestIdleCallback(() => resolve(true));
|
|
110
|
-
} else {
|
|
111
|
-
setTimeout(() => resolve(true), 200);
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (condition.startsWith('media:')) {
|
|
117
|
-
const query = condition.slice(6);
|
|
118
|
-
return window.matchMedia(query).matches;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
`;
|
|
124
|
-
}
|
|
1
|
+
import{createApp as e}from"vue";export function hydrate(t,n,r={}){try{e(n,r).mount(t,!0)}catch(e){throw console.error(`Vue hydration failed:`,e),e}}export function getHydrationScript(){return`
|
|
2
|
+
import { createApp } from 'vue';
|
|
3
|
+
|
|
4
|
+
// Auto-hydrate all Vue islands
|
|
5
|
+
document.querySelectorAll('[data-framework="vue"]').forEach(async (el) => {
|
|
6
|
+
const src = el.getAttribute('data-src');
|
|
7
|
+
const propsJson = el.getAttribute('data-props') || '{}';
|
|
8
|
+
const condition = el.getAttribute('data-condition') || 'on:client';
|
|
9
|
+
|
|
10
|
+
// Check hydration condition
|
|
11
|
+
if (!shouldHydrate(el, condition)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Dynamic import the component
|
|
17
|
+
const module = await import(src);
|
|
18
|
+
const Component = module.default || module;
|
|
19
|
+
|
|
20
|
+
// Parse props
|
|
21
|
+
const props = JSON.parse(propsJson);
|
|
22
|
+
|
|
23
|
+
// Create and mount Vue app
|
|
24
|
+
const app = createApp(Component, props);
|
|
25
|
+
app.mount(el, true);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Failed to hydrate Vue island:', error);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
function shouldHydrate(element, condition) {
|
|
32
|
+
if (!condition || condition === 'on:client') {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (condition === 'on:visible') {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const observer = new IntersectionObserver((entries) => {
|
|
39
|
+
if (entries[0].isIntersecting) {
|
|
40
|
+
observer.disconnect();
|
|
41
|
+
resolve(true);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
observer.observe(element);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (condition === 'on:interaction') {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const events = ['click', 'mouseenter', 'focusin', 'touchstart'];
|
|
51
|
+
const handler = () => {
|
|
52
|
+
events.forEach(e => element.removeEventListener(e, handler));
|
|
53
|
+
resolve(true);
|
|
54
|
+
};
|
|
55
|
+
events.forEach(e => element.addEventListener(e, handler, { once: true }));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (condition === 'on:idle') {
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
if ('requestIdleCallback' in window) {
|
|
62
|
+
requestIdleCallback(() => resolve(true));
|
|
63
|
+
} else {
|
|
64
|
+
setTimeout(() => resolve(true), 200);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (condition.startsWith('media:')) {
|
|
70
|
+
const query = condition.slice(6);
|
|
71
|
+
return window.matchMedia(query).matches;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
`}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{hydrate,getHydrationScript}from"./hydration.js";
|
package/dist/mod.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{render as e}from"./server/renderer.js";import{getHydrationScript as t}from"./client/hydration.js";export const vueIntegration={name:`vue`,version:`0.1.0`,render:e,getHydrationScript:t,config(){return{name:`vue`,fileExtensions:[`.vue`],jsxImportSources:[],detectionPatterns:{imports:[/^vue$/,/^vue\//,/from\s+['"]vue['"]/],content:[/<template>/,/<script.*setup>/,/\bdefineComponent\b/,/\bref\b/,/\breactive\b/,/\bcomputed\b/]}}},async vitePlugin(){let{default:e}=await import(`@vitejs/plugin-vue`);return e({template:{compilerOptions:{isCustomElement:e=>e===`avalon-island`}}})}};export{render}from"./server/renderer.js";export{hydrate,getHydrationScript}from"./client/hydration.js";export{extractCSS,applyScopedCSS,generateScopeId}from"./server/css-extractor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFile as e}from"node:fs/promises";export async function extractCSS(t,a={}){let o=[t.startsWith(`/`)?`src${t}`:t,t.replace(`/islands/`,`/src/islands/`),t.startsWith(`/`)?t.substring(1):t],s=``;for(let t of o)try{s=await e(t,`utf-8`);break}catch{continue}if(!s)throw Error(`Vue file not found in any of the attempted paths: ${o.join(`, `)}`);let c=r(s);if(c.length===0)return``;let l=a.scopeId||generateScopeId(t),u=``;for(let e of c)e.scoped?u+=applyScopedCSS(e.content,l):u+=e.content;return u}export function applyScopedCSS(e,t){return e.replace(/([^{}]+){/g,(e,n)=>{let r=n.trim();return r.startsWith(`@`)?e:`${r.split(`,`).map(e=>`${e.trim()}[${t}]`).join(`, `)} {`})}function r(e){let t=/<style([^>]*)>([\s\S]*?)<\/style>/gi,n=[],r;for(;(r=t.exec(e))!==null;){let e=r[1],t=r[2].trim(),i=e.includes(`scoped`);n.push({content:t,scoped:i,attributes:e})}return n}export function generateScopeId(e){return`data-v-${e.replace(/[^a-zA-Z0-9]/g,``).toLowerCase()}`}export function applyScopeToHTML(e,t){return e.replace(/<([a-zA-Z][^>]*?)>/g,(e,n)=>n.startsWith(`/`)||n.endsWith(`/`)?e:`<${n} ${t}>`)}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createSSRApp as e}from"vue";import{renderToString as t}from"vue/server-renderer";import{extractCSS as n,generateScopeId as r,applyScopeToHTML as i}from"./css-extractor.js";import{toImportSpecifier as a}from"@useavalon/core/utils";import{resolveIslandPath as o}from"@useavalon/avalon/islands/framework-detection";export async function render(a){let{component:o,props:s={},src:l,condition:u=`on:client`,ssrOnly:d=!1}=a;try{let a=await t(e(await c(l),s)),o=``,f=``;try{f=r(l),o=await n(l,{scopeId:f})}catch{}let p=a;return o&&(p=i(a,f)),{html:p,css:o||void 0,scopeId:f||void 0,hydrationData:{src:l,props:s,framework:`vue`,condition:u,ssrOnly:d}}}catch(e){throw Error(`Vue SSR rendering failed: ${e}`)}}async function c(e){if(process.env.NODE_ENV!==`production`&&globalThis.__viteDevServer){let t=globalThis.__viteDevServer,n=await o(e),r=await t.ssrLoadModule(n);return r.default||r}let t=await import(a(e.replace(`/islands/`,`/dist/ssr/islands/`).replace(`.vue`,`.js`)));return t.default||t}export function getComponentMetadata(e){return typeof e==`object`&&e?{name:e.name||`Anonymous`,type:`component`,hasSetup:`setup`in e,hasTemplate:`template`in e,hasRender:`render`in e}:{type:typeof e}}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export{};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/vue",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Vue integration for Avalon islands architecture",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,22 +18,23 @@
|
|
|
18
18
|
"hydration"
|
|
19
19
|
],
|
|
20
20
|
"exports": {
|
|
21
|
-
".": "./mod.
|
|
22
|
-
"./server": "./server/renderer.
|
|
23
|
-
"./client": "./client/index.
|
|
24
|
-
"./client/hmr": "./client/hmr-adapter.
|
|
25
|
-
"./types": "./types.
|
|
21
|
+
".": "./dist/mod.js",
|
|
22
|
+
"./server": "./dist/server/renderer.js",
|
|
23
|
+
"./client": "./dist/client/index.js",
|
|
24
|
+
"./client/hmr": "./dist/client/hmr-adapter.js",
|
|
25
|
+
"./types": "./dist/types.js"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "bun run ../../../scripts/build-package.ts",
|
|
29
|
+
"prepublishOnly": "bun run build"
|
|
26
30
|
},
|
|
27
31
|
"files": [
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"!**/__tests__/**",
|
|
31
|
-
"!**/*.test.ts",
|
|
32
|
-
"!vitest.config.ts",
|
|
32
|
+
"dist/**/*.js",
|
|
33
|
+
"dist/**/*.d.ts",
|
|
33
34
|
"README.md"
|
|
34
35
|
],
|
|
35
36
|
"dependencies": {
|
|
36
|
-
"@useavalon/avalon": "^0.1.
|
|
37
|
+
"@useavalon/avalon": "^0.1.15",
|
|
37
38
|
"@useavalon/core": "^0.1.3",
|
|
38
39
|
"@vitejs/plugin-vue": "^5.2.4"
|
|
39
40
|
},
|
package/client/hmr-adapter.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vue HMR Adapter
|
|
3
|
-
*
|
|
4
|
-
* Provides Hot Module Replacement support for Vue 3 components.
|
|
5
|
-
* Integrates with @vitejs/plugin-vue to preserve reactive state during updates.
|
|
6
|
-
* Uses Vue's __VUE_HMR_RUNTIME__ API for hot updates.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/// <reference lib="dom" />
|
|
10
|
-
|
|
11
|
-
import { BaseFrameworkAdapter, type StateSnapshot } from '@useavalon/avalon/client/hmr';
|
|
12
|
-
|
|
13
|
-
type VueComponent<P = Record<string, unknown>> = VueComponentOptions<P> | ((props: P) => unknown);
|
|
14
|
-
|
|
15
|
-
interface VueComponentOptions<P = Record<string, unknown>> {
|
|
16
|
-
name?: string;
|
|
17
|
-
props?: string[] | Record<string, unknown>;
|
|
18
|
-
data?: () => Record<string, unknown>;
|
|
19
|
-
setup?: (props: P, context: unknown) => unknown;
|
|
20
|
-
render?: () => unknown;
|
|
21
|
-
template?: string;
|
|
22
|
-
components?: Record<string, VueComponent>;
|
|
23
|
-
computed?: Record<string, () => unknown>;
|
|
24
|
-
methods?: Record<string, (...args: unknown[]) => unknown>;
|
|
25
|
-
watch?: Record<string, unknown>;
|
|
26
|
-
emits?: string[] | Record<string, unknown>;
|
|
27
|
-
expose?: string[];
|
|
28
|
-
beforeCreate?: () => void;
|
|
29
|
-
created?: () => void;
|
|
30
|
-
beforeMount?: () => void;
|
|
31
|
-
mounted?: () => void;
|
|
32
|
-
beforeUpdate?: () => void;
|
|
33
|
-
updated?: () => void;
|
|
34
|
-
beforeUnmount?: () => void;
|
|
35
|
-
unmounted?: () => void;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface VueApp {
|
|
39
|
-
mount(rootContainer: HTMLElement | string, isHydrate?: boolean): unknown;
|
|
40
|
-
unmount(): void;
|
|
41
|
-
use(plugin: unknown, ...options: unknown[]): this;
|
|
42
|
-
component(name: string, component: VueComponent): this;
|
|
43
|
-
directive(name: string, directive: unknown): this;
|
|
44
|
-
provide(key: string | symbol, value: unknown): this;
|
|
45
|
-
config: {
|
|
46
|
-
errorHandler?: (err: Error, instance: unknown, info: string) => void;
|
|
47
|
-
warnHandler?: (msg: string, instance: unknown, trace: string) => void;
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface VueModule {
|
|
52
|
-
createApp(rootComponent: VueComponent, rootProps?: Record<string, unknown>): VueApp;
|
|
53
|
-
version: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface VueHMRRuntime {
|
|
57
|
-
createRecord(id: string, component: VueComponent): boolean;
|
|
58
|
-
reload(id: string, component: VueComponent): void;
|
|
59
|
-
rerender(id: string, render: () => unknown): void;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
declare global {
|
|
63
|
-
var __VUE_HMR_RUNTIME__: VueHMRRuntime | undefined;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
interface VueStateSnapshot extends StateSnapshot {
|
|
67
|
-
framework: 'vue';
|
|
68
|
-
data: {
|
|
69
|
-
reactiveData?: Record<string, unknown>;
|
|
70
|
-
componentName?: string;
|
|
71
|
-
capturedProps?: Record<string, unknown>;
|
|
72
|
-
computedValues?: Record<string, unknown>;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export class VueHMRAdapter extends BaseFrameworkAdapter {
|
|
77
|
-
readonly name = 'vue';
|
|
78
|
-
private apps: WeakMap<HTMLElement, VueApp> = new WeakMap();
|
|
79
|
-
private componentIds: WeakMap<HTMLElement, string> = new WeakMap();
|
|
80
|
-
|
|
81
|
-
canHandle(component: unknown): boolean {
|
|
82
|
-
if (!component) return false;
|
|
83
|
-
if (typeof component === 'function') return true;
|
|
84
|
-
if (typeof component !== 'object') return false;
|
|
85
|
-
const obj = component as Record<string, unknown>;
|
|
86
|
-
if (
|
|
87
|
-
'setup' in obj ||
|
|
88
|
-
'data' in obj ||
|
|
89
|
-
'render' in obj ||
|
|
90
|
-
'template' in obj ||
|
|
91
|
-
'props' in obj ||
|
|
92
|
-
'computed' in obj ||
|
|
93
|
-
'methods' in obj ||
|
|
94
|
-
'components' in obj ||
|
|
95
|
-
'emits' in obj ||
|
|
96
|
-
'mounted' in obj ||
|
|
97
|
-
'created' in obj ||
|
|
98
|
-
'beforeMount' in obj ||
|
|
99
|
-
'beforeCreate' in obj
|
|
100
|
-
)
|
|
101
|
-
return true;
|
|
102
|
-
if ('__vccOpts' in obj) return true;
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
override preserveState(island: HTMLElement): VueStateSnapshot | null {
|
|
107
|
-
try {
|
|
108
|
-
const baseSnapshot = super.preserveState(island);
|
|
109
|
-
if (!baseSnapshot) return null;
|
|
110
|
-
const capturedProps = island.dataset.props ? JSON.parse(island.dataset.props) : {};
|
|
111
|
-
const componentName = this.extractComponentName(island.dataset.src || '');
|
|
112
|
-
const reactiveData = this.captureReactiveData(island);
|
|
113
|
-
return { ...baseSnapshot, framework: 'vue', data: { componentName, capturedProps, reactiveData } };
|
|
114
|
-
} catch (error) {
|
|
115
|
-
console.warn('Failed to preserve Vue state:', error);
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async update(island: HTMLElement, newComponent: unknown, props: Record<string, unknown>): Promise<void> {
|
|
121
|
-
if (!this.canHandle(newComponent)) throw new Error('Component is not a valid Vue component');
|
|
122
|
-
const Component = newComponent as VueComponent;
|
|
123
|
-
try {
|
|
124
|
-
const vueModule = (await import('vue')) as VueModule;
|
|
125
|
-
const { createApp } = vueModule;
|
|
126
|
-
const existingApp = this.apps.get(island);
|
|
127
|
-
const componentId = this.componentIds.get(island);
|
|
128
|
-
const hmrRuntime = globalThis.__VUE_HMR_RUNTIME__;
|
|
129
|
-
if (hmrRuntime && componentId) {
|
|
130
|
-
try {
|
|
131
|
-
hmrRuntime.reload(componentId, Component);
|
|
132
|
-
if (existingApp) return;
|
|
133
|
-
} catch (error) {
|
|
134
|
-
console.warn('Vue HMR runtime reload failed, falling back to full remount:', error);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
if (existingApp) {
|
|
138
|
-
try {
|
|
139
|
-
existingApp.unmount();
|
|
140
|
-
} catch (error) {
|
|
141
|
-
console.warn('Failed to unmount existing Vue app:', error);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const app = createApp(Component, props);
|
|
145
|
-
app.config.errorHandler = (err: Error, _instance: unknown, info: string) => {
|
|
146
|
-
console.error('Vue component error during HMR:', err, info);
|
|
147
|
-
};
|
|
148
|
-
app.mount(island, true);
|
|
149
|
-
this.apps.set(island, app);
|
|
150
|
-
const src = island.dataset.src || '';
|
|
151
|
-
const newComponentId = this.generateComponentId(src);
|
|
152
|
-
this.componentIds.set(island, newComponentId);
|
|
153
|
-
if (hmrRuntime) hmrRuntime.createRecord(newComponentId, Component);
|
|
154
|
-
island.dataset.hydrated = 'true';
|
|
155
|
-
island.dataset.hydrationStatus = 'success';
|
|
156
|
-
} catch (error) {
|
|
157
|
-
console.error('Vue HMR update failed:', error);
|
|
158
|
-
island.dataset.hydrationStatus = 'error';
|
|
159
|
-
throw error;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
override restoreState(island: HTMLElement, state: StateSnapshot): void {
|
|
164
|
-
try {
|
|
165
|
-
super.restoreState(island, state);
|
|
166
|
-
} catch (error) {
|
|
167
|
-
console.warn('Failed to restore Vue state:', error);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
override handleError(island: HTMLElement, error: Error): void {
|
|
172
|
-
console.error('Vue HMR error:', error);
|
|
173
|
-
super.handleError(island, error);
|
|
174
|
-
const errorIndicator = island.querySelector('.hmr-error-indicator');
|
|
175
|
-
if (errorIndicator) {
|
|
176
|
-
const msg = error.message;
|
|
177
|
-
let hint = '';
|
|
178
|
-
if (msg.includes('reactive') || msg.includes('ref'))
|
|
179
|
-
hint = ' (Hint: Check reactive state usage - refs must be accessed with .value)';
|
|
180
|
-
else if (msg.includes('render')) hint = ' (Hint: Check component render function or template for errors)';
|
|
181
|
-
else if (msg.includes('hydration') || msg.includes('mismatch'))
|
|
182
|
-
hint = ' (Hint: Server and client render must match)';
|
|
183
|
-
else if (msg.includes('setup'))
|
|
184
|
-
hint = ' (Hint: Check setup function - it should return render function or object)';
|
|
185
|
-
errorIndicator.textContent = `Vue HMR Error: ${msg}${hint}`;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
private extractComponentName(src: string): string {
|
|
190
|
-
const parts = src.split('/');
|
|
191
|
-
const filename = parts.at(-1) ?? '';
|
|
192
|
-
return filename.replace(/\.(vue|tsx?|jsx?)$/, '');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
private generateComponentId(src: string): string {
|
|
196
|
-
return src.replaceAll(/[^a-zA-Z0-9]/g, '_');
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private captureReactiveData(island: HTMLElement): Record<string, unknown> | undefined {
|
|
200
|
-
try {
|
|
201
|
-
const vueInstance = (island as unknown as { __vueParentComponent?: unknown }).__vueParentComponent;
|
|
202
|
-
if (vueInstance && typeof vueInstance === 'object') {
|
|
203
|
-
const data = (vueInstance as { data?: Record<string, unknown> }).data;
|
|
204
|
-
if (data && typeof data === 'object') return { ...data };
|
|
205
|
-
}
|
|
206
|
-
} catch (error) {
|
|
207
|
-
console.debug('Could not capture Vue reactive data:', error);
|
|
208
|
-
}
|
|
209
|
-
return undefined;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
unmount(island: HTMLElement): void {
|
|
213
|
-
const app = this.apps.get(island);
|
|
214
|
-
if (app) {
|
|
215
|
-
try {
|
|
216
|
-
app.unmount();
|
|
217
|
-
this.apps.delete(island);
|
|
218
|
-
this.componentIds.delete(island);
|
|
219
|
-
} catch (error) {
|
|
220
|
-
console.warn('Failed to unmount Vue app:', error);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export const vueAdapter = new VueHMRAdapter();
|
package/client/index.ts
DELETED
package/mod.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vue Integration for Avalon
|
|
3
|
-
*
|
|
4
|
-
* Provides Vue 3 support with SSR, hydration, and scoped CSS extraction.
|
|
5
|
-
* This integration enables Vue Single File Components (.vue) to work
|
|
6
|
-
* seamlessly with Avalon's islands architecture.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { Plugin } from 'vite';
|
|
10
|
-
import type { Integration, IntegrationConfig } from '@useavalon/core/types';
|
|
11
|
-
import { render } from './server/renderer.ts';
|
|
12
|
-
import { getHydrationScript } from './client/hydration.ts';
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Vue integration instance
|
|
16
|
-
*
|
|
17
|
-
* Implements the standard Integration interface for Vue components.
|
|
18
|
-
*/
|
|
19
|
-
export const vueIntegration: Integration = {
|
|
20
|
-
name: 'vue',
|
|
21
|
-
version: '0.1.0',
|
|
22
|
-
|
|
23
|
-
render,
|
|
24
|
-
getHydrationScript,
|
|
25
|
-
|
|
26
|
-
config(): IntegrationConfig {
|
|
27
|
-
return {
|
|
28
|
-
name: 'vue',
|
|
29
|
-
fileExtensions: ['.vue'],
|
|
30
|
-
jsxImportSources: [],
|
|
31
|
-
detectionPatterns: {
|
|
32
|
-
imports: [/^vue$/, /^vue\//, /from\s+['"]vue['"]/],
|
|
33
|
-
content: [/<template>/, /<script.*setup>/, /\bdefineComponent\b/, /\bref\b/, /\breactive\b/, /\bcomputed\b/],
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Provides the @vitejs/plugin-vue Vite plugin with avalon-island custom element configuration.
|
|
40
|
-
* This allows Vue components to work seamlessly with Avalon's islands architecture.
|
|
41
|
-
*/
|
|
42
|
-
async vitePlugin(): Promise<Plugin | Plugin[]> {
|
|
43
|
-
const { default: vue } = await import('@vitejs/plugin-vue');
|
|
44
|
-
return vue({
|
|
45
|
-
template: {
|
|
46
|
-
compilerOptions: {
|
|
47
|
-
// Treat avalon-island as a custom element so Vue doesn't try to resolve it
|
|
48
|
-
isCustomElement: (tag: string) => tag === 'avalon-island',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
// Re-export public API
|
|
56
|
-
export { render } from './server/renderer.ts';
|
|
57
|
-
export { hydrate, getHydrationScript } from './client/hydration.ts';
|
|
58
|
-
export { extractCSS, applyScopedCSS, generateScopeId } from './server/css-extractor.ts';
|
|
59
|
-
export type * from './types.ts';
|
package/server/css-extractor.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vue CSS Extractor
|
|
3
|
-
*
|
|
4
|
-
* Utilities for extracting and processing CSS from Vue Single File Components.
|
|
5
|
-
* Handles both scoped and global styles with proper attribute application.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
9
|
-
import type { CSSExtractionOptions, StyleBlock } from "../types.ts";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Extract CSS from Vue Single File Component
|
|
13
|
-
*
|
|
14
|
-
* Parses <style> blocks from .vue files and applies scoping if needed.
|
|
15
|
-
* Supports both scoped and global styles.
|
|
16
|
-
*
|
|
17
|
-
* @param src - Path to the Vue component file
|
|
18
|
-
* @param options - CSS extraction options
|
|
19
|
-
* @returns Extracted and processed CSS string
|
|
20
|
-
*/
|
|
21
|
-
export async function extractCSS(
|
|
22
|
-
src: string,
|
|
23
|
-
options: CSSExtractionOptions = {},
|
|
24
|
-
) {
|
|
25
|
-
// Try different path variations to find the Vue file
|
|
26
|
-
const pathVariations = [
|
|
27
|
-
// Standard framework paths
|
|
28
|
-
src.startsWith("/") ? `src${src}` : src,
|
|
29
|
-
src.replace("/islands/", "/src/islands/"),
|
|
30
|
-
// Remove leading slash variations
|
|
31
|
-
src.startsWith("/") ? src.substring(1) : src,
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
let vueContent = "";
|
|
35
|
-
|
|
36
|
-
for (const path of pathVariations) {
|
|
37
|
-
try {
|
|
38
|
-
vueContent = await readFile(path, "utf-8");
|
|
39
|
-
break;
|
|
40
|
-
} catch {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!vueContent) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`Vue file not found in any of the attempted paths: ${
|
|
48
|
-
pathVariations.join(", ")
|
|
49
|
-
}`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Extract all style blocks
|
|
54
|
-
const styleBlocks = extractStyleBlocks(vueContent);
|
|
55
|
-
|
|
56
|
-
if (styleBlocks.length === 0) {
|
|
57
|
-
return "";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Generate scope ID if not provided
|
|
61
|
-
const scopeId = options.scopeId || generateScopeId(src);
|
|
62
|
-
|
|
63
|
-
// Process each style block
|
|
64
|
-
let componentCSS = "";
|
|
65
|
-
|
|
66
|
-
for (const block of styleBlocks) {
|
|
67
|
-
if (block.scoped) {
|
|
68
|
-
componentCSS += applyScopedCSS(block.content, scopeId);
|
|
69
|
-
} else {
|
|
70
|
-
componentCSS += block.content;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return componentCSS;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Apply scoped CSS transformation
|
|
79
|
-
*
|
|
80
|
-
* Adds scope attributes to CSS selectors for Vue's scoped styles.
|
|
81
|
-
* Skips at-rules like @media, @keyframes, etc.
|
|
82
|
-
*
|
|
83
|
-
* @param css - CSS content to scope
|
|
84
|
-
* @param scopeId - Scope identifier (e.g., "data-v-abc123")
|
|
85
|
-
* @returns Scoped CSS string
|
|
86
|
-
*/
|
|
87
|
-
export function applyScopedCSS(css: string, scopeId: string) {
|
|
88
|
-
return css.replace(/([^{}]+){/g, (match, selector) => {
|
|
89
|
-
const trimmedSelector = selector.trim();
|
|
90
|
-
|
|
91
|
-
// Skip at-rules (@media, @keyframes, @supports, etc.)
|
|
92
|
-
if (trimmedSelector.startsWith("@")) {
|
|
93
|
-
return match;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Add scope attribute to each selector
|
|
97
|
-
// Handle multiple selectors separated by commas
|
|
98
|
-
const scopedSelectors = trimmedSelector
|
|
99
|
-
.split(",")
|
|
100
|
-
.map((s: string) => `${s.trim()}[${scopeId}]`)
|
|
101
|
-
.join(", ");
|
|
102
|
-
|
|
103
|
-
return `${scopedSelectors} {`;
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Extract style blocks from Vue SFC content
|
|
109
|
-
*
|
|
110
|
-
* Parses <style> tags and extracts their content and attributes.
|
|
111
|
-
*
|
|
112
|
-
* @param vueContent - Vue SFC file content
|
|
113
|
-
* @returns Array of style blocks with metadata
|
|
114
|
-
*/
|
|
115
|
-
function extractStyleBlocks(vueContent: string) {
|
|
116
|
-
const styleRegex = /<style([^>]*)>([\s\S]*?)<\/style>/gi;
|
|
117
|
-
const blocks: StyleBlock[] = [];
|
|
118
|
-
let match;
|
|
119
|
-
|
|
120
|
-
while ((match = styleRegex.exec(vueContent)) !== null) {
|
|
121
|
-
const attributes = match[1];
|
|
122
|
-
const content = match[2].trim();
|
|
123
|
-
const isScoped = attributes.includes("scoped");
|
|
124
|
-
|
|
125
|
-
blocks.push({
|
|
126
|
-
content,
|
|
127
|
-
scoped: isScoped,
|
|
128
|
-
attributes,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return blocks;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Generate a consistent scope ID for a component
|
|
137
|
-
*
|
|
138
|
-
* Creates a deterministic scope ID based on the component path.
|
|
139
|
-
* Format: "data-v-{hash}" where hash is derived from the path.
|
|
140
|
-
*
|
|
141
|
-
* @param src - Component source path
|
|
142
|
-
* @returns Scope ID string
|
|
143
|
-
*/
|
|
144
|
-
export function generateScopeId(src: string) {
|
|
145
|
-
// Remove special characters and convert to lowercase for consistency
|
|
146
|
-
const hash = src.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
147
|
-
return `data-v-${hash}`;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Apply scope attributes to HTML elements
|
|
152
|
-
*
|
|
153
|
-
* Adds scope attributes to HTML tags for matching with scoped CSS.
|
|
154
|
-
* Skips closing tags and self-closing tags.
|
|
155
|
-
*
|
|
156
|
-
* @param html - HTML string to process
|
|
157
|
-
* @param scopeId - Scope identifier
|
|
158
|
-
* @returns HTML with scope attributes
|
|
159
|
-
*/
|
|
160
|
-
export function applyScopeToHTML(html: string, scopeId: string) {
|
|
161
|
-
return html.replace(/<([a-zA-Z][^>]*?)>/g, (match, tagContent) => {
|
|
162
|
-
// Skip closing tags
|
|
163
|
-
if (tagContent.startsWith("/")) {
|
|
164
|
-
return match;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Skip self-closing tags (already have /)
|
|
168
|
-
if (tagContent.endsWith("/")) {
|
|
169
|
-
return match;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Add scope attribute
|
|
173
|
-
return `<${tagContent} ${scopeId}>`;
|
|
174
|
-
});
|
|
175
|
-
}
|
package/server/renderer.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vue Server Renderer
|
|
3
|
-
*
|
|
4
|
-
* Provides server-side rendering capabilities for Vue components.
|
|
5
|
-
* Uses Vue's official SSR API with proper hydration support.
|
|
6
|
-
*
|
|
7
|
-
* Migrated from src/islands/renderers/vue-renderer.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { createSSRApp } from 'vue';
|
|
11
|
-
import { renderToString as vueRenderToString } from 'vue/server-renderer';
|
|
12
|
-
import type { RenderParams, RenderResult } from '@useavalon/core/types';
|
|
13
|
-
import { extractCSS, generateScopeId, applyScopeToHTML } from './css-extractor.ts';
|
|
14
|
-
import { toImportSpecifier } from '@useavalon/core/utils';
|
|
15
|
-
import { resolveIslandPath } from '@useavalon/avalon/islands/framework-detection';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Render a Vue component to HTML string with SSR
|
|
19
|
-
*
|
|
20
|
-
* Creates a Vue SSR app instance and renders it to string.
|
|
21
|
-
* Extracts and applies scoped CSS from the component.
|
|
22
|
-
*
|
|
23
|
-
* Based on Vue.js SSR documentation and Astro's Vue integration:
|
|
24
|
-
* - Creates proper SSR app with createSSRApp
|
|
25
|
-
* - Wraps SSR HTML in a div with data-server-rendered="true"
|
|
26
|
-
* - Uses consistent container structure for client hydration
|
|
27
|
-
*
|
|
28
|
-
* @param params - Render parameters including component, props, and source path
|
|
29
|
-
* @returns Render result with HTML, CSS, and hydration data
|
|
30
|
-
*/
|
|
31
|
-
export async function render(params: RenderParams): Promise<RenderResult> {
|
|
32
|
-
const { component: _component, props = {}, src, condition = 'on:client', ssrOnly = false } = params;
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
const VueComponent = await loadComponent(src);
|
|
36
|
-
|
|
37
|
-
const app = createSSRApp(VueComponent as any, props);
|
|
38
|
-
const ssrHtml = await vueRenderToString(app);
|
|
39
|
-
|
|
40
|
-
let componentCSS = '';
|
|
41
|
-
let scopeId = '';
|
|
42
|
-
|
|
43
|
-
try {
|
|
44
|
-
scopeId = generateScopeId(src);
|
|
45
|
-
componentCSS = await extractCSS(src, { scopeId });
|
|
46
|
-
} catch {
|
|
47
|
-
// CSS extraction failed, continue without CSS
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let finalHtml = ssrHtml;
|
|
51
|
-
if (componentCSS) {
|
|
52
|
-
finalHtml = applyScopeToHTML(ssrHtml, scopeId);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
html: finalHtml,
|
|
57
|
-
css: componentCSS || undefined,
|
|
58
|
-
scopeId: scopeId || undefined,
|
|
59
|
-
hydrationData: { src, props, framework: 'vue', condition, ssrOnly },
|
|
60
|
-
};
|
|
61
|
-
} catch (error) {
|
|
62
|
-
throw new Error(`Vue SSR rendering failed: ${error}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Load a Vue component module
|
|
68
|
-
*
|
|
69
|
-
* Handles both development (via Vite) and production (pre-built) scenarios.
|
|
70
|
-
*
|
|
71
|
-
* @param src - Component source path
|
|
72
|
-
* @returns Vue component module
|
|
73
|
-
*/
|
|
74
|
-
async function loadComponent(src: string) {
|
|
75
|
-
const isDev = process.env.NODE_ENV !== 'production';
|
|
76
|
-
|
|
77
|
-
if (isDev && (globalThis as any).__viteDevServer) {
|
|
78
|
-
// Development: use Vite's SSR module loading
|
|
79
|
-
|
|
80
|
-
const viteServer = (globalThis as any).__viteDevServer;
|
|
81
|
-
const resolvedPath = await resolveIslandPath(src);
|
|
82
|
-
const module = await viteServer.ssrLoadModule(resolvedPath);
|
|
83
|
-
return module.default || module;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Production: load from build output
|
|
87
|
-
const ssrPath = src.replace('/islands/', '/dist/ssr/islands/').replace('.vue', '.js');
|
|
88
|
-
|
|
89
|
-
const module = await import(
|
|
90
|
-
/* @vite-ignore */
|
|
91
|
-
toImportSpecifier(ssrPath)
|
|
92
|
-
);
|
|
93
|
-
return module.default || module;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Get component metadata for debugging
|
|
98
|
-
*
|
|
99
|
-
* @param component - Vue component
|
|
100
|
-
* @returns Component metadata object
|
|
101
|
-
*/
|
|
102
|
-
export function getComponentMetadata(component: unknown) {
|
|
103
|
-
if (typeof component === 'object' && component !== null) {
|
|
104
|
-
return {
|
|
105
|
-
name: (component as { name?: string }).name || 'Anonymous',
|
|
106
|
-
type: 'component',
|
|
107
|
-
hasSetup: 'setup' in component,
|
|
108
|
-
hasTemplate: 'template' in component,
|
|
109
|
-
hasRender: 'render' in component,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
type: typeof component,
|
|
115
|
-
};
|
|
116
|
-
}
|
package/types.ts
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vue Integration Types
|
|
3
|
-
*
|
|
4
|
-
* Type definitions specific to the Vue integration package.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { RenderParams, RenderResult } from '@useavalon/core/types';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Vue-specific render parameters
|
|
11
|
-
*/
|
|
12
|
-
export interface VueRenderParams extends RenderParams {
|
|
13
|
-
/**
|
|
14
|
-
* Vue app context for SSR
|
|
15
|
-
*/
|
|
16
|
-
context?: Map<string, unknown>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Vue-specific render result with CSS extraction
|
|
21
|
-
*/
|
|
22
|
-
export interface VueRenderResult extends RenderResult {
|
|
23
|
-
/**
|
|
24
|
-
* Extracted CSS from Vue SFC <style> blocks
|
|
25
|
-
*/
|
|
26
|
-
css?: string;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Head content (e.g., meta tags, title)
|
|
30
|
-
*/
|
|
31
|
-
head?: string;
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Scope ID for scoped styles
|
|
35
|
-
*/
|
|
36
|
-
scopeId?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Vue component module structure
|
|
41
|
-
*/
|
|
42
|
-
export interface VueComponentModule {
|
|
43
|
-
default?: unknown;
|
|
44
|
-
[key: string]: unknown;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* CSS extraction options
|
|
49
|
-
*/
|
|
50
|
-
export interface CSSExtractionOptions {
|
|
51
|
-
/**
|
|
52
|
-
* Whether to apply scoping to CSS
|
|
53
|
-
*/
|
|
54
|
-
scoped?: boolean;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Custom scope ID (generated if not provided)
|
|
58
|
-
*/
|
|
59
|
-
scopeId?: string;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Style block metadata from Vue SFC
|
|
64
|
-
*/
|
|
65
|
-
export interface StyleBlock {
|
|
66
|
-
/**
|
|
67
|
-
* CSS content
|
|
68
|
-
*/
|
|
69
|
-
content: string;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Whether the style is scoped
|
|
73
|
-
*/
|
|
74
|
-
scoped: boolean;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Style attributes (e.g., lang, scoped)
|
|
78
|
-
*/
|
|
79
|
-
attributes: string;
|
|
80
|
-
}
|