diff --git a/src/cli/README.md b/src/cli/README.md index cf7f4d9..3bc474c 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -4,7 +4,7 @@ ## installation ```bash -npm install -g elm-spa +npm install -g elm-spa@latest ``` ## usage @@ -13,194 +13,33 @@ npm install -g elm-spa $ elm-spa help ``` ``` -elm-spa – version 6.0.0 +elm-spa – version 6.0.4 Commands: elm-spa new . . . . . . . . . create a new project elm-spa add . . . . . . . . create a new page elm-spa build . . . . . . one-time production build -elm-spa watch . . . . . . . runs build as you code elm-spa server . . . . . . start a live dev server +Other commands: +elm-spa gen . . . . generates code without elm make +elm-spa watch . . . . runs elm-spa gen as you code + Visit https://elm-spa.dev for more! ``` +## learn more -# Docs - -Here are a few reasons to use __elm-spa__: - -1. __Automatic routing__ - automatically generates URL routing and connects your pages together, based on an easy-to-remember naming convention. -1. __Keep pages simple__ - comes with a friendly API for making pages as lightweight or advanced as you need. -1. __Storage, authentication, & more__ - the official website has guides for building common SPA features for real world applications. - -## Routing - -URL routing is __automatically__ generated from the file names in `src/Pages`: - -URL | Filepath ---- | --- -`/` | `Home_.elm` -`/about-us` | `AboutUs.elm` -`/about-us/offices` | `AboutUs/Offices.elm` -`/posts` | `Posts.elm` -`/posts/:id` | `Posts/Id_.elm` -`/users/:name/settings` | `Users/Name_/Settings.elm` -`/users/:name/posts/:id` | `Users/Name_/Posts/Id_.elm` - -### Top-level Route - -The reserved filename `Home_.elm` is used to indicate the homepage at `/`. - -This is different than `Home.elm` (without the underscore) would handle requests to `/home`. - -### Static Routes - -You can make a page at `/hello/world` by creating a new file at `src/Pages/Hello/World.elm`. - -All module names are converted into lowercase, dash-separated lists (kebab-case) automatically: - -Filepath | URL ---- | --- -`AboutUs.elm` | `/about-us` -`AboutUs/Offices.elm` | `/about-us/offices` -`SomethingWithCapitalLetters.elm` | `/something-with-capital-letters` - -### Dynamic Routes - -You can suffix any file with `_` to indicate a __dynamic route__. A dynamic route passes it's URL parameters within the `Request params` value passed into each `page`. - -Here's an example: - -`src/Pages/Users/Name_.elm` - -URL | `req.params` ---- | --- -`/users/`_`ryan`_ | `{ name = "ryan" }` -`/users/`_`erik`_ | `{ name = "erik" }` -`/users/`_`alexa`_ | `{ name = "alexa" }` - - -### Nested Dynamic Routes - -You can also suffix _folders_ with `_` to support __nested dynamic routes__. - -Here's an example: - -`src/Pages/Users/Name_/Posts/Id_.elm` - -URL | `req.params` ---- | --- -`/users/`_`ryan`_`/posts/`_`123`_ | `{ name = "ryan", id = "123" }` -`/users/`_`ryan`_`/posts/`_`456`_ | `{ name = "ryan", id = "456" }` -`/users/`_`erik`_`/posts/`_`789`_ | `{ name = "erik", id = "789" }` -`/users/`_`abc`_`/posts/`_`xyz`_ | `{ name = "abc", id = "xyz" }` - -## Pages - -Every module in `src/Pages` __must__ expose three things for elm-spa to work as expected: - -1. `Model` - the model of the page. -2. `Msg` - the messages that page sends. -3. `page` - a function returning a `Page Model Msg` - -Every `page` should have this signature: - -```elm -page : Shared.Model -> Request Params -> Page Model Msg -``` - -Here's how you can create pages: - -### `Page.static` - -The simplest page only needs a `view` function: - -```elm -Page.static - { view = view - } -``` - -```elm -view : View msg -``` - -__Note:__ Instead of returning `Html msg`, all views return an application-defined `View msg`– this allows us to use [elm-ui](#todo), [elm-css](#todo), [elm/html](#todo), or your own custom view library! - -(We'll learn more about that later) - -### `Page.sandbox` - -If you need to track state, you can upgrade to a `sandbox` page: - -```elm -Page.sandbox - { init = init - , update = update - , view = view - } -``` - -```elm -init : Model -update : Msg -> Model -> Model -view : Model -> View Msg -``` - -This is based on the [Browser.sandbox](#todo) API in `elm/browser`, which introduces the Elm architecture. - -### `Page.element` - -To send `Cmd msg` or listen for `Sub msg` events, you'll need a more complex API: - -```elm -Page.element - { init = init - , update = update - , view = view - , subscriptions = subscriptions - } -``` - -```elm -init : ( Model, Cmd Msg ) -update : Msg -> Model -> ( Model, Cmd Msg ) -view : Model -> View Msg -subscriptions : Model -> Sub Msg -``` - -`Cmd` let you send things like HTTP requests, while `Sub` let your application listen for DOM events and other external changes. - -## `Request params` - -Each page has access to a `Request params` value, which contains information about the current URL request: - -```elm -request.params -- parameters for dynamic routes -request.query -- a dictionary of query parameters -request.key -- used for programmatic navigation -request.url -- the original raw URL value -``` - -__Note:__ For static routes like `/about-us`, the `request.params` value will be `()`. - -However, for routes like `/users/:id`, `request.params.id` will contain the `String` value for the dynamic `id` parameter. - -## `Shared.Model` - -Sometimes you want to persist information between pages, like a signed-in user. __elm-spa__ provides all pages with a `Shared.Model` value, so you can easily verify that a user is signed in! - -Updates to that `Shared.Model` are possible via `Cmd msg` sent by `Page.element` pages. The official guide will walk through that process in more depth, if you're interested in learning more. - - +Check out the official guide at https://elm-spa.dev! # contributing The CLI is written with TypeScript + NodeJS. Here's how you can get started contributing: ```bash -npm start # first time dev setup +git clone git@github.com:ryannhg/elm-spa # clone the repo +cd elm-spa/src/cli # enter the CLI folder +npm start # run first time dev setup ``` ```bash diff --git a/src/cli/package-lock.json b/src/cli/package-lock.json index 6e61cac..231232d 100644 --- a/src/cli/package-lock.json +++ b/src/cli/package-lock.json @@ -1,12 +1,12 @@ { "name": "elm-spa", - "version": "6.0.3", + "version": "6.0.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "elm-spa", - "version": "6.0.3", + "version": "6.0.4", "license": "BSD-3-Clause", "dependencies": { "chokidar": "3.4.2", diff --git a/src/cli/package.json b/src/cli/package.json index 3d4c313..82ab5f9 100644 --- a/src/cli/package.json +++ b/src/cli/package.json @@ -1,6 +1,6 @@ { "name": "elm-spa", - "version": "6.0.3", + "version": "6.0.4", "description": "single page apps made easy", "bin": "dist/src/index.js", "scripts": { diff --git a/src/cli/src/cli/build.ts b/src/cli/src/cli/build.ts index 3cb925e..ad93a50 100644 --- a/src/cli/src/cli/build.ts +++ b/src/cli/src/cli/build.ts @@ -185,7 +185,7 @@ const compileMainElm = (env: Environment) => async () => { `${RED}!${reset} elm-spa failed to understand an error`, `Please report the output below to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`, `-----`, - error, + JSON.stringify(error, null, 2), `-----`, `${RED}!${reset} elm-spa failed to understand an error`, `Please send the output above to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`, @@ -195,7 +195,21 @@ const compileMainElm = (env: Environment) => async () => { }) } - type ElmError = { + type ElmError + = ElmCompileError + | ElmJsonError + + type ElmCompileError = { + type: 'compile-errors' + errors: ElmProblemError[] + } + + type ElmJsonError = Problem & { + type: 'error', + path: string, + } + + type ElmProblemError = { path: string problems: Problem[] } @@ -212,9 +226,11 @@ const compileMainElm = (env: Environment) => async () => { string: string } - const colorElmError = (output : { errors: ElmError[] }) => { - - const { errors } = output + const colorElmError = (output : ElmError) => { + const errors : ElmProblemError[] = + output.type === 'compile-errors' + ? output.errors + : [ { path: output.path, problems: [output] } ] const strIf = (str: string) => (cond: boolean): string => cond ? str : '' const boldIf = strIf(bold) @@ -222,7 +238,7 @@ const compileMainElm = (env: Environment) => async () => { const repeat = (str: string, num: number, min = 3) => [...Array(num < 0 ? min : num)].map(_ => str).join('') - const errorToString = (error: ElmError): string => { + const errorToString = (error: ElmProblemError): string => { const problemToString = (problem: Problem): string => { const path = error.path.substr(process.cwd().length + 1) return [ diff --git a/src/cli/src/cli/server.ts b/src/cli/src/cli/server.ts index c0e2cff..4714bf2 100644 --- a/src/cli/src/cli/server.ts +++ b/src/cli/src/cli/server.ts @@ -52,9 +52,11 @@ const start = async () => new Promise((resolve, reject) => { const ws = new websocket.server({ httpServer: server }) const script = ` new WebSocket('ws://' + window.location.host, 'elm-spa').onmessage = function () { window.location.reload() } ` ws.on('request', (req) => { - const conn = req.accept('elm-spa', req.origin) - connections[req.remoteAddress] = conn - conn.on('close', () => delete connections[conn.remoteAddress]) + try { + const conn = req.accept('elm-spa', req.origin) + connections[req.remoteAddress] = conn + conn.on('close', () => delete connections[conn.remoteAddress]) + } catch (_) { /* Safely ignores unknown requests */ } }) // Send reload if any files change