Comparing version 6.0.6--beta to 6.0.6
{ | ||
"name": "elm-spa", | ||
"version": "6.0.6--beta", | ||
"version": "6.0.6", | ||
"description": "single page apps made easy", | ||
@@ -17,3 +17,3 @@ "bin": "dist/src/index.js", | ||
"type": "git", | ||
"url": "git+https://github.com/ryannhg/elm-spa.git" | ||
"url": "git+https://github.com/ryan-haskell/elm-spa.git" | ||
}, | ||
@@ -29,5 +29,5 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://github.com/ryannhg/elm-spa/issues" | ||
"url": "https://github.com/ryan-haskell/elm-spa/issues" | ||
}, | ||
"homepage": "https://github.com/ryannhg/elm-spa#readme", | ||
"homepage": "https://github.com/ryan-haskell/elm-spa#readme", | ||
"devDependencies": { | ||
@@ -45,4 +45,4 @@ "@types/chokidar": "2.1.3", | ||
"chokidar": "3.4.2", | ||
"elm": "0.19.1-3", | ||
"mime": "2.4.6", | ||
"node-elm-compiler": "5.0.5", | ||
"terser": "5.3.8", | ||
@@ -49,0 +49,0 @@ "websocket": "1.0.32" |
@@ -15,6 +15,10 @@ "use strict"; | ||
add: add_1.default.run, | ||
build: build_1.default.run, | ||
build: build_1.default.build, | ||
server: server_1.default.run, | ||
gen: build_1.default.gen, | ||
watch: watch_1.default.run, | ||
server: server_1.default.run, | ||
help: help_1.default.run | ||
help: help_1.default.run, | ||
// Aliases for Elm folks | ||
init: init_1.default.run, | ||
make: build_1.default.build, | ||
}; |
@@ -40,3 +40,3 @@ "use strict"; | ||
let [url, template] = process.argv.slice(3); | ||
if (!url) { | ||
if (!url || url === '--help') { | ||
return Promise.reject(example); | ||
@@ -54,3 +54,3 @@ } | ||
await File.create(outputFilepath, contents); | ||
return ` ${bold('New page created at:')}\n ${outputFilepath}`; | ||
return ` ${bold('New page created at:')}\n ${outputFilepath}\n`; | ||
} | ||
@@ -71,3 +71,3 @@ }; | ||
Visit ${green(`https://elm-spa.dev/guide/cli`)} for more details! | ||
Visit ${green(`https://elm-spa.dev/guide/01-cli`)} for more details! | ||
`.trim(); | ||
@@ -84,5 +84,5 @@ const template404 = (url, template, suggestions) => { | ||
${suggestions.length ? suggest : ''} | ||
Visit ${green(`https://elm-spa.dev/guide/cli`)} for more details! | ||
Visit ${green(`https://elm-spa.dev/guide/01-cli`)} for more details! | ||
`.trim(); | ||
}; |
@@ -30,12 +30,17 @@ "use strict"; | ||
const File = __importStar(require("../file")); | ||
const readline_1 = require("readline"); | ||
const routes_1 = __importDefault(require("../templates/routes")); | ||
const pages_1 = __importDefault(require("../templates/pages")); | ||
const page_1 = __importDefault(require("../templates/page")); | ||
const request_1 = __importDefault(require("../templates/request")); | ||
const model_1 = __importDefault(require("../templates/model")); | ||
const msg_1 = __importDefault(require("../templates/msg")); | ||
const child_process_1 = __importDefault(require("child_process")); | ||
const params_1 = __importDefault(require("../templates/params")); | ||
const Process = __importStar(require("../process")); | ||
const terser_1 = __importDefault(require("terser")); | ||
const terminal_1 = require("../terminal"); | ||
const utils_1 = require("../templates/utils"); | ||
const _common_1 = require("./_common"); | ||
exports.build = (env) => () => Promise.all([ | ||
const elm = require('node-elm-compiler'); | ||
exports.build = ({ env, runElmMake }) => () => Promise.all([ | ||
createMissingDefaultFiles(), | ||
@@ -45,3 +50,3 @@ _common_1.createMissingAddTemplates() | ||
.then(createGeneratedFiles) | ||
.then(compileMainElm(env)); | ||
.then(runElmMake ? compileMainElm(env) : _ => ` ${terminal_1.check} ${terminal_1.bold}elm-spa${terminal_1.reset} generated new files.`); | ||
const createMissingDefaultFiles = async () => { | ||
@@ -94,3 +99,3 @@ const toAction = async (filepath) => { | ||
``, | ||
`Visit ${terminal_1.colors.green}https://elm-spa.dev/guide/pages${terminal_1.reset} for more details!` | ||
`Visit ${terminal_1.colors.green}https://elm-spa.dev/guide/03-pages${terminal_1.reset} for more details!` | ||
].join('\n'); | ||
@@ -111,2 +116,4 @@ }; | ||
...paramFiles, | ||
{ filepath: ['Page'], contents: page_1.default() }, | ||
{ filepath: ['Request'], contents: request_1.default() }, | ||
{ filepath: ['Gen', 'Route'], contents: routes_1.default(segments, utils_1.options(kindForPage)) }, | ||
@@ -132,33 +139,42 @@ { filepath: ['Gen', 'Pages'], contents: pages_1.default(segments, utils_1.options(kindForPage)) }, | ||
}; | ||
const output = path_1.default.join(config_1.default.folders.dist, 'elm.js'); | ||
const outputFilepath = path_1.default.join(config_1.default.folders.dist, 'elm.js'); | ||
const compileMainElm = (env) => async () => { | ||
await ensureElmIsInstalled(env); | ||
const start = Date.now(); | ||
const elmMake = async () => { | ||
const flags = env === 'development' ? '--debug' : '--optimize'; | ||
const inDevelopment = env === 'development'; | ||
const inProduction = env === 'production'; | ||
const isSrcMainElmDefined = await File.exists(path_1.default.join(config_1.default.folders.src, 'Main.elm')); | ||
const input = isSrcMainElmDefined | ||
const inputFilepath = isSrcMainElmDefined | ||
? path_1.default.join(config_1.default.folders.src, 'Main.elm') | ||
: path_1.default.join(config_1.default.folders.defaults.dest, 'Main.elm'); | ||
if (await File.exists(config_1.default.folders.dist) === false) { | ||
await File.mkdir(config_1.default.folders.dist); | ||
} | ||
return Process.run(`${config_1.default.binaries.elm} make ${input} --output=${output} --report=json ${flags}`) | ||
.catch(colorElmError); | ||
return elm.compileToString(inputFilepath, { | ||
output: outputFilepath, | ||
report: 'json', | ||
debug: inDevelopment, | ||
optimize: inProduction, | ||
}) | ||
.catch((error) => { | ||
try { | ||
return colorElmError(JSON.parse(error.message.split('\n')[1])); | ||
} | ||
catch (_a) { | ||
const { RED, green } = terminal_1.colors; | ||
return Promise.reject([ | ||
`${RED}!${terminal_1.reset} elm-spa failed to understand an error`, | ||
`Please report the output below to ${green}https://github.com/ryan-haskell/elm-spa/issues${terminal_1.reset}`, | ||
`-----`, | ||
JSON.stringify(error, null, 2), | ||
`-----`, | ||
`${RED}!${terminal_1.reset} elm-spa failed to understand an error`, | ||
`Please send the output above to ${green}https://github.com/ryan-haskell/elm-spa/issues${terminal_1.reset}`, | ||
`` | ||
].join('\n\n')); | ||
} | ||
}); | ||
}; | ||
const red = terminal_1.colors.RED; | ||
const green = terminal_1.colors.green; | ||
const colorElmError = (err) => { | ||
let errors = []; | ||
try { | ||
errors = JSON.parse(err).errors || []; | ||
} | ||
catch (e) { | ||
return Promise.reject([ | ||
`${red}Something went wrong with elm-spa.${terminal_1.reset}`, | ||
`Please report this entire error to ${green}https://github.com/ryannhg/elm-spa/issues${terminal_1.reset}`, | ||
`-----`, | ||
err, | ||
`-----` | ||
].join('\n\n')); | ||
} | ||
const colorElmError = (output) => { | ||
const errors = output.type === 'compile-errors' | ||
? output.errors | ||
: [{ path: output.path, problems: [output] }]; | ||
const strIf = (str) => (cond) => cond ? str : ''; | ||
@@ -181,16 +197,65 @@ const boldIf = strIf(terminal_1.bold); | ||
}; | ||
return errors.length | ||
? Promise.reject(errors.map(errorToString).join('\n\n\n')) | ||
: err; | ||
return Promise.reject(errors.map(err => errorToString(err)).join('\n\n\n')); | ||
}; | ||
const success = () => `${terminal_1.check} Build successful! ${terminal_1.dim}(${Date.now() - start}ms)${terminal_1.reset}`; | ||
const minify = () => Process.run(`${config_1.default.binaries.terser} ${output} --compress 'pure_funcs="F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9",pure_getters,keep_fargs=false,unsafe_comps,unsafe' | ${config_1.default.binaries.terser} --mangle --output=${output}`); | ||
const minify = (rawCode) => terser_1.default.minify(rawCode, { compress: { pure_funcs: `F2,F3,F4,F5,F6,F7,F8,F9,A2,A3,A4,A5,A6,A7,A8,A9`.split(','), pure_getters: true, keep_fargs: false, unsafe_comps: true, unsafe: true } }) | ||
.then(intermediate => terser_1.default.minify(intermediate.code || '', { mangle: true })) | ||
.then(minified => File.create(outputFilepath, minified.code || '')); | ||
return (env === 'development') | ||
? elmMake() | ||
.then(_ => success()).catch(error => error) | ||
: elmMake().then(minify) | ||
.then(rawJsCode => File.create(outputFilepath, rawJsCode)) | ||
.then(_ => success()) | ||
.catch(error => error) | ||
: elmMake() | ||
.then(minify) | ||
.then(_ => [success() + '\n']); | ||
}; | ||
const ensureElmIsInstalled = async (environment) => { | ||
await new Promise((resolve, reject) => { | ||
child_process_1.default.exec('elm', (err) => { | ||
if (err) { | ||
if (environment === 'production') { | ||
attemptToInstallViaNpm(resolve, reject); | ||
} | ||
else { | ||
offerToInstallForDeveloper(resolve, reject); | ||
} | ||
} | ||
else { | ||
resolve(undefined); | ||
} | ||
}); | ||
}); | ||
}; | ||
const attemptToInstallViaNpm = (resolve, reject) => { | ||
process.stdout.write(`\n ${terminal_1.bold}Awesome!${terminal_1.reset} Installing Elm via NPM... `); | ||
child_process_1.default.exec(`npm install --global elm@latest-0.19.1`, (err) => { | ||
if (err) { | ||
console.info(terminal_1.error); | ||
reject(` The automatic install didn't work...\n Please visit ${terminal_1.colors.green}https://guide.elm-lang.org/install/elm${terminal_1.reset} to install Elm.\n`); | ||
} | ||
else { | ||
console.info(terminal_1.check); | ||
console.info(` Elm is now installed!`); | ||
resolve(undefined); | ||
} | ||
}); | ||
}; | ||
const offerToInstallForDeveloper = (resolve, reject) => { | ||
const rl = readline_1.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}); | ||
rl.question(`\n${terminal_1.warn} Elm hasn't been installed yet.\n\n May I ${terminal_1.colors.cyan}install${terminal_1.reset} it for you? ${terminal_1.dim}[y/n]${terminal_1.reset} `, answer => { | ||
if (answer.toLowerCase() === 'n') { | ||
reject(` ${terminal_1.bold}No changes made!${terminal_1.reset}\n Please visit ${terminal_1.colors.green}https://guide.elm-lang.org/install/elm${terminal_1.reset} to install Elm.`); | ||
} | ||
else { | ||
attemptToInstallViaNpm(resolve, reject); | ||
} | ||
}); | ||
}; | ||
exports.default = { | ||
run: exports.build('production') | ||
build: exports.build({ env: 'production', runElmMake: true }), | ||
gen: exports.build({ env: 'production', runElmMake: false }) | ||
}; |
@@ -21,6 +21,9 @@ "use strict"; | ||
${bold(`elm-spa ${cyan(`build`)}`)} . . . . . . one-time production build | ||
${bold(`elm-spa ${cyan(`watch`)}`)} . . . . . . . runs build as you code | ||
${bold(`elm-spa ${cyan(`server`)}`)} . . . . . . start a live dev server | ||
Visit ${green(`https://next.elm-spa.dev`)} for more! | ||
Other commands: | ||
${bold(`elm-spa ${cyan(`gen`)}`)} . . . . generates code without elm make | ||
${bold(`elm-spa ${cyan(`watch`)}`)} . . . . runs elm-spa gen as you code | ||
Visit ${green(`https://elm-spa.dev`)} for more! | ||
`; |
@@ -30,13 +30,32 @@ "use strict"; | ||
const terminal_1 = require("../terminal"); | ||
const readline_1 = require("readline"); | ||
// Scaffold a new elm-spa project | ||
exports.default = { | ||
run: () => { | ||
const dest = process.cwd(); | ||
File.copy(config_1.default.folders.init, dest); | ||
try { | ||
fs_1.default.renameSync(path_1.default.join(dest, '_gitignore'), path_1.default.join(dest, '.gitignore')); | ||
run: async () => { | ||
return new Promise(offerToInitializeProject); | ||
} | ||
}; | ||
const offerToInitializeProject = (resolve, reject) => { | ||
const rl = readline_1.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}); | ||
rl.question(`\n May I create a ${terminal_1.colors.cyan}new project${terminal_1.reset} in the ${terminal_1.colors.yellow}current folder${terminal_1.reset}? ${terminal_1.dim}[y/n]${terminal_1.reset} `, answer => { | ||
if (answer.toLowerCase() === 'n') { | ||
reject(` ${terminal_1.bold}No changes made!${terminal_1.reset}`); | ||
} | ||
catch (_) { } | ||
return ` ${terminal_1.bold}New project created in:${terminal_1.reset}\n ${process.cwd()}`; | ||
else { | ||
resolve(initializeNewProject()); | ||
rl.close(); | ||
} | ||
}); | ||
}; | ||
const initializeNewProject = () => { | ||
const dest = process.cwd(); | ||
File.copy(config_1.default.folders.init, dest); | ||
try { | ||
fs_1.default.renameSync(path_1.default.join(dest, '_gitignore'), path_1.default.join(dest, '.gitignore')); | ||
} | ||
catch (_) { } | ||
return ` ${terminal_1.check} ${terminal_1.bold}New project created in:${terminal_1.reset}\n ${process.cwd()}\n`; | ||
}; |
@@ -78,5 +78,8 @@ "use strict"; | ||
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 */ } | ||
}); | ||
@@ -94,5 +97,5 @@ // Send reload if any files change | ||
run: async () => { | ||
const output = await watch_1.watch(); | ||
const output = await watch_1.watch(true); | ||
return start().then(serverOutput => [serverOutput, output]); | ||
} | ||
}; |
@@ -10,4 +10,4 @@ "use strict"; | ||
const config_1 = __importDefault(require("../config")); | ||
exports.watch = () => { | ||
const runBuild = build_1.build('development'); | ||
exports.watch = (runElmMake) => { | ||
const runBuild = build_1.build({ env: 'development', runElmMake }); | ||
chokidar_1.default | ||
@@ -19,2 +19,3 @@ .watch(config_1.default.folders.src, { ignoreInitial: true }) | ||
console.info(output); | ||
console.info(''); | ||
}) | ||
@@ -24,2 +25,3 @@ .catch(reason => { | ||
console.error(reason); | ||
console.info(''); | ||
})); | ||
@@ -29,3 +31,3 @@ return runBuild(); | ||
exports.default = { | ||
run: exports.watch | ||
run: () => exports.watch(false) | ||
}; |
@@ -36,7 +36,4 @@ "use strict"; | ||
}, | ||
binaries: { | ||
elm: `npx elm`, | ||
terser: `npx terser` | ||
}, | ||
defaults: [ | ||
['Auth.elm'], | ||
['Effect.elm'], | ||
@@ -46,4 +43,2 @@ ['Main.elm'], | ||
[`Pages`, `${reserved.notFound}.elm`], | ||
['Page.elm'], | ||
['Request.elm'], | ||
['View.elm'] | ||
@@ -50,0 +45,0 @@ ] |
@@ -12,5 +12,9 @@ #!/usr/bin/env node | ||
build: cli_1.default.build, | ||
gen: cli_1.default.gen, | ||
watch: cli_1.default.watch, | ||
server: cli_1.default.server, | ||
help: cli_1.default.help | ||
help: cli_1.default.help, | ||
// Aliases for Elm folks | ||
init: cli_1.default.new, | ||
make: cli_1.default.build, | ||
}; | ||
@@ -17,0 +21,0 @@ const command = process.argv[2]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = (page) => ` | ||
module Pages.${page.join('.')} exposing (page) | ||
module Pages.${page.join('.')} exposing (view) | ||
@@ -9,6 +9,6 @@ import View exposing (View) | ||
page : View Never | ||
page = | ||
view : View msg | ||
view = | ||
View.placeholder "${page.join('.')}" | ||
`.trimLeft(); |
@@ -9,3 +9,3 @@ "use strict"; | ||
import Effect exposing (Effect) | ||
import ElmSpa.Internals.Page | ||
import ElmSpa.Page | ||
${utils_1.paramsImports(pages)} | ||
@@ -64,7 +64,7 @@ import Gen.Model as Model | ||
type alias Bundle params model msg = | ||
ElmSpa.Internals.Page.Bundle params model msg Shared.Model (Effect Msg) Model Msg (View Msg) | ||
ElmSpa.Page.Bundle params model msg Shared.Model (Effect Msg) Model Msg (View Msg) | ||
bundle page toModel toMsg = | ||
ElmSpa.Internals.Page.bundle | ||
ElmSpa.Page.bundle | ||
{ redirecting = | ||
@@ -71,0 +71,0 @@ { model = Model.Redirecting_ |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const config_1 = __importDefault(require("../config")); | ||
const utils_1 = require("./utils"); | ||
const routeParserOrder = (pages) => [...pages].sort(sorter); | ||
const isHomepage = (list) => list.join('.') === config_1.default.reserved.homepage; | ||
const isDynamic = (piece) => piece.endsWith('_'); | ||
const alphaSorter = (a, b) => a < b ? -1 : b < a ? 1 : 0; | ||
const sorter = (a, b) => { | ||
if (isHomepage(a)) | ||
return -1; | ||
if (isHomepage(b)) | ||
return 1; | ||
if (a.length < b.length) | ||
return -1; | ||
if (a.length > b.length) | ||
return 1; | ||
for (let i in a) { | ||
const [isA, isB] = [isDynamic(a[i]), isDynamic(b[i])]; | ||
if (isA && isB) | ||
return alphaSorter(a[i], b[i]); | ||
if (isA) | ||
return 1; | ||
if (isB) | ||
return -1; | ||
} | ||
return 0; | ||
}; | ||
exports.default = (pages, _options) => ` | ||
@@ -26,3 +54,3 @@ module Gen.Route exposing | ||
routes = | ||
${utils_1.indent(utils_1.routeParserList(pages), 1)} | ||
${utils_1.indent(utils_1.routeParserList(routeParserOrder(pages)), 1)} | ||
@@ -29,0 +57,0 @@ |
@@ -216,6 +216,14 @@ "use strict"; | ||
}; | ||
// Used in place of sophisticated AST parsing | ||
const exposes = (keyword) => (elmSourceCode) => new RegExp(`module\\s(\\S)+\\sexposing(\\s)+\\([^\\)]*${keyword}[^\\)]*\\)`, 'm').test(elmSourceCode); | ||
const exposes = (value) => (str) => { | ||
const regex = new RegExp('^module\\s+[^\\s]+\\s+exposing\\s+\\(((?:\\.\\)|[^)])+)\\)'); | ||
const match = (str.match(regex) || [])[1]; | ||
if (match) { | ||
return match.split(',').filter(a => a).map(a => a.trim()).includes(value); | ||
} | ||
else { | ||
return false; | ||
} | ||
}; | ||
exports.exposesModel = exposes('Model'); | ||
exports.exposesMsg = exposes('Msg'); | ||
exports.exposesMsg = (str) => exposes('Msg')(str) || exposes('Msg(..)')(str); | ||
exports.exposesPageFunction = exposes('page'); | ||
@@ -222,0 +230,0 @@ exports.exposesViewFunction = exposes('view'); |
{ | ||
"name": "elm-spa", | ||
"version": "6.0.6--beta", | ||
"version": "6.0.6", | ||
"description": "single page apps made easy", | ||
@@ -17,3 +17,3 @@ "bin": "dist/src/index.js", | ||
"type": "git", | ||
"url": "git+https://github.com/ryannhg/elm-spa.git" | ||
"url": "git+https://github.com/ryan-haskell/elm-spa.git" | ||
}, | ||
@@ -29,5 +29,5 @@ "keywords": [ | ||
"bugs": { | ||
"url": "https://github.com/ryannhg/elm-spa/issues" | ||
"url": "https://github.com/ryan-haskell/elm-spa/issues" | ||
}, | ||
"homepage": "https://github.com/ryannhg/elm-spa#readme", | ||
"homepage": "https://github.com/ryan-haskell/elm-spa#readme", | ||
"devDependencies": { | ||
@@ -45,7 +45,7 @@ "@types/chokidar": "2.1.3", | ||
"chokidar": "3.4.2", | ||
"elm": "0.19.1-3", | ||
"mime": "2.4.6", | ||
"node-elm-compiler": "5.0.5", | ||
"terser": "5.3.8", | ||
"websocket": "1.0.32" | ||
} | ||
} | ||
} |
183
README.md
@@ -7,3 +7,3 @@ # elm-spa cli | ||
```bash | ||
npm install -g elm-spa | ||
npm install -g elm-spa@latest | ||
``` | ||
@@ -17,3 +17,3 @@ | ||
``` | ||
elm-spa – version 6.0.0 | ||
elm-spa – version 6.0.6 | ||
@@ -24,178 +24,15 @@ Commands: | ||
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 | ||
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 | ||
@@ -206,3 +43,5 @@ | ||
```bash | ||
npm start # first time dev setup | ||
git clone git@github.com:ryan-haskell/elm-spa # clone the repo | ||
cd elm-spa/src/cli # enter the CLI folder | ||
npm start # run first time dev setup | ||
``` | ||
@@ -209,0 +48,0 @@ |
@@ -16,3 +16,3 @@ { | ||
"elm/url": "1.0.0", | ||
"ryannhg/elm-spa": "5.1.0" | ||
"ryan-haskell/elm-spa": "1.0.0" | ||
}, | ||
@@ -28,2 +28,2 @@ "indirect": { | ||
} | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1345
0
70958
40
59
+ Addednode-elm-compiler@5.0.5
+ Addedbalanced-match@1.0.2(transitive)
+ Addedbrace-expansion@1.1.11(transitive)
+ Addedconcat-map@0.0.1(transitive)
+ Addedcross-spawn@6.0.5(transitive)
+ Addedfind-elm-dependencies@2.0.4(transitive)
+ Addedfirstline@1.3.1(transitive)
+ Addedfs.realpath@1.0.0(transitive)
+ Addedglob@7.2.3(transitive)
+ Addedinflight@1.0.6(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedisexe@2.0.0(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedminimatch@3.1.2(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedmkdirp@0.5.6(transitive)
+ Addednice-try@1.0.5(transitive)
+ Addednode-elm-compiler@5.0.5(transitive)
+ Addedonce@1.4.0(transitive)
+ Addedpath-is-absolute@1.0.1(transitive)
+ Addedpath-key@2.0.1(transitive)
+ Addedrimraf@2.6.3(transitive)
+ Addedsemver@5.7.2(transitive)
+ Addedshebang-command@1.2.0(transitive)
+ Addedshebang-regex@1.0.0(transitive)
+ Addedtemp@0.9.4(transitive)
+ Addedwhich@1.3.1(transitive)
+ Addedwrappy@1.0.2(transitive)
- Removedelm@0.19.1-3
- Removedajv@6.12.6(transitive)
- Removedasn1@0.2.6(transitive)
- Removedassert-plus@1.0.0(transitive)
- Removedasynckit@0.4.0(transitive)
- Removedaws-sign2@0.7.0(transitive)
- Removedaws4@1.13.2(transitive)
- Removedbcrypt-pbkdf@1.0.2(transitive)
- Removedcaseless@0.12.0(transitive)
- Removedcombined-stream@1.0.8(transitive)
- Removedcore-util-is@1.0.2(transitive)
- Removeddashdash@1.14.1(transitive)
- Removeddelayed-stream@1.0.0(transitive)
- Removedecc-jsbn@0.1.2(transitive)
- Removedelm@0.19.1-3(transitive)
- Removedextend@3.0.2(transitive)
- Removedextsprintf@1.3.0(transitive)
- Removedfast-deep-equal@3.1.3(transitive)
- Removedfast-json-stable-stringify@2.1.0(transitive)
- Removedforever-agent@0.6.1(transitive)
- Removedform-data@2.3.3(transitive)
- Removedgetpass@0.1.7(transitive)
- Removedhar-schema@2.0.0(transitive)
- Removedhar-validator@5.1.5(transitive)
- Removedhttp-signature@1.2.0(transitive)
- Removedisstream@0.1.2(transitive)
- Removedjsbn@0.1.1(transitive)
- Removedjson-schema@0.4.0(transitive)
- Removedjson-schema-traverse@0.4.1(transitive)
- Removedjson-stringify-safe@5.0.1(transitive)
- Removedjsprim@1.4.2(transitive)
- Removedmime-db@1.52.0(transitive)
- Removedmime-types@2.1.35(transitive)
- Removedoauth-sign@0.9.0(transitive)
- Removedperformance-now@2.1.0(transitive)
- Removedpsl@1.10.0(transitive)
- Removedpunycode@2.3.1(transitive)
- Removedqs@6.5.3(transitive)
- Removedrequest@2.88.2(transitive)
- Removedsafe-buffer@5.2.1(transitive)
- Removedsafer-buffer@2.1.2(transitive)
- Removedsshpk@1.18.0(transitive)
- Removedtough-cookie@2.5.0(transitive)
- Removedtunnel-agent@0.6.0(transitive)
- Removedtweetnacl@0.14.5(transitive)
- Removeduri-js@4.4.1(transitive)
- Removeduuid@3.4.0(transitive)
- Removedverror@1.10.0(transitive)