@useavalon/vue 0.1.0 → 0.1.2
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 +1 -1
- package/package.json +8 -4
- package/server/renderer.ts +3 -3
- package/types.ts +1 -1
- package/__tests__/config.test.ts +0 -90
- package/__tests__/css-extractor.test.ts +0 -119
package/mod.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { Plugin } from 'vite';
|
|
10
|
-
import type { Integration, IntegrationConfig } from '
|
|
10
|
+
import type { Integration, IntegrationConfig } from '@useavalon/core/types';
|
|
11
11
|
import { render } from './server/renderer.ts';
|
|
12
12
|
import { getHydrationScript } from './client/hydration.ts';
|
|
13
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useavalon/vue",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Vue integration for Avalon islands architecture",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,10 +17,14 @@
|
|
|
17
17
|
"./client": "./client/index.ts",
|
|
18
18
|
"./types": "./types.ts"
|
|
19
19
|
},
|
|
20
|
-
"files": ["**/*.ts", "**/*.tsx", "README.md"],
|
|
20
|
+
"files": ["**/*.ts", "**/*.tsx", "!**/__tests__/**", "!**/*.test.ts", "README.md"],
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
22
|
+
"@useavalon/avalon": "^0.1.6",
|
|
23
|
+
"@useavalon/core": "^0.1.1",
|
|
24
24
|
"@vitejs/plugin-vue": "^5.2.4"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"vue": "^3.4.0",
|
|
28
|
+
"vite": "^8.0.0"
|
|
25
29
|
}
|
|
26
30
|
}
|
package/server/renderer.ts
CHANGED
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import { createSSRApp } from 'vue';
|
|
11
11
|
import { renderToString as vueRenderToString } from 'vue/server-renderer';
|
|
12
|
-
import type { RenderParams, RenderResult } from '
|
|
12
|
+
import type { RenderParams, RenderResult } from '@useavalon/core/types';
|
|
13
13
|
import { extractCSS, generateScopeId, applyScopeToHTML } from './css-extractor.ts';
|
|
14
|
-
import { toImportSpecifier } from '
|
|
15
|
-
import { resolveIslandPath } from '
|
|
14
|
+
import { toImportSpecifier } from '@useavalon/core/utils';
|
|
15
|
+
import { resolveIslandPath } from '@useavalon/avalon/islands/framework-detection';
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Render a Vue component to HTML string with SSR
|
package/types.ts
CHANGED
package/__tests__/config.test.ts
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { vueIntegration } from '../mod.ts';
|
|
3
|
-
|
|
4
|
-
describe('vueIntegration.config()', () => {
|
|
5
|
-
const cfg = vueIntegration.config();
|
|
6
|
-
|
|
7
|
-
it('returns "vue" as the integration name', () => {
|
|
8
|
-
expect(cfg.name).toBe('vue');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('declares .vue file extension', () => {
|
|
12
|
-
expect(cfg.fileExtensions).toEqual(['.vue']);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
describe('detection patterns - imports', () => {
|
|
16
|
-
const { imports } = cfg.detectionPatterns;
|
|
17
|
-
|
|
18
|
-
it('matches bare "vue" import', () => {
|
|
19
|
-
expect(imports.some((r) => r.test('vue'))).toBe(true);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('matches "vue/" subpath import', () => {
|
|
23
|
-
expect(imports.some((r) => r.test('vue/server-renderer'))).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('matches from "vue" import statement', () => {
|
|
27
|
-
const code = "import { ref } from 'vue'";
|
|
28
|
-
expect(imports.some((r) => r.test(code))).toBe(true);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('does not match unrelated imports', () => {
|
|
32
|
-
const code = "import { render } from 'react'";
|
|
33
|
-
expect(imports.some((r) => r.test(code))).toBe(false);
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('detection patterns - content', () => {
|
|
38
|
-
const { content } = cfg.detectionPatterns;
|
|
39
|
-
|
|
40
|
-
it('matches <template> tag', () => {
|
|
41
|
-
expect(content.some((r) => r.test('<template>'))).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('matches <script setup> tag', () => {
|
|
45
|
-
expect(content.some((r) => r.test('<script setup>'))).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('matches <script lang="ts" setup> tag', () => {
|
|
49
|
-
expect(content.some((r) => r.test('<script lang="ts" setup>'))).toBe(true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('matches defineComponent API', () => {
|
|
53
|
-
expect(content.some((r) => r.test('defineComponent({ })'))).toBe(true);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('matches ref API', () => {
|
|
57
|
-
expect(content.some((r) => r.test('const count = ref(0)'))).toBe(true);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
it('matches reactive API', () => {
|
|
61
|
-
expect(content.some((r) => r.test('const state = reactive({})'))).toBe(true);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('matches computed API', () => {
|
|
65
|
-
expect(content.some((r) => r.test('const doubled = computed(() => count.value * 2)'))).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it('does not match unrelated content', () => {
|
|
69
|
-
expect(content.some((r) => r.test('const x = 42'))).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('vueIntegration.getHydrationScript()', () => {
|
|
75
|
-
const script = vueIntegration.getHydrationScript();
|
|
76
|
-
|
|
77
|
-
it('returns a non-empty string', () => {
|
|
78
|
-
expect(script).toBeTruthy();
|
|
79
|
-
expect(typeof script).toBe('string');
|
|
80
|
-
expect(script.length).toBeGreaterThan(0);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('contains a query selector for data-framework="vue"', () => {
|
|
84
|
-
expect(script).toContain('data-framework="vue"');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('contains dynamic import logic', () => {
|
|
88
|
-
expect(script).toContain('import(');
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { applyScopedCSS, applyScopeToHTML, generateScopeId } from '../server/css-extractor.ts';
|
|
3
|
-
|
|
4
|
-
describe('applyScopedCSS', () => {
|
|
5
|
-
it('appends scope attribute to a simple selector', () => {
|
|
6
|
-
const css = '.foo { color: red; }';
|
|
7
|
-
const result = applyScopedCSS(css, 'data-v-abc123');
|
|
8
|
-
expect(result).toContain('.foo[data-v-abc123]');
|
|
9
|
-
expect(result).toContain('color: red;');
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('scopes multiple comma-separated selectors', () => {
|
|
13
|
-
const css = '.foo, .bar { margin: 0; }';
|
|
14
|
-
const result = applyScopedCSS(css, 'data-v-abc123');
|
|
15
|
-
expect(result).toContain('.foo[data-v-abc123]');
|
|
16
|
-
expect(result).toContain('.bar[data-v-abc123]');
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('scopes element selectors', () => {
|
|
20
|
-
const css = 'h1 { font-size: 2em; }';
|
|
21
|
-
const result = applyScopedCSS(css, 'data-v-xyz');
|
|
22
|
-
expect(result).toContain('h1[data-v-xyz]');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('scopes compound selectors', () => {
|
|
26
|
-
const css = '.parent .child { display: flex; }';
|
|
27
|
-
const result = applyScopedCSS(css, 'data-v-s1');
|
|
28
|
-
expect(result).toContain('.parent .child[data-v-s1]');
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('skips @media at-rules', () => {
|
|
32
|
-
const css = '@media (max-width: 600px) { .foo { color: red; } }';
|
|
33
|
-
const result = applyScopedCSS(css, 'data-v-abc');
|
|
34
|
-
// The @media rule itself should not be scoped
|
|
35
|
-
expect(result).toMatch(/@media\s*\(max-width:\s*600px\)/);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('skips @keyframes at-rules', () => {
|
|
39
|
-
const css = '@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }';
|
|
40
|
-
const result = applyScopedCSS(css, 'data-v-abc');
|
|
41
|
-
expect(result).toContain('@keyframes fadeIn');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('skips @supports at-rules', () => {
|
|
45
|
-
const css = '@supports (display: grid) { .grid { display: grid; } }';
|
|
46
|
-
const result = applyScopedCSS(css, 'data-v-abc');
|
|
47
|
-
expect(result).toMatch(/@supports\s*\(display:\s*grid\)/);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('returns empty string for empty CSS', () => {
|
|
51
|
-
expect(applyScopedCSS('', 'data-v-abc')).toBe('');
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('applyScopeToHTML', () => {
|
|
56
|
-
it('adds scope attribute to opening tags', () => {
|
|
57
|
-
const html = '<div class="wrapper"><span>hello</span></div>';
|
|
58
|
-
const result = applyScopeToHTML(html, 'data-v-abc');
|
|
59
|
-
expect(result).toContain('<div class="wrapper" data-v-abc>');
|
|
60
|
-
expect(result).toContain('<span data-v-abc>');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it('leaves closing tags unmodified', () => {
|
|
64
|
-
const html = '<div>text</div>';
|
|
65
|
-
const result = applyScopeToHTML(html, 'data-v-abc');
|
|
66
|
-
expect(result).toContain('</div>');
|
|
67
|
-
// Closing tag should not have the scope attribute
|
|
68
|
-
expect(result).not.toContain('</div data-v-abc>');
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('leaves self-closing tags unmodified', () => {
|
|
72
|
-
const html = '<img src="a.png"/>';
|
|
73
|
-
const result = applyScopeToHTML(html, 'data-v-abc');
|
|
74
|
-
// Self-closing tags ending with / are skipped by the implementation
|
|
75
|
-
expect(result).toContain('<img src="a.png"/>');
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('handles multiple opening tags', () => {
|
|
79
|
-
const html = '<ul><li>one</li><li>two</li></ul>';
|
|
80
|
-
const result = applyScopeToHTML(html, 'data-v-s1');
|
|
81
|
-
expect(result).toContain('<ul data-v-s1>');
|
|
82
|
-
expect(result).toContain('<li data-v-s1>');
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('returns empty string for empty HTML', () => {
|
|
86
|
-
expect(applyScopeToHTML('', 'data-v-abc')).toBe('');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('handles tags with attributes', () => {
|
|
90
|
-
const html = '<a href="/link" class="btn">click</a>';
|
|
91
|
-
const result = applyScopeToHTML(html, 'data-v-abc');
|
|
92
|
-
expect(result).toContain('<a href="/link" class="btn" data-v-abc>');
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
describe('generateScopeId', () => {
|
|
97
|
-
it('returns a string starting with "data-v-"', () => {
|
|
98
|
-
const id = generateScopeId('/components/Foo.vue');
|
|
99
|
-
expect(id).toMatch(/^data-v-/);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('returns deterministic output for the same input', () => {
|
|
103
|
-
const a = generateScopeId('/components/Foo.vue');
|
|
104
|
-
const b = generateScopeId('/components/Foo.vue');
|
|
105
|
-
expect(a).toBe(b);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('returns different IDs for different paths', () => {
|
|
109
|
-
const a = generateScopeId('/components/Foo.vue');
|
|
110
|
-
const b = generateScopeId('/components/Bar.vue');
|
|
111
|
-
expect(a).not.toBe(b);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('produces alphanumeric hash portion', () => {
|
|
115
|
-
const id = generateScopeId('/src/App.vue');
|
|
116
|
-
const hash = id.replace('data-v-', '');
|
|
117
|
-
expect(hash).toMatch(/^[a-z0-9]+$/);
|
|
118
|
-
});
|
|
119
|
-
});
|