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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oc-template-elm-compiler",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Compiler for the Elm OC template",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
@@ -18,10 +18,10 @@
18
18
  },
19
19
  "dependencies": {},
20
20
  "devDependencies": {
21
- "oc-server": "^4.0.2",
22
- "oc-template-elm-compiler": "^3.0.1",
21
+ "oc-server": "^4.0.3",
22
+ "oc-template-elm-compiler": "^3.0.2",
23
23
  "elm": "^0.19.1-6",
24
- "typescript": "^5.3.3",
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, firstName : String, lastName : String, born : Int, hobbies : List String, funFact: Maybe 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 = "", firstName = "", lastName = "", born = 0, hobbies = [], funFact = Nothing }
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.born |> encode |> askFunFact )
74
+ ( { model | loading = True, error = Nothing }, DataRequest model.year |> encode |> askFunFact )
84
75
 
85
76
  Recv data ->
86
- ( case D.decodeValue factDecoder data of
87
- Ok newModel ->
88
- { model | funFact = Just newModel }
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
- { staticPath = "", firstName = "", lastName = "", born = 0, hobbies = [], funFact = Nothing }
92
- , Cmd.none
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 [ css
100
- [ backgroundColor (hex "3b246c")
95
+ div
96
+ [ css
97
+ [ backgroundColor (hex "0f172a")
101
98
  , color (hex "fff")
102
- , fontFamilies [ "sans-serif" ]
103
- , padding (px 40)
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
- [ img [ Html.Styled.Attributes.width 50, Html.Styled.Attributes.height 50, src (model.staticPath ++ "public/logo.png"), alt "Logo" ] []
107
- , h1 [ css [ margin4 (px 0) (px 0) (px 20) (px 0) ] ]
108
- [ text ("Hello, ")
109
- , span [ css [ textDecoration underline ] ] [text (model.firstName)]
110
- , text( " " ++ model.lastName ) ]
111
- , div [ css [ marginBottom (px 20) ] ]
112
- [ div [ css [ margin2 (px 6) (px 0) ] ] [ text ("Born: " ++ String.fromInt model.born) ]
113
- , div [ css [ margin2 (px 6) (px 0) ] ] [ text ("Hobbies: " ++ String.join ", " (List.map String.toLower model.hobbies)) ]
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.map6 Model
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 "firstName" D.string)
152
- (D.field "lastName" D.string)
153
- (D.field "born" D.int)
154
- (D.field "hobbies" (D.list D.string))
155
- (D.maybe (D.field "funFact" D.string))
156
-
157
- -- decoder of a single string field
158
- factDecoder : D.Decoder String
159
- factDecoder =
160
- D.field "funFact" D.string
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
- const data = await serverClient.funFact(params);
8
- app.ports.getFunFact.send(data);
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
- 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;