oc-template-svelte-compiler 3.0.0 → 3.0.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.
@@ -18,13 +18,13 @@ function svelteOCProviderTemplate({ viewPath, providerFunctions }) {
18
18
  target: element,
19
19
  props: rest
20
20
  });
21
- }
22
21
 
23
- element.parentElement.unmount = () => {
24
- if (app) {
25
- unmount(app);
26
- app = null;
27
- }
22
+ element.parentElement.unmount = () => {
23
+ if (app) {
24
+ unmount(app);
25
+ app = null;
26
+ }
27
+ };
28
28
  }
29
29
 
30
30
  renderer.component = View;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-template-svelte-compiler",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Compiler for the Svelte OC template",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -28,9 +28,9 @@
28
28
  },
29
29
  "license": "MIT",
30
30
  "dependencies": {
31
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
31
+ "@sveltejs/vite-plugin-svelte": "^6.0.0",
32
32
  "oc-template-svelte": "^0.0.2",
33
- "oc-vite-compiler": "^6.0.0",
33
+ "oc-vite-compiler": "^6.0.2",
34
34
  "svelte": "^5.23.0"
35
35
  },
36
36
  "files": [
@@ -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 `?userId=1`)
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
@@ -23,17 +23,16 @@
23
23
  }
24
24
  },
25
25
  "devDependencies": {
26
- "@sveltejs/vite-plugin-svelte": "^5.0.3",
27
- "@testing-library/jest-dom": "^5.16.5",
26
+ "@sveltejs/vite-plugin-svelte": "^6.0.0",
27
+ "@testing-library/jest-dom": "^7.0.1",
28
28
  "@tsconfig/svelte": "^5.0.4",
29
- "@types/testing-library__jest-dom": "^5.14.5",
30
- "jsdom": "^21.1.1",
31
- "oc-server": "^4.0.0",
32
- "oc-template-svelte-compiler": "^3.0.0",
33
- "svelte": "^5.23.0",
29
+ "jsdom": "^30.1.2",
30
+ "oc-server": "^4.0.3",
31
+ "oc-template-svelte-compiler": "^3.0.2",
32
+ "svelte": "^5.57.2",
34
33
  "svelte-check": "^4.0.0",
35
- "typescript": "5.0.2",
34
+ "typescript": "5.9.3",
36
35
  "vite": "^7.1.12",
37
- "vitest": "^0.29.7"
36
+ "vitest": "^5.0.3"
38
37
  }
39
38
  }
@@ -2,70 +2,153 @@
2
2
  import logo from '../public/logo.png';
3
3
  import { type InitialData, serverClient } from 'oc-server/client';
4
4
 
5
- let funFact: string | null = $state(null);
6
- let { born, firstName, hobbies, lastName }: InitialData = $props();
5
+ let { name, year }: InitialData = $props();
6
+ let fact: string | null = $state(null);
7
+ let loading = $state(false);
8
+ let error = $state('');
7
9
  let getFunFact = async () => {
8
- let response = await serverClient.funFact({ year: born });
9
- funFact = response.funFact;
10
+ loading = true;
11
+ error = '';
12
+ try {
13
+ let response = await serverClient.funFact({ year });
14
+ fact = response.funFact;
15
+ } catch (err) {
16
+ error = String(err);
17
+ } finally {
18
+ loading = false;
19
+ }
10
20
  };
11
21
  </script>
12
22
 
13
23
  <div class="container">
14
- <img width="50" height="50" src={logo} alt="Logo" />
15
-
16
- <h1>
17
- Hello, <span>{firstName}</span>
18
- {lastName}
19
- </h1>
20
- <div class="info">
21
- <div class="block">Born: {born}</div>
22
- <div class="block">
23
- Hobbies: {hobbies}
24
+ <div class="card">
25
+ <div class="brand">
26
+ <img class="logo" width="40" height="40" src={logo} alt="Logo" />
27
+ <p class="eyebrow">OpenComponents · Vite</p>
24
28
  </div>
29
+ <h1 class="title">Hello, <span class="name">{name}</span></h1>
30
+ <p class="subtitle">Born in <span class="pill">{year}</span></p>
31
+ {#if fact}
32
+ <div class="fact">{fact}</div>
33
+ {/if}
34
+ {#if error}
35
+ <div class="error">{error}</div>
36
+ {/if}
37
+ <button class="button" onclick={getFunFact} disabled={loading}>
38
+ {loading ? 'Loading…' : 'Fun year fact'}
39
+ </button>
25
40
  </div>
26
- {#if funFact}
27
- <div>{funFact}</div>
28
- {/if}
29
- <button class="button" onclick={getFunFact}> Fun year fact </button>
30
41
  </div>
31
42
 
32
43
  <style>
33
44
  .container {
34
- background-color: #3b246c;
45
+ background-color: #0f172a;
35
46
  color: #fff;
36
- font-family: sans-serif;
37
- padding: 40px;
47
+ font-family: 'DejaVu Sans', system-ui, -apple-system, 'Segoe UI', Roboto,
48
+ sans-serif;
49
+ padding: 56px 24px;
38
50
  }
39
51
 
40
- .button {
41
- background-color: #a97613;
42
- border: none;
43
- padding: 15px 32px;
44
- text-align: center;
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;
45
98
  font-size: 16px;
46
- text-decoration: none;
99
+ color: #475569;
100
+ }
101
+
102
+ .pill {
47
103
  display: inline-block;
48
- color: inherit;
49
- cursor: pointer;
104
+ background-color: #eef2ff;
105
+ color: #4338ca;
106
+ border-radius: 999px;
107
+ padding: 2px 12px;
108
+ font-weight: 700;
50
109
  }
51
110
 
52
- .button:hover {
53
- background-color: #c79535;
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;
54
122
  }
55
123
 
56
- h1 {
57
- margin: 0 0 20px 0;
124
+ .button:hover {
125
+ background-color: #4338ca;
58
126
  }
59
127
 
60
- span {
61
- text-decoration: underline;
128
+ .button:disabled {
129
+ opacity: 0.6;
130
+ cursor: default;
62
131
  }
63
132
 
64
- .info {
65
- margin-bottom: 20px;
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;
66
142
  }
67
143
 
68
- .block {
69
- margin: 6px 0;
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;
70
153
  }
71
154
  </style>
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render } from 'svelte/server';
3
+
4
+ import App from './App.svelte';
5
+
6
+ describe('App - Page', () => {
7
+ it('renders the fun fact button', () => {
8
+ const { body } = render(App, {
9
+ props: {
10
+ name: 'firstName',
11
+ year: 1991,
12
+ },
13
+ });
14
+
15
+ expect(body).toMatch(/Fun year fact/i);
16
+ });
17
+ });
@@ -1,82 +1,47 @@
1
1
  import { Server } from 'oc-server';
2
2
 
3
- const userDatabase = [
4
- { name: 'John Doe', born: 1986, hobbies: ['Swimming', 'Basketball'] },
5
- { name: 'Jane Doe', born: 1991, hobbies: ['Running', 'Rugby'] },
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
- async function getUser(userId: number) {
15
- if (userId < 0 || userId >= userDatabase.length) {
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
- // Enable console logs from browser to server for debugging
14
+ // Stream browser console logs to the terminal during `npm start`
29
15
  console: true,
30
16
  },
31
17
  })
32
- //Define URL parameters that can be passed to your component
18
+ // Parameters become URL query strings, e.g. `?name=Ada&year=1986`
33
19
  .withParameters({
34
- userId: {
35
- default: 1,
36
- description: 'The user id from the user database',
37
- example: 1,
38
- mandatory: true,
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
- // Main handler - returns initial data for component rendering
43
- // This runs when the component is first loaded
44
- .handler(async (params, ctx) => {
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
- // Actions are server endpoints that the client can call
70
- // Use these for user interactions, form submissions, etc.
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
- // This enables automatic type inference between server and client
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;
@@ -1,30 +1,110 @@
1
1
  .container {
2
- background-color: #3b246c;
2
+ background-color: #0f172a;
3
3
  color: #fff;
4
- font-family: sans-serif;
5
- padding: 40px;
4
+ font-family: 'DejaVu Sans', system-ui, -apple-system, 'Segoe UI', Roboto,
5
+ sans-serif;
6
+ padding: 56px 24px;
6
7
  }
7
8
 
8
- .button {
9
- background-color: #a97613;
10
- border: none;
11
- padding: 15px 32px;
12
- text-align: center;
9
+ .card {
10
+ background-color: #ffffff;
11
+ color: #0f172a;
12
+ border-radius: 20px;
13
+ padding: 32px;
14
+ max-width: 440px;
15
+ margin: 0 auto;
16
+ box-shadow: 0 12px 32px rgba(2, 6, 23, 0.45);
17
+ }
18
+
19
+ .brand {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 12px;
23
+ margin-bottom: 20px;
24
+ }
25
+
26
+ .logo {
27
+ width: 40px;
28
+ height: 40px;
29
+ border-radius: 12px;
30
+ display: block;
31
+ }
32
+
33
+ .eyebrow {
34
+ margin: 0;
35
+ font-size: 12px;
36
+ font-weight: 700;
37
+ letter-spacing: 0.14em;
38
+ text-transform: uppercase;
39
+ color: #4f46e5;
40
+ }
41
+
42
+ .title {
43
+ margin: 0 0 8px 0;
44
+ font-size: 30px;
45
+ line-height: 1.15;
46
+ letter-spacing: -0.02em;
47
+ }
48
+
49
+ .name {
50
+ color: #4f46e5;
51
+ }
52
+
53
+ .subtitle {
54
+ margin: 0 0 4px 0;
13
55
  font-size: 16px;
14
- text-decoration: none;
56
+ color: #475569;
57
+ }
58
+
59
+ .pill {
15
60
  display: inline-block;
16
- color: inherit;
61
+ background-color: #eef2ff;
62
+ color: #4338ca;
63
+ border-radius: 999px;
64
+ padding: 2px 12px;
65
+ font-weight: 700;
66
+ }
67
+
68
+ .button {
69
+ margin-top: 20px;
70
+ width: 100%;
71
+ background-color: #4f46e5;
72
+ color: #fff;
73
+ border: none;
74
+ border-radius: 12px;
75
+ padding: 12px 24px;
76
+ font-size: 15px;
77
+ font-weight: 600;
17
78
  cursor: pointer;
18
79
  }
19
80
 
20
- .info {
21
- margin-bottom: 20px;
81
+ .button:hover {
82
+ background-color: #4338ca;
22
83
  }
23
84
 
24
- .block {
25
- margin: 6px 0;
85
+ .button:disabled {
86
+ opacity: 0.6;
87
+ cursor: default;
26
88
  }
27
89
 
28
- .button:hover {
29
- background-color: #c79535;
90
+ .fact {
91
+ margin-top: 20px;
92
+ background-color: #eef2ff;
93
+ border-left: 4px solid #4f46e5;
94
+ border-radius: 0 12px 12px 0;
95
+ padding: 14px 16px;
96
+ font-size: 15px;
97
+ line-height: 1.5;
98
+ color: #1e1b4b;
99
+ }
100
+
101
+ .error {
102
+ margin-top: 20px;
103
+ background-color: #fef2f2;
104
+ border-left: 4px solid #ef4444;
105
+ border-radius: 0 12px 12px 0;
106
+ padding: 14px 16px;
107
+ font-size: 15px;
108
+ line-height: 1.5;
109
+ color: #991b1b;
30
110
  }
@@ -2,10 +2,9 @@ import { defineConfig } from 'vite';
2
2
 
3
3
  // https://vitejs.dev/config/
4
4
  export default defineConfig({
5
- // Add your own plugins here for thinkgs like eslint or typecheckers
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
+ // config (including the svelte plugin) lives in vitest.config.ts instead.
6
9
  plugins: [],
7
- // @ts-ignore Missing test property
8
- test: {
9
- environment: 'jsdom'
10
- }
11
10
  });
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import { svelte } from '@sveltejs/vite-plugin-svelte';
3
+
4
+ // Vitest-only config. The packaging build reads vite.config.ts (which must
5
+ // stay plugin-free), so .svelte support for unit tests lives here. Tests
6
+ // render on the server (no DOM needed), which also makes the plugin emit
7
+ // server output for components.
8
+ export default defineConfig({
9
+ plugins: [svelte()],
10
+ test: {
11
+ environment: 'node',
12
+ },
13
+ });
@@ -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
- }