mirror of
https://github.com/ryan-haskell/elm-spa.git
synced 2024-09-11 12:35:44 +03:00
Merge pull request #83 from ryannhg/rhg/handle-elm-errors
6.0.4 - Handle more Elm compiler errors
This commit is contained in:
commit
8391379931
@ -4,7 +4,7 @@
|
|||||||
## installation
|
## installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g elm-spa
|
npm install -g elm-spa@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
## usage
|
## usage
|
||||||
@ -13,194 +13,33 @@ npm install -g elm-spa
|
|||||||
$ elm-spa help
|
$ elm-spa help
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
elm-spa – version 6.0.0
|
elm-spa – version 6.0.4
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
elm-spa new . . . . . . . . . create a new project
|
elm-spa new . . . . . . . . . create a new project
|
||||||
elm-spa add <url> . . . . . . . . create a new page
|
elm-spa add <url> . . . . . . . . create a new page
|
||||||
elm-spa build . . . . . . one-time production build
|
elm-spa build . . . . . . one-time production build
|
||||||
elm-spa watch . . . . . . . runs build as you code
|
|
||||||
elm-spa server . . . . . . start a live dev server
|
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!
|
Visit https://elm-spa.dev for more!
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## learn more
|
||||||
|
|
||||||
# Docs
|
Check out the official guide at https://elm-spa.dev!
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# contributing
|
# contributing
|
||||||
|
|
||||||
The CLI is written with TypeScript + NodeJS. Here's how you can get started contributing:
|
The CLI is written with TypeScript + NodeJS. Here's how you can get started contributing:
|
||||||
|
|
||||||
```bash
|
```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
|
```bash
|
||||||
|
4
src/cli/package-lock.json
generated
4
src/cli/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "elm-spa",
|
"name": "elm-spa",
|
||||||
"version": "6.0.3",
|
"version": "6.0.4",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "elm-spa",
|
"name": "elm-spa",
|
||||||
"version": "6.0.3",
|
"version": "6.0.4",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "3.4.2",
|
"chokidar": "3.4.2",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "elm-spa",
|
"name": "elm-spa",
|
||||||
"version": "6.0.3",
|
"version": "6.0.4",
|
||||||
"description": "single page apps made easy",
|
"description": "single page apps made easy",
|
||||||
"bin": "dist/src/index.js",
|
"bin": "dist/src/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -185,7 +185,7 @@ const compileMainElm = (env: Environment) => async () => {
|
|||||||
`${RED}!${reset} elm-spa failed to understand an error`,
|
`${RED}!${reset} elm-spa failed to understand an error`,
|
||||||
`Please report the output below to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`,
|
`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`,
|
`${RED}!${reset} elm-spa failed to understand an error`,
|
||||||
`Please send the output above to ${green}https://github.com/ryannhg/elm-spa/issues${reset}`,
|
`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
|
path: string
|
||||||
problems: Problem[]
|
problems: Problem[]
|
||||||
}
|
}
|
||||||
@ -212,9 +226,11 @@ const compileMainElm = (env: Environment) => async () => {
|
|||||||
string: string
|
string: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorElmError = (output : { errors: ElmError[] }) => {
|
const colorElmError = (output : ElmError) => {
|
||||||
|
const errors : ElmProblemError[] =
|
||||||
const { errors } = output
|
output.type === 'compile-errors'
|
||||||
|
? output.errors
|
||||||
|
: [ { path: output.path, problems: [output] } ]
|
||||||
|
|
||||||
const strIf = (str: string) => (cond: boolean): string => cond ? str : ''
|
const strIf = (str: string) => (cond: boolean): string => cond ? str : ''
|
||||||
const boldIf = strIf(bold)
|
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 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 problemToString = (problem: Problem): string => {
|
||||||
const path = error.path.substr(process.cwd().length + 1)
|
const path = error.path.substr(process.cwd().length + 1)
|
||||||
return [
|
return [
|
||||||
|
@ -52,9 +52,11 @@ const start = async () => new Promise((resolve, reject) => {
|
|||||||
const ws = new websocket.server({ httpServer: server })
|
const ws = new websocket.server({ httpServer: server })
|
||||||
const script = ` new WebSocket('ws://' + window.location.host, 'elm-spa').onmessage = function () { window.location.reload() } `
|
const script = ` new WebSocket('ws://' + window.location.host, 'elm-spa').onmessage = function () { window.location.reload() } `
|
||||||
ws.on('request', (req) => {
|
ws.on('request', (req) => {
|
||||||
const conn = req.accept('elm-spa', req.origin)
|
try {
|
||||||
connections[req.remoteAddress] = conn
|
const conn = req.accept('elm-spa', req.origin)
|
||||||
conn.on('close', () => delete connections[conn.remoteAddress])
|
connections[req.remoteAddress] = conn
|
||||||
|
conn.on('close', () => delete connections[conn.remoteAddress])
|
||||||
|
} catch (_) { /* Safely ignores unknown requests */ }
|
||||||
})
|
})
|
||||||
|
|
||||||
// Send reload if any files change
|
// Send reload if any files change
|
||||||
|
Loading…
Reference in New Issue
Block a user