@useavalon/vue 0.1.2 → 0.1.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/README.md +42 -42
- package/client/hmr-adapter.ts +226 -0
- package/client/hydration.ts +124 -124
- package/client/index.ts +7 -7
- package/mod.ts +59 -59
- package/package.json +19 -5
- package/server/css-extractor.ts +175 -175
- package/server/renderer.ts +116 -116
- package/types.ts +80 -80
- package/vitest.config.ts +0 -13
package/mod.ts
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
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';
|
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/vue",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Vue integration for Avalon islands architecture",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,17 +10,31 @@
|
|
|
10
10
|
"directory": "packages/integrations/vue"
|
|
11
11
|
},
|
|
12
12
|
"homepage": "https://useavalon.dev/docs/frameworks/vue",
|
|
13
|
-
"keywords": [
|
|
13
|
+
"keywords": [
|
|
14
|
+
"avalon",
|
|
15
|
+
"islands",
|
|
16
|
+
"vue",
|
|
17
|
+
"ssr",
|
|
18
|
+
"hydration"
|
|
19
|
+
],
|
|
14
20
|
"exports": {
|
|
15
21
|
".": "./mod.ts",
|
|
16
22
|
"./server": "./server/renderer.ts",
|
|
17
23
|
"./client": "./client/index.ts",
|
|
24
|
+
"./client/hmr": "./client/hmr-adapter.ts",
|
|
18
25
|
"./types": "./types.ts"
|
|
19
26
|
},
|
|
20
|
-
"files": [
|
|
27
|
+
"files": [
|
|
28
|
+
"**/*.ts",
|
|
29
|
+
"**/*.tsx",
|
|
30
|
+
"!**/__tests__/**",
|
|
31
|
+
"!**/*.test.ts",
|
|
32
|
+
"!vitest.config.ts",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
21
35
|
"dependencies": {
|
|
22
|
-
"@useavalon/avalon": "^0.1.
|
|
23
|
-
"@useavalon/core": "^0.1.
|
|
36
|
+
"@useavalon/avalon": "^0.1.12",
|
|
37
|
+
"@useavalon/core": "^0.1.3",
|
|
24
38
|
"@vitejs/plugin-vue": "^5.2.4"
|
|
25
39
|
},
|
|
26
40
|
"peerDependencies": {
|
package/server/css-extractor.ts
CHANGED
|
@@ -1,175 +1,175 @@
|
|
|
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
|
-
}
|
|
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
|
+
}
|