oc-template-preact-compiler 4.0.0 → 4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-template-preact-compiler",
3
- "version": "4.0.0",
3
+ "version": "4.0.2",
4
4
  "description": "Compiler for the Preact OC template",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,7 +30,7 @@
30
30
  "dependencies": {
31
31
  "@preact/preset-vite": "^2.7.0",
32
32
  "oc-template-preact": "^0.1.1",
33
- "oc-vite-compiler": "^6.0.0",
33
+ "oc-vite-compiler": "^6.0.2",
34
34
  "preact": "^10"
35
35
  },
36
36
  "peerDependencies": {
@@ -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,15 +23,14 @@
23
23
  },
24
24
  "dependencies": {},
25
25
  "devDependencies": {
26
- "@preact/preset-vite": "^2.7.0",
27
- "@testing-library/react": "^14.0.0",
28
- "@testing-library/user-event": "^14.4.3",
29
- "jsdom": "^21.1.1",
30
- "oc-server": "^4.0.0",
31
- "oc-template-preact-compiler": "^4.0.0",
32
- "preact": "^10.13.2",
33
- "typescript": "5.0.2",
34
- "vitest": "^0.29.7",
26
+ "@testing-library/jest-dom": "^7.0.1",
27
+ "@testing-library/preact": "^3.2.4",
28
+ "jsdom": "^30.1.2",
29
+ "oc-server": "^4.0.3",
30
+ "oc-template-preact-compiler": "^4.0.2",
31
+ "preact": "^11.0.1",
32
+ "typescript": "5.9.3",
33
+ "vitest": "^5.0.3",
35
34
  "vite": "^7.1.12"
36
35
  }
37
36
  }
@@ -1,11 +1,8 @@
1
1
  import { describe, it, afterEach, beforeEach, expect, vi } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import { DataProvider } from 'oc-template-preact-compiler/dist/utils/useData';
2
+ import { render, screen } from '@testing-library/preact';
4
3
 
5
4
  import App from './App';
6
5
 
7
- const getData = vi.fn();
8
-
9
6
  describe('App - Page', () => {
10
7
  beforeEach(() => {
11
8
  window.oc = { events: { on: vi.fn(), fire: vi.fn() } } as any;
@@ -15,24 +12,7 @@ describe('App - Page', () => {
15
12
  });
16
13
 
17
14
  it('Gets more data when clicking the button', () => {
18
- getData.mockImplementationOnce(() => Promise.resolve({}));
19
- render(
20
- <DataProvider
21
- value={{
22
- firstName: 'firstName',
23
- lastName: 'lastName',
24
- userId: 0,
25
- getData,
26
- }}
27
- >
28
- <App
29
- firstName="firstName"
30
- lastName="lastName"
31
- born={1991}
32
- hobbies={[]}
33
- />
34
- </DataProvider>
35
- );
15
+ render(<App name="firstName" year={1991} />);
36
16
 
37
17
  const extraInfoButton = screen.getByText(/Fun year fact/i);
38
18
  expect(extraInfoButton).toBeTruthy();
@@ -7,46 +7,58 @@ import {
7
7
  import styles from './styles.css';
8
8
  import logo from '../public/logo.png';
9
9
 
10
- type AdditionalData = ActionOutput<'funFact'>;
10
+ type FunFact = ActionOutput<'funFact'>;
11
11
 
12
12
  const App = (props: InitialData) => {
13
- const { firstName, lastName, born, hobbies } = props;
14
- const [additionalData, setAdditionalData] = useState<AdditionalData | null>(
15
- null
16
- );
13
+ // These come from the server.ts handler return value
14
+ const { name, year } = props;
15
+ const [fact, setFact] = useState<FunFact | null>(null);
16
+ const [loading, setLoading] = useState(false);
17
17
  const [error, setError] = useState('');
18
18
 
19
19
  const getFunFact = async () => {
20
+ setLoading(true);
20
21
  setError('');
21
22
  try {
22
- const data = await serverClient.funFact({ year: born });
23
- setAdditionalData(data);
23
+ // Call the server action with type safety
24
+ setFact(await serverClient.funFact({ year }));
24
25
  } catch (err) {
25
26
  setError(String(err));
27
+ } finally {
28
+ setLoading(false);
26
29
  }
27
30
  };
28
31
 
29
- if (error) {
30
- return <div>Something wrong happened!</div>;
31
- }
32
-
33
32
  return (
34
33
  <div className={styles.container}>
35
- <img width="50" height="50" src={logo} alt="Logo" />
36
- <h1 style={{ margin: '0 0 20px 0' }}>
37
- Hello, <span style={{ textDecoration: 'underline' }}>{firstName}</span>{' '}
38
- {lastName}
39
- </h1>
40
- <div className={styles.info}>
41
- <div className={styles.block}>Born: {born}</div>
42
- <div className={styles.block}>
43
- Hobbies: {hobbies.map((x) => x.toLowerCase()).join(', ')}
34
+ <div className={styles.card}>
35
+ <div className={styles.brand}>
36
+ {/* Static assets are imported from ../public/ */}
37
+ <img
38
+ className={styles.logo}
39
+ width="40"
40
+ height="40"
41
+ src={logo}
42
+ alt="Logo"
43
+ />
44
+ <p className={styles.eyebrow}>OpenComponents · Vite</p>
44
45
  </div>
46
+ <h1 className={styles.title}>
47
+ Hello, <span className={styles.name}>{name}</span>
48
+ </h1>
49
+ <p className={styles.subtitle}>
50
+ Born in <span className={styles.pill}>{year}</span>
51
+ </p>
52
+ {fact && <div className={styles.fact}>{fact.funFact}</div>}
53
+ {error && <div className={styles.error}>{error}</div>}
54
+ <button
55
+ className={styles.button}
56
+ onClick={getFunFact}
57
+ disabled={loading}
58
+ >
59
+ {loading ? 'Loading…' : 'Fun year fact'}
60
+ </button>
45
61
  </div>
46
- {additionalData && <div>{additionalData.funFact}</div>}
47
- <button className={styles.button} onClick={getFunFact}>
48
- Fun year fact
49
- </button>
50
62
  </div>
51
63
  );
52
64
  };
@@ -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,7 +2,7 @@ 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
6
  plugins: [],
7
7
  // @ts-ignore Missing test property
8
8
  test: {
@@ -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
- }