oc-template-elm-compiler 3.0.1 → 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.
|
@@ -11,7 +11,7 @@ function elmEntry({ viewPath, providerFunctions }) {
|
|
|
11
11
|
const { _baseUrl, _componentName, _componentVersion, _staticPath, ...rest } = props;
|
|
12
12
|
const app = Elm["${extractName(viewPath)}"].init({
|
|
13
13
|
node,
|
|
14
|
-
flags: rest
|
|
14
|
+
flags: { ...rest, staticPath: _staticPath }
|
|
15
15
|
})
|
|
16
16
|
|
|
17
17
|
${providerFunctions}
|
|
@@ -49,7 +49,7 @@ function jsEntry({ viewPath, providerFunctions }) {
|
|
|
49
49
|
const { _baseUrl, _componentName, _componentVersion, _staticPath, ...rest } = props;
|
|
50
50
|
const app = program.init({
|
|
51
51
|
node,
|
|
52
|
-
flags: rest
|
|
52
|
+
flags: { ...rest, staticPath: _staticPath }
|
|
53
53
|
})
|
|
54
54
|
|
|
55
55
|
${providerFunctions}
|
package/package.json
CHANGED
|
@@ -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
|
|
@@ -18,10 +18,10 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {},
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"oc-server": "^4.0.
|
|
22
|
-
"oc-template-elm-compiler": "^3.0.
|
|
21
|
+
"oc-server": "^4.0.3",
|
|
22
|
+
"oc-template-elm-compiler": "^3.0.2",
|
|
23
23
|
"elm": "^0.19.1-6",
|
|
24
|
-
"typescript": "^5.
|
|
24
|
+
"typescript": "^5.9.3",
|
|
25
25
|
"vite": "^7.1.12"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -3,7 +3,7 @@ port module Main exposing (..)
|
|
|
3
3
|
import Browser
|
|
4
4
|
import Css exposing (..)
|
|
5
5
|
import Html.Styled exposing (..)
|
|
6
|
-
import Html.Styled.Attributes exposing (
|
|
6
|
+
import Html.Styled.Attributes as Attr exposing (css)
|
|
7
7
|
import Html.Styled.Events exposing (onClick)
|
|
8
8
|
import Json.Decode as D
|
|
9
9
|
import Json.Encode as E
|
|
@@ -28,16 +28,17 @@ main =
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
type alias Model =
|
|
31
|
-
{ staticPath: String,
|
|
32
|
-
|
|
33
|
-
type alias FactModel =
|
|
34
|
-
{ funFact: String }
|
|
31
|
+
{ staticPath : String, name : String, year : Int, funFact : Maybe String, error : Maybe String, loading : Bool }
|
|
35
32
|
|
|
36
33
|
|
|
37
34
|
type alias DataRequest =
|
|
38
35
|
{ year : Int }
|
|
39
36
|
|
|
40
37
|
|
|
38
|
+
type alias ActionResponse =
|
|
39
|
+
{ funFact : String, error : String }
|
|
40
|
+
|
|
41
|
+
|
|
41
42
|
encode : DataRequest -> E.Value
|
|
42
43
|
encode data =
|
|
43
44
|
E.object
|
|
@@ -52,21 +53,11 @@ init flags =
|
|
|
52
53
|
model
|
|
53
54
|
|
|
54
55
|
Err _ ->
|
|
55
|
-
{ staticPath = "",
|
|
56
|
+
{ staticPath = "", name = "", year = 0, funFact = Nothing, error = Nothing, loading = False }
|
|
56
57
|
, Cmd.none
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
isJust : Maybe a -> Bool
|
|
61
|
-
isJust data =
|
|
62
|
-
case data of
|
|
63
|
-
Nothing ->
|
|
64
|
-
False
|
|
65
|
-
|
|
66
|
-
_ ->
|
|
67
|
-
True
|
|
68
|
-
|
|
69
|
-
|
|
70
61
|
|
|
71
62
|
-- UPDATE
|
|
72
63
|
|
|
@@ -80,62 +71,160 @@ update : Msg -> Model -> ( Model, Cmd Msg )
|
|
|
80
71
|
update msg model =
|
|
81
72
|
case msg of
|
|
82
73
|
GetFunFact ->
|
|
83
|
-
( model, DataRequest model.
|
|
74
|
+
( { model | loading = True, error = Nothing }, DataRequest model.year |> encode |> askFunFact )
|
|
84
75
|
|
|
85
76
|
Recv data ->
|
|
86
|
-
|
|
87
|
-
Ok
|
|
88
|
-
|
|
77
|
+
case D.decodeValue responseDecoder data of
|
|
78
|
+
Ok res ->
|
|
79
|
+
if res.error == "" then
|
|
80
|
+
( { model | loading = False, funFact = Just res.funFact }, Cmd.none )
|
|
81
|
+
|
|
82
|
+
else
|
|
83
|
+
( { model | loading = False, error = Just res.error }, Cmd.none )
|
|
89
84
|
|
|
90
85
|
Err _ ->
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
( { model | loading = False, error = Just "Something went wrong" }, Cmd.none )
|
|
87
|
+
|
|
88
|
+
|
|
94
89
|
|
|
95
90
|
-- VIEW
|
|
96
91
|
|
|
92
|
+
|
|
97
93
|
view : Model -> Html Msg
|
|
98
94
|
view model =
|
|
99
|
-
div
|
|
100
|
-
|
|
95
|
+
div
|
|
96
|
+
[ css
|
|
97
|
+
[ backgroundColor (hex "0f172a")
|
|
101
98
|
, color (hex "fff")
|
|
102
|
-
, fontFamilies [ "sans-serif" ]
|
|
103
|
-
,
|
|
99
|
+
, fontFamilies [ "DejaVu Sans", "system-ui", "-apple-system", "Segoe UI", "Roboto", "sans-serif" ]
|
|
100
|
+
, padding2 (px 56) (px 24)
|
|
101
|
+
, displayFlex
|
|
102
|
+
, justifyContent center
|
|
104
103
|
]
|
|
105
104
|
]
|
|
106
|
-
[
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
105
|
+
[ div
|
|
106
|
+
[ css
|
|
107
|
+
[ backgroundColor (hex "ffffff")
|
|
108
|
+
, color (hex "0f172a")
|
|
109
|
+
, borderRadius (px 20)
|
|
110
|
+
, padding (px 32)
|
|
111
|
+
, width (pct 100)
|
|
112
|
+
, maxWidth (px 440)
|
|
113
|
+
, boxShadow5 (px 0) (px 12) (px 32) (px 0) (rgba 2 6 23 0.45)
|
|
114
|
+
]
|
|
115
|
+
]
|
|
116
|
+
[ div [ css [ displayFlex, alignItems center, property "gap" "12px", marginBottom (px 20) ] ]
|
|
117
|
+
[ img
|
|
118
|
+
[ css [ width (px 40), height (px 40), borderRadius (px 12), display block ]
|
|
119
|
+
, Attr.src (model.staticPath ++ "public/logo.png")
|
|
120
|
+
, Attr.alt "Logo"
|
|
121
|
+
]
|
|
122
|
+
[]
|
|
123
|
+
, p
|
|
124
|
+
[ css
|
|
125
|
+
[ margin zero
|
|
126
|
+
, fontSize (px 12)
|
|
127
|
+
, fontWeight (int 700)
|
|
128
|
+
, letterSpacing (Css.em 0.14)
|
|
129
|
+
, textTransform uppercase
|
|
130
|
+
, color (hex "4f46e5")
|
|
131
|
+
]
|
|
132
|
+
]
|
|
133
|
+
[ text "OpenComponents · Vite" ]
|
|
134
|
+
]
|
|
135
|
+
, h1
|
|
136
|
+
[ css
|
|
137
|
+
[ margin4 (px 0) (px 0) (px 8) (px 0)
|
|
138
|
+
, fontSize (px 30)
|
|
139
|
+
, lineHeight (num 1.15)
|
|
140
|
+
, letterSpacing (Css.em -0.02)
|
|
141
|
+
]
|
|
142
|
+
]
|
|
143
|
+
[ text "Hello, "
|
|
144
|
+
, span [ css [ color (hex "4f46e5") ] ] [ text model.name ]
|
|
145
|
+
]
|
|
146
|
+
, p
|
|
147
|
+
[ css [ margin4 (px 0) (px 0) (px 4) (px 0), fontSize (px 16), color (hex "475569") ] ]
|
|
148
|
+
[ text "Born in "
|
|
149
|
+
, span
|
|
150
|
+
[ css
|
|
151
|
+
[ display inlineBlock
|
|
152
|
+
, backgroundColor (hex "eef2ff")
|
|
153
|
+
, color (hex "4338ca")
|
|
154
|
+
, borderRadius (px 999)
|
|
155
|
+
, padding2 (px 2) (px 12)
|
|
156
|
+
, fontWeight (int 700)
|
|
157
|
+
]
|
|
158
|
+
]
|
|
159
|
+
[ text (String.fromInt model.year) ]
|
|
160
|
+
]
|
|
161
|
+
, case model.funFact of
|
|
162
|
+
Nothing ->
|
|
163
|
+
text ""
|
|
164
|
+
|
|
165
|
+
Just fact ->
|
|
166
|
+
div
|
|
167
|
+
[ css
|
|
168
|
+
[ marginTop (px 20)
|
|
169
|
+
, backgroundColor (hex "eef2ff")
|
|
170
|
+
, borderLeft3 (px 4) solid (hex "4f46e5")
|
|
171
|
+
, borderRadius4 (px 0) (px 12) (px 12) (px 0)
|
|
172
|
+
, padding2 (px 14) (px 16)
|
|
173
|
+
, fontSize (px 15)
|
|
174
|
+
, lineHeight (num 1.5)
|
|
175
|
+
, color (hex "1e1b4b")
|
|
176
|
+
]
|
|
177
|
+
]
|
|
178
|
+
[ text fact ]
|
|
179
|
+
, case model.error of
|
|
180
|
+
Nothing ->
|
|
181
|
+
text ""
|
|
182
|
+
|
|
183
|
+
Just message ->
|
|
184
|
+
div
|
|
185
|
+
[ css
|
|
186
|
+
[ marginTop (px 20)
|
|
187
|
+
, backgroundColor (hex "fef2f2")
|
|
188
|
+
, borderLeft3 (px 4) solid (hex "ef4444")
|
|
189
|
+
, borderRadius4 (px 0) (px 12) (px 12) (px 0)
|
|
190
|
+
, padding2 (px 14) (px 16)
|
|
191
|
+
, fontSize (px 15)
|
|
192
|
+
, lineHeight (num 1.5)
|
|
193
|
+
, color (hex "991b1b")
|
|
194
|
+
]
|
|
195
|
+
]
|
|
196
|
+
[ text message ]
|
|
197
|
+
, button
|
|
198
|
+
[ css
|
|
199
|
+
[ marginTop (px 20)
|
|
200
|
+
, width (pct 100)
|
|
201
|
+
, backgroundColor (hex "4f46e5")
|
|
202
|
+
, color (hex "fff")
|
|
203
|
+
, border (px 0)
|
|
204
|
+
, borderRadius (px 12)
|
|
205
|
+
, padding2 (px 12) (px 24)
|
|
206
|
+
, fontSize (px 15)
|
|
207
|
+
, fontWeight (int 600)
|
|
208
|
+
, cursor pointer
|
|
209
|
+
, opacity (num (if model.loading then 0.6 else 1))
|
|
210
|
+
, hover [ backgroundColor (hex "4338ca") ]
|
|
211
|
+
]
|
|
212
|
+
, onClick GetFunFact
|
|
213
|
+
, Attr.disabled model.loading
|
|
214
|
+
]
|
|
215
|
+
[ text
|
|
216
|
+
(if model.loading then
|
|
217
|
+
"Loading…"
|
|
218
|
+
|
|
219
|
+
else
|
|
220
|
+
"Fun year fact"
|
|
221
|
+
)
|
|
222
|
+
]
|
|
114
223
|
]
|
|
115
|
-
, case model.funFact of
|
|
116
|
-
Nothing ->
|
|
117
|
-
text ""
|
|
118
|
-
|
|
119
|
-
Just fact ->
|
|
120
|
-
div [] [text (fact)]
|
|
121
|
-
, button [ css
|
|
122
|
-
[ backgroundColor (hex "a97613")
|
|
123
|
-
, border (px 0)
|
|
124
|
-
, padding2 (px 15) (px 32)
|
|
125
|
-
, textAlign center
|
|
126
|
-
, fontSize (px 16)
|
|
127
|
-
, textDecoration none
|
|
128
|
-
, display inlineBlock
|
|
129
|
-
, color inherit
|
|
130
|
-
, cursor pointer
|
|
131
|
-
]
|
|
132
|
-
, onClick GetFunFact
|
|
133
|
-
]
|
|
134
|
-
[ text "Fun year fact" ]
|
|
135
|
-
|
|
136
224
|
]
|
|
137
225
|
|
|
138
226
|
|
|
227
|
+
|
|
139
228
|
-- SUBSCRIPTIONS
|
|
140
229
|
|
|
141
230
|
|
|
@@ -146,18 +235,21 @@ subscriptions _ =
|
|
|
146
235
|
|
|
147
236
|
decoder : D.Decoder Model
|
|
148
237
|
decoder =
|
|
149
|
-
D.
|
|
238
|
+
D.map3
|
|
239
|
+
(\staticPath name year ->
|
|
240
|
+
{ staticPath = staticPath, name = name, year = year, funFact = Nothing, error = Nothing, loading = False }
|
|
241
|
+
)
|
|
150
242
|
(D.field "staticPath" D.string)
|
|
151
|
-
(D.field "
|
|
152
|
-
(D.field "
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
243
|
+
(D.field "name" D.string)
|
|
244
|
+
(D.field "year" D.int)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
responseDecoder : D.Decoder ActionResponse
|
|
248
|
+
responseDecoder =
|
|
249
|
+
D.map2 ActionResponse
|
|
250
|
+
(D.field "funFact" D.string)
|
|
251
|
+
(D.field "error" D.string)
|
|
252
|
+
|
|
161
253
|
|
|
162
254
|
|
|
163
255
|
-- PORTS
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { serverClient } from 'oc-server';
|
|
1
|
+
import { serverClient } from 'oc-server/client';
|
|
2
2
|
import { Elm } from './Main.elm';
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
js: (app) => {
|
|
6
6
|
app.ports.askFunFact.subscribe(async (params) => {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
try {
|
|
8
|
+
const data = await serverClient.funFact(params);
|
|
9
|
+
app.ports.getFunFact.send({ funFact: data.funFact, error: '' });
|
|
10
|
+
} catch (err) {
|
|
11
|
+
app.ports.getFunFact.send({ funFact: '', error: String(err) });
|
|
12
|
+
}
|
|
9
13
|
});
|
|
10
14
|
},
|
|
11
15
|
program: Elm.Main,
|
|
@@ -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;
|