oc-template-vue-compiler 4.0.1 → 4.0.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/package.json +2 -2
- package/scaffold/src/.cursor/rules/oc-project.mdc +1 -1
- package/scaffold/src/package.json +10 -11
- package/scaffold/src/src/App.test.ts +4 -7
- package/scaffold/src/src/App.vue +121 -40
- package/scaffold/src/src/server.ts +25 -60
- package/scaffold/src/vite.config.ts +5 -7
- package/scaffold/src/vitest.config.ts +13 -0
- package/scaffold/src/src/styles.css +0 -30
- package/scaffold/src/src/types.ts +0 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oc-template-vue-compiler",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"description": "Compiler for the vueJS OC template",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@vitejs/plugin-vue": "^4.5.2",
|
|
32
32
|
"oc-template-vue": "^0.1.0",
|
|
33
|
-
"oc-vite-compiler": "^6.0.
|
|
33
|
+
"oc-vite-compiler": "^6.0.2"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"dist/**",
|
|
@@ -56,7 +56,7 @@ OpenComponents is a microfrontend technology that resembles a Vite project but w
|
|
|
56
56
|
|
|
57
57
|
### Parameters vs Actions
|
|
58
58
|
|
|
59
|
-
- **Parameters**: Initial props passed via URL query string (like `?
|
|
59
|
+
- **Parameters**: Initial props passed via URL query string (like `?name=Ada&year=1986`)
|
|
60
60
|
- **Actions**: Server endpoints that the client can call after initial render
|
|
61
61
|
|
|
62
62
|
### Streaming Mode Benefits
|
|
@@ -22,18 +22,17 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@testing-library/jest-dom": "^
|
|
25
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
26
26
|
"@testing-library/vue": "^8.0.0",
|
|
27
|
-
"@
|
|
28
|
-
"@
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"oc-
|
|
32
|
-
"
|
|
33
|
-
"typescript": "5.8.2",
|
|
27
|
+
"@vitejs/plugin-vue": "^6.0.10",
|
|
28
|
+
"@vue/tsconfig": "^0.9.1",
|
|
29
|
+
"jsdom": "^30.1.2",
|
|
30
|
+
"oc-server": "^4.0.3",
|
|
31
|
+
"oc-template-vue-compiler": "^4.0.3",
|
|
32
|
+
"typescript": "5.9.3",
|
|
34
33
|
"vite": "^7.1.12",
|
|
35
|
-
"vitest": "^0.
|
|
36
|
-
"vue": "^3.5.
|
|
37
|
-
"vue-tsc": "^
|
|
34
|
+
"vitest": "^5.0.3",
|
|
35
|
+
"vue": "^3.5.43",
|
|
36
|
+
"vue-tsc": "^3.3.12"
|
|
38
37
|
}
|
|
39
38
|
}
|
|
@@ -19,17 +19,14 @@ describe('App - Page', () => {
|
|
|
19
19
|
getSetting.mockImplementationOnce(() => ({}));
|
|
20
20
|
render(App, {
|
|
21
21
|
props: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
born: 1991,
|
|
25
|
-
hobbies: [],
|
|
22
|
+
name: 'firstName',
|
|
23
|
+
year: 1991,
|
|
26
24
|
},
|
|
27
25
|
global: {
|
|
28
26
|
provide: {
|
|
29
27
|
dataProvider: {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
userId: 0,
|
|
28
|
+
name: 'firstName',
|
|
29
|
+
year: 1991,
|
|
33
30
|
getData,
|
|
34
31
|
getSetting,
|
|
35
32
|
},
|
package/scaffold/src/src/App.vue
CHANGED
|
@@ -1,73 +1,154 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref } from 'vue'
|
|
3
3
|
import logo from '../public/logo.png';
|
|
4
|
-
import { serverClient, ActionOutput } from 'oc-server/client'
|
|
5
|
-
|
|
4
|
+
import { serverClient, type ActionOutput } from 'oc-server/client'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{ name: string, year: number }>()
|
|
7
|
+
|
|
8
|
+
const fact = ref<ActionOutput<'funFact'> | null>(null);
|
|
9
|
+
const loading = ref(false);
|
|
10
|
+
const error = ref('');
|
|
6
11
|
|
|
7
|
-
const additionalData = ref<ActionOutput<'funFact'> | null>(null);
|
|
8
12
|
async function getFunFact() {
|
|
9
|
-
|
|
13
|
+
loading.value = true;
|
|
14
|
+
error.value = '';
|
|
15
|
+
try {
|
|
16
|
+
fact.value = await serverClient.funFact({ year: props.year });
|
|
17
|
+
} catch (err) {
|
|
18
|
+
error.value = String(err);
|
|
19
|
+
} finally {
|
|
20
|
+
loading.value = false;
|
|
21
|
+
}
|
|
10
22
|
}
|
|
11
|
-
const hobbies = props.hobbies.map((x) => x.toLowerCase()).join(', ')
|
|
12
23
|
</script>
|
|
13
24
|
|
|
14
25
|
<template>
|
|
15
|
-
<div
|
|
16
|
-
<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</h1>
|
|
21
|
-
<div className="info">
|
|
22
|
-
<div className="block">Born: {{ born }}</div>
|
|
23
|
-
<div className="block">
|
|
24
|
-
Hobbies: {{ hobbies }}
|
|
26
|
+
<div class="container">
|
|
27
|
+
<div class="card">
|
|
28
|
+
<div class="brand">
|
|
29
|
+
<img class="logo" width="40" height="40" v-bind:src="logo" alt="Logo" />
|
|
30
|
+
<p class="eyebrow">OpenComponents · Vite</p>
|
|
25
31
|
</div>
|
|
32
|
+
<h1 class="title">Hello, <span class="name">{{ name }}</span></h1>
|
|
33
|
+
<p class="subtitle">Born in <span class="pill">{{ year }}</span></p>
|
|
34
|
+
<div v-if="fact" class="fact">{{ fact.funFact }}</div>
|
|
35
|
+
<div v-if="error" class="error">{{ error }}</div>
|
|
36
|
+
<button class="button" @click="getFunFact" :disabled="loading">
|
|
37
|
+
{{ loading ? 'Loading…' : 'Fun year fact' }}
|
|
38
|
+
</button>
|
|
26
39
|
</div>
|
|
27
|
-
<div v-if="additionalData">{{ additionalData?.funFact }}</div>
|
|
28
|
-
<button className="button" @click="getFunFact">
|
|
29
|
-
Fun year fact
|
|
30
|
-
</button>
|
|
31
40
|
</div>
|
|
32
41
|
</template>
|
|
33
42
|
|
|
34
43
|
<style scoped>
|
|
35
44
|
.container {
|
|
36
|
-
background-color: #
|
|
45
|
+
background-color: #0f172a;
|
|
37
46
|
color: #fff;
|
|
38
|
-
font-family:
|
|
39
|
-
|
|
47
|
+
font-family: 'DejaVu Sans', system-ui, -apple-system, 'Segoe UI', Roboto,
|
|
48
|
+
sans-serif;
|
|
49
|
+
padding: 56px 24px;
|
|
40
50
|
}
|
|
41
51
|
|
|
42
|
-
.
|
|
43
|
-
background-color: #
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
.card {
|
|
53
|
+
background-color: #ffffff;
|
|
54
|
+
color: #0f172a;
|
|
55
|
+
border-radius: 20px;
|
|
56
|
+
padding: 32px;
|
|
57
|
+
max-width: 440px;
|
|
58
|
+
margin: 0 auto;
|
|
59
|
+
box-shadow: 0 12px 32px rgba(2, 6, 23, 0.45);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.brand {
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
gap: 12px;
|
|
66
|
+
margin-bottom: 20px;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.logo {
|
|
70
|
+
width: 40px;
|
|
71
|
+
height: 40px;
|
|
72
|
+
border-radius: 12px;
|
|
73
|
+
display: block;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.eyebrow {
|
|
77
|
+
margin: 0;
|
|
78
|
+
font-size: 12px;
|
|
79
|
+
font-weight: 700;
|
|
80
|
+
letter-spacing: 0.14em;
|
|
81
|
+
text-transform: uppercase;
|
|
82
|
+
color: #4f46e5;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.title {
|
|
86
|
+
margin: 0 0 8px 0;
|
|
87
|
+
font-size: 30px;
|
|
88
|
+
line-height: 1.15;
|
|
89
|
+
letter-spacing: -0.02em;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.name {
|
|
93
|
+
color: #4f46e5;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.subtitle {
|
|
97
|
+
margin: 0 0 4px 0;
|
|
47
98
|
font-size: 16px;
|
|
48
|
-
|
|
99
|
+
color: #475569;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.pill {
|
|
49
103
|
display: inline-block;
|
|
50
|
-
color:
|
|
51
|
-
|
|
104
|
+
background-color: #eef2ff;
|
|
105
|
+
color: #4338ca;
|
|
106
|
+
border-radius: 999px;
|
|
107
|
+
padding: 2px 12px;
|
|
108
|
+
font-weight: 700;
|
|
52
109
|
}
|
|
53
110
|
|
|
54
|
-
.button
|
|
55
|
-
|
|
111
|
+
.button {
|
|
112
|
+
margin-top: 20px;
|
|
113
|
+
width: 100%;
|
|
114
|
+
background-color: #4f46e5;
|
|
115
|
+
color: #fff;
|
|
116
|
+
border: none;
|
|
117
|
+
border-radius: 12px;
|
|
118
|
+
padding: 12px 24px;
|
|
119
|
+
font-size: 15px;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
cursor: pointer;
|
|
56
122
|
}
|
|
57
123
|
|
|
58
|
-
|
|
59
|
-
|
|
124
|
+
.button:hover {
|
|
125
|
+
background-color: #4338ca;
|
|
60
126
|
}
|
|
61
127
|
|
|
62
|
-
|
|
63
|
-
|
|
128
|
+
.button:disabled {
|
|
129
|
+
opacity: 0.6;
|
|
130
|
+
cursor: default;
|
|
64
131
|
}
|
|
65
132
|
|
|
66
|
-
.
|
|
67
|
-
margin-
|
|
133
|
+
.fact {
|
|
134
|
+
margin-top: 20px;
|
|
135
|
+
background-color: #eef2ff;
|
|
136
|
+
border-left: 4px solid #4f46e5;
|
|
137
|
+
border-radius: 0 12px 12px 0;
|
|
138
|
+
padding: 14px 16px;
|
|
139
|
+
font-size: 15px;
|
|
140
|
+
line-height: 1.5;
|
|
141
|
+
color: #1e1b4b;
|
|
68
142
|
}
|
|
69
143
|
|
|
70
|
-
.
|
|
71
|
-
margin:
|
|
144
|
+
.error {
|
|
145
|
+
margin-top: 20px;
|
|
146
|
+
background-color: #fef2f2;
|
|
147
|
+
border-left: 4px solid #ef4444;
|
|
148
|
+
border-radius: 0 12px 12px 0;
|
|
149
|
+
padding: 14px 16px;
|
|
150
|
+
font-size: 15px;
|
|
151
|
+
line-height: 1.5;
|
|
152
|
+
color: #991b1b;
|
|
72
153
|
}
|
|
73
154
|
</style>
|
|
@@ -1,82 +1,47 @@
|
|
|
1
1
|
import { Server } from 'oc-server';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const yearsFunFactDatabase: Record<number, string> = {
|
|
9
|
-
1986: "Halley's Comet made its closest approach to Earth in 1986, visible to the naked eye.",
|
|
10
|
-
1987: 'The first Final Fantasy game was released in 1987.',
|
|
3
|
+
// Small lookup for the action below. Kept tiny on purpose: it only shows
|
|
4
|
+
// that an action receives input and returns data.
|
|
5
|
+
const funFacts: Record<number, string> = {
|
|
6
|
+
1986: "Halley's Comet made its closest approach to Earth in 1986.",
|
|
11
7
|
1991: 'The first web page was created in 1991 by Tim Berners-Lee.',
|
|
12
8
|
};
|
|
13
9
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
throw new Error(`User with ID ${userId} not found`);
|
|
17
|
-
}
|
|
18
|
-
return userDatabase[userId];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function getFunFact(year: number) {
|
|
22
|
-
return yearsFunFactDatabase[year] || `No fun fact available for year ${year}`;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Export the server instance - this is required for OC
|
|
10
|
+
// Tip: `new Server({ stream: true })` lets handlers and actions return
|
|
11
|
+
// Promises that resolve on the client instead of blocking the render.
|
|
26
12
|
export const server = new Server({
|
|
27
13
|
development: {
|
|
28
|
-
//
|
|
14
|
+
// Stream browser console logs to the terminal during `npm start`
|
|
29
15
|
console: true,
|
|
30
16
|
},
|
|
31
17
|
})
|
|
32
|
-
//
|
|
18
|
+
// Parameters become URL query strings, e.g. `?name=Ada&year=1986`
|
|
33
19
|
.withParameters({
|
|
34
|
-
|
|
35
|
-
default:
|
|
36
|
-
description: '
|
|
37
|
-
example:
|
|
38
|
-
|
|
20
|
+
name: {
|
|
21
|
+
default: 'Jane Doe',
|
|
22
|
+
description: 'Who to greet',
|
|
23
|
+
example: 'Jane Doe',
|
|
24
|
+
type: 'string',
|
|
25
|
+
},
|
|
26
|
+
year: {
|
|
27
|
+
default: 1991,
|
|
28
|
+
description: 'Birth year used for the fun fact',
|
|
29
|
+
example: 1991,
|
|
39
30
|
type: 'number',
|
|
40
31
|
},
|
|
41
32
|
})
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const { userId } = params;
|
|
46
|
-
const user = await getUser(userId);
|
|
47
|
-
const [firstName, lastName] = user.name.split(/\s+/);
|
|
48
|
-
|
|
49
|
-
if (firstName === 'Invalid') {
|
|
50
|
-
// Return undefined to prevent rendering
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Set cache headers when same params produce same result
|
|
55
|
-
// This improves performance by reducing server load
|
|
56
|
-
// Don't cache in local environment because we might get render mismatches
|
|
57
|
-
if (ctx.env.name !== 'local') {
|
|
58
|
-
ctx.setHeader('Cache-Control', 'max-age=300'); // Cache for 5 minutes
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Return data that will be passed as props to React component
|
|
62
|
-
return {
|
|
63
|
-
firstName,
|
|
64
|
-
lastName,
|
|
65
|
-
born: user.born,
|
|
66
|
-
hobbies: user.hobbies,
|
|
67
|
-
};
|
|
33
|
+
// Runs once per render. Its return value becomes the UI props.
|
|
34
|
+
.handler(async ({ name = 'Jane Doe', year = 1991 }) => {
|
|
35
|
+
return { name: name.trim() || 'Jane Doe', year };
|
|
68
36
|
})
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
.action('funFact', async (params: { year: number }) => {
|
|
72
|
-
const { year } = params;
|
|
73
|
-
const funFact = await getFunFact(year);
|
|
37
|
+
// Runs on demand from the browser via `serverClient.funFact({ year })`
|
|
38
|
+
.action('funFact', async ({ year }: { year: number }) => {
|
|
74
39
|
return {
|
|
75
|
-
funFact
|
|
40
|
+
funFact: funFacts[year] ?? `No fun fact available for year ${year}`,
|
|
76
41
|
};
|
|
77
42
|
});
|
|
78
43
|
|
|
79
|
-
//
|
|
44
|
+
// Enables type-safe props and actions on the client
|
|
80
45
|
declare module 'oc-server' {
|
|
81
46
|
interface Register {
|
|
82
47
|
server: typeof server;
|
|
@@ -2,12 +2,10 @@ import { defineConfig } from 'vite';
|
|
|
2
2
|
|
|
3
3
|
// https://vitejs.dev/config/
|
|
4
4
|
export default defineConfig({
|
|
5
|
-
// Add your own plugins here for
|
|
5
|
+
// Add your own plugins here for things like eslint or typecheckers
|
|
6
|
+
// NOTE: do not add plugins here. oc loads this file into the component
|
|
7
|
+
// packaging build, where template plugins break compilation. Test-only
|
|
8
|
+
// plugins (e.g. @vitejs/plugin-vue for .vue support under vitest) belong
|
|
9
|
+
// in vitest.config.ts instead.
|
|
6
10
|
plugins: [],
|
|
7
|
-
// @ts-ignore Missing test property
|
|
8
|
-
test: {
|
|
9
|
-
environment: 'jsdom',
|
|
10
|
-
globals: true,
|
|
11
|
-
setupFiles: ['src/setupTests.js'],
|
|
12
|
-
}
|
|
13
11
|
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import vue from '@vitejs/plugin-vue';
|
|
3
|
+
|
|
4
|
+
// Vitest-only config. The packaging build reads vite.config.ts (which must
|
|
5
|
+
// stay plugin-free), so .vue support for unit tests lives here.
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [vue()],
|
|
8
|
+
test: {
|
|
9
|
+
environment: 'jsdom',
|
|
10
|
+
globals: true,
|
|
11
|
+
setupFiles: ['src/setupTests.js'],
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
.container {
|
|
2
|
-
background-color: #3b246c;
|
|
3
|
-
color: #fff;
|
|
4
|
-
font-family: sans-serif;
|
|
5
|
-
padding: 40px;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.button {
|
|
9
|
-
background-color: #a97613;
|
|
10
|
-
border: none;
|
|
11
|
-
padding: 15px 32px;
|
|
12
|
-
text-align: center;
|
|
13
|
-
font-size: 16px;
|
|
14
|
-
text-decoration: none;
|
|
15
|
-
display: inline-block;
|
|
16
|
-
color: inherit;
|
|
17
|
-
cursor: pointer;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.info {
|
|
21
|
-
margin-bottom: 20px;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
.block {
|
|
25
|
-
margin: 6px 0;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.button:hover {
|
|
29
|
-
background-color: #c79535;
|
|
30
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export interface OcParameters {
|
|
2
|
-
userId: number;
|
|
3
|
-
getMoreData?: boolean;
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export interface AdditionalData {
|
|
7
|
-
hobbies: string[];
|
|
8
|
-
age: number;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ClientProps extends Partial<AdditionalData> {
|
|
12
|
-
userId: number;
|
|
13
|
-
firstName: string;
|
|
14
|
-
lastName: string;
|
|
15
|
-
}
|