{"id":26092864,"url":"https://github.com/feedbackone/elmstronaut","last_synced_at":"2025-04-13T12:50:34.575Z","repository":{"id":279494661,"uuid":"939000700","full_name":"feedbackone/elmstronaut","owner":"feedbackone","description":"Render Elm modules as Astro components","archived":false,"fork":false,"pushed_at":"2025-02-28T09:39:13.000Z","size":197,"stargazers_count":24,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-22T09:12:51.804Z","etag":null,"topics":["astro","astro-integration","elm"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/elmstronaut","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/feedbackone.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-25T20:43:07.000Z","updated_at":"2025-03-21T19:56:18.000Z","dependencies_parsed_at":null,"dependency_job_id":"2df4ec75-63a7-4a36-a8af-b83cd120fa4b","html_url":"https://github.com/feedbackone/elmstronaut","commit_stats":null,"previous_names":["feedbackone/elmstronaut"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feedbackone%2Felmstronaut","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feedbackone%2Felmstronaut/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feedbackone%2Felmstronaut/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/feedbackone%2Felmstronaut/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/feedbackone","download_url":"https://codeload.github.com/feedbackone/elmstronaut/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248717252,"owners_count":21150388,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["astro","astro-integration","elm"],"created_at":"2025-03-09T11:02:14.874Z","updated_at":"2025-04-13T12:50:34.553Z","avatar_url":"https://github.com/feedbackone.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# Elmstronaut 🌳👨‍🚀\n\n\u003e That's one small step for (a) man, one giant leap for Elm-kind.\n\nAn Astro integration that enables rendering of Elm modules as Astro components.\n\n## Table of contents\n\n- [Installation](#installation)\n- [Setup](#setup)\n- [Basic usage](#basic-usage)\n- [Fallback slot](#fallback-slot)\n- [Passing flags](#passing-flags)\n- [Using ports](#using-ports)\n- [Tailwind support](#tailwind-support)\n- [Examples](#examples)\n- [Limitations](#limitations)\n- [Future plans](#future-plans)\n- [Contributing](#contributing)\n\n\n## Installation\n\n```sh\npnpm add elm elmstronaut\n```\n\n## Setup\n\nThis guide assumes you already have an Astro project set up. If not, please run `pnpm create astro@latest` first and come back when you're ready.\n\n- Create a folder called `elm` under the `src` directory. Your Elm files will live here.\n- Make sure there is an _elm.json_ file in the root directory. Run `pnpm elm init` if you haven't initialized your Elm project yet.\n- Modify `\"source-directories\"` from `src` to `src/elm` in the _elm.json_\n\n  ```diff\n    \"source-directories\": [\n  -   \"src\"\n  +   \"src/elm\"\n    ],\n  ```\n- Add `elmstronaut` to Astro integrations in the _astro.config.mts_\n\n  ```diff\n  + import elmstronaut from \"elmstronaut\";\n\n    export default defineConfig({\n  +    integrations: [elmstronaut()],\n    });\n  ```\n\n## Basic usage\n\nLet's start with a canonical \"Hello, world\" example.\n\n_src/elm/Hello.elm_\n```elm\nmodule Hello exposing (main)\n\nimport Html exposing (Html, text)\n\n\nmain : Html msg\nmain =\n    text \"Hello, Astro 👋\"\n```\n\n_src/pages/index.astro_\n```jsx\n---\nimport Hello from \"../elm/Hello.elm\";\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n\u003cLayout\u003e\n  \u003cHello client:load /\u003e\n\u003c/Layout\u003e\n```\n\n\u003e [!IMPORTANT]\n\u003e Notice the `client:load` directive. This is essential as we don't support SSR yet. Hopefully, some day in the near future 🤞.\n\nCongratulations!\nWe can now use Elm components in Astro! 🎉\n\n## Fallback slot\n\nYou can also pass an optional \"fallback\" slot to display while the component is loading.\n\n```jsx\n---\nimport Hello from \"../elm/Hello.elm\";\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n\u003cLayout\u003e\n  \u003cHello client:load\u003e\n    \u003cp slot=\"fallback\"\u003eLoading...\u003c/p\u003e\n  \u003c/Hello\u003e\n\u003c/Layout\u003e\n```\n\nThis will improve the user experience, and decrease the [CLS](https://web.dev/articles/cls) score of your page.\n\n## Passing flags\n\nComponent props are automatically passed as flags to your Elm app. Although you can access them directly (don't do this – there is a reason you're using Elm after all), the proper way is to decode them.\n\nLet's take a look at another widely known example – the Counter!\n\n_src/pages/counter.astro_\n```jsx\n---\nimport Counter from \"../elm/Counter.elm\";\nimport Layout from \"../layouts/Layout.astro\";\n---\n\n\u003cLayout\u003e\n  \u003cCounter client:load initial={29} /\u003e\n\u003c/Layout\u003e\n```\n\n_src/elm/Counter.elm_\n```elm\nmodule Counter exposing (main)\n\nimport Browser\nimport Html exposing (Html, button, div, p, text)\nimport Html.Events exposing (onClick)\nimport Json.Decode\n\n\n\n-- MAIN\n\n\nmain : Program Json.Decode.Value Model Msg\nmain =\n    Browser.element\n        { init = init\n        , update = update\n        , subscriptions = \\_ -\u003e Sub.none\n        , view = view\n        }\n\n\n\n-- FLAGS\n\n\ntype alias Flags =\n    { initial : Int }\n\n\nflagsDecoder : Json.Decode.Decoder Flags\nflagsDecoder =\n    Json.Decode.map Flags\n        (Json.Decode.field \"initial\" Json.Decode.int)\n\n\n\n-- MODEL\n\n\ntype alias Model =\n    { count : Int }\n\n\ninit : Json.Decode.Value -\u003e ( Model, Cmd Msg )\ninit flags =\n    let\n        initialCount =\n            Json.Decode.decodeValue flagsDecoder flags\n                |\u003e Result.map .initial\n                |\u003e Result.withDefault 0\n    in\n    ( { count = initialCount }, Cmd.none )\n\n\n\n-- UPDATE\n\n\ntype Msg\n    = Increment\n    | Decrement\n\n\nupdate : Msg -\u003e Model -\u003e ( Model, Cmd Msg )\nupdate msg model =\n    case msg of\n        Increment -\u003e\n            ( { model | count = model.count + 1 }, Cmd.none )\n\n        Decrement -\u003e\n            ( { model | count = model.count - 1 }, Cmd.none )\n\n\n\n-- VIEW\n\n\nview : Model -\u003e Html Msg\nview model =\n    div []\n        [ button [ onClick Increment ] [ text \"+\" ]\n        , p [] [ text (String.fromInt model.count) ]\n        , button [ onClick Decrement ] [ text \"-\" ]\n        ]\n```\n\nLet's walk trough the important bits:\n\n- First, we pass `Json.Decode.Value` as the type of the second argument of the `main` function.\n\n- Then, we define the `Flags` type and its decoder.\n\n- Lastly, we pass the decoder we defined above and the `flags` argument of the `init` function to the `Json.Decode.decodeValue` function. If the decoding succeeds, the `initialCount` would get the value of the `initial` prop. Otherwise, it will be set to `0`. No runtime errors. Beauty!\n\n\u003e [!TIP]\n\u003e [NoRedInk/elm-json-decode-pipeline](https://package.elm-lang.org/packages/NoRedInk/elm-json-decode-pipeline/) package immensely simplifies the process of writing decoders.\n\n## Using ports\n\nTo use ports we need to define `window.onElmInit`. It receives a callback, which will be called each time an Elm app is initialized. For each initialization it's corresponding Elm module name and the app will be passed as arguments.\n\n_src/elm/interop.ts_ (or other)\n```ts\nwindow.onElmInit = (elmModuleName: string, app: ElmApp) =\u003e {\n  if (elmModuleName === \"Hello\") {\n    // Subscribe to messages from Elm\n    app.ports?.foo.subscribe?.((message) =\u003e console.log(message));\n\n    // Send messages to Elm\n    app.ports?.bar.send?.(\"baz\");\n  }\n};\n```\n\nThe `elmModuleName` is the module name provided in the Elm file.\n\nFor example, if `Hello.elm` would have been located at `src/elm/Greeting/Hello.elm` instead of `src/elm/Hello.elm` as mentioned in the examples above, the `elmModuleName` would be `Greeting.Hello`.\n\n## Tailwind support\n\nIf you're using [Tailwind](https://tailwindcss.com/) in your Elm files, make sure to add the following spinnet to your CSS:\n\n```diff\n  @import \"tailwindcss\";\n\n+ @source \"../../src/elm\";\n```\n\nThis ensures that the classes used in the Elm files would be included in the final bundle.\n\n## Examples\nThe [examples](https://github.com/feedbackone/elmstronaut/tree/main/examples) folder could be a useful place to start. Altough it currently only contains a few basic examples, we're planning to add more in the near future.\n\n## Limitations\n- Can't render nested components (POC is ready)\n- No SSR support (yet)\n- Only `Browser.element` is supported. **This is by design.** The routing part will always be handled by Astro.\n\n## Future plans\n- [ ] Add support for rendering named slots.\n- [ ] \"Go to definition\" should open the Elm file instead of the `elmstronaut.d.ts`.\n- [ ] Add SSR support.\n- [ ] Figure out a way to compile multiple Elm modules into one bundle.\n- [ ] Remove the constraint of having the `elm` folder.\n- [ ] Add an `optimize` option to the config to force production builds when needed.\n- [ ] Add an `elmJsonPath` option to be able to specify the path to the _elm.json_ file.\n- [ ] Generate an Elm custom type with all possible routes based on the `pages` folder, so that we can use `href` safely (similar to Elm Land).\n- [ ] Generate a type union of all Elm module names. We can then use that type instead of `string` for `elmModuleName`.\n- [ ] Parse Elm files and generate proper types for ports.\n\n## Contributing\nPlease check out our contributing guidelines [here](/CONTRIBUTING.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeedbackone%2Felmstronaut","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffeedbackone%2Felmstronaut","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffeedbackone%2Felmstronaut/lists"}