@useavalon/vue 0.1.1 → 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/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.1",
3
+ "version": "0.1.3",
4
4
  "description": "Vue integration for Avalon islands architecture",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,19 +10,35 @@
10
10
  "directory": "packages/integrations/vue"
11
11
  },
12
12
  "homepage": "https://useavalon.dev/docs/frameworks/vue",
13
- "keywords": ["avalon", "islands", "vue", "ssr", "hydration"],
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": ["**/*.ts", "**/*.tsx", "!**/__tests__/**", "!**/*.test.ts", "README.md"],
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.6",
23
- "@useavalon/core": "^0.1.1",
24
- "vue": "3.5.21",
25
- "vite": "8.0.0",
36
+ "@useavalon/avalon": "^0.1.12",
37
+ "@useavalon/core": "^0.1.3",
26
38
  "@vitejs/plugin-vue": "^5.2.4"
39
+ },
40
+ "peerDependencies": {
41
+ "vue": "^3.4.0",
42
+ "vite": "^8.0.0"
27
43
  }
28
44
  }
@@ -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
+ }