Comparing version 6.0.0--beta to 6.0.0
{ | ||
"name": "elm-spa", | ||
"version": "6.0.0--beta", | ||
"version": "6.0.0", | ||
"description": "single page apps made easy", | ||
@@ -43,4 +43,4 @@ "bin": "dist/src/index.js", | ||
"chokidar": "3.4.2", | ||
"elm": "0.19.1-3", | ||
"mime": "2.4.6", | ||
"node-elm-compiler": "5.0.5", | ||
"terser": "5.3.8", | ||
@@ -47,0 +47,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, | ||
}; |
@@ -30,15 +30,56 @@ "use strict"; | ||
const add_1 = __importDefault(require("../templates/add")); | ||
const terminal_1 = require("../terminal"); | ||
const _common_1 = require("./_common"); | ||
const bold = (str) => '\x1b[1m' + str + '\x1b[0m'; | ||
const cyan = (str) => '\x1b[36m' + str + '\x1b[0m'; | ||
const green = (str) => '\x1b[32m' + str + '\x1b[0m'; | ||
const yellow = (str) => '\x1b[33m' + str + '\x1b[0m'; | ||
const pink = (str) => '\x1b[35m' + str + '\x1b[0m'; | ||
// Scaffold a new elm-spa page | ||
exports.default = { | ||
run: async () => { | ||
let url = process.argv[3]; | ||
if (!url) { | ||
return Promise.reject(`${terminal_1.bold}elm-spa add${terminal_1.reset} requires a ${terminal_1.bold}url${terminal_1.reset} parameter!`); | ||
let [url, template] = process.argv.slice(3); | ||
if (!url || url === '--help') { | ||
return Promise.reject(example); | ||
} | ||
const page = utils_1.urlArgumentToPages(url); | ||
const filepath = path_1.default.join(config_1.default.folders.pages.src, ...page) + '.elm'; | ||
await File.create(filepath, add_1.default(page)); | ||
return ` ${terminal_1.bold}New page created at:${terminal_1.reset}\n ${filepath}`; | ||
const outputFilepath = path_1.default.join(config_1.default.folders.pages.src, ...page) + '.elm'; | ||
let contents = add_1.default(page); | ||
if (template) { | ||
const availableTemplates = await _common_1.createMissingAddTemplates(); | ||
const templateSrc = path_1.default.join(config_1.default.folders.templates.user, template + '.elm'); | ||
contents = await File.read(templateSrc).catch(_ => Promise.reject(template404(url, template, availableTemplates))); | ||
contents = contents.split('{{module}}').join(page.join('.')); | ||
} | ||
await File.create(outputFilepath, contents); | ||
return ` ${bold('New page created at:')}\n ${outputFilepath}\n`; | ||
} | ||
}; | ||
const example = ' ' + ` | ||
${bold(`elm-spa add`)} <url> [template] | ||
Examples: | ||
${bold(`elm-spa ${cyan(`add`)}`)} ${yellow('/')} . . . . . . . . adds a homepage | ||
${bold(`elm-spa ${cyan(`add`)}`)} ${yellow('/about-us')} . . . . adds a static route | ||
${bold(`elm-spa ${cyan(`add`)}`)} ${yellow('/people/:id')} . . . adds a dynamic route | ||
Examples with templates: | ||
${bold(`elm-spa ${cyan(`add`)}`)} ${yellow('/')} ${pink('static')} | ||
${bold(`elm-spa ${cyan(`add`)}`)} ${yellow('/about-us')} ${pink('sandbox')} | ||
${bold(`elm-spa ${cyan(`add`)}`)} ${yellow('/people/:id')} ${pink('element')} | ||
Visit ${green(`https://elm-spa.dev/guide/cli`)} for more details! | ||
`.trim(); | ||
const template404 = (url, template, suggestions) => { | ||
const suggest = ` | ||
Here are the available templates: | ||
${suggestions.map(temp => `${yellow(`elm-spa add`)} ${yellow(url)} ${bold(pink(temp))}`).join('\n ')} | ||
`; | ||
return ' ' + ` | ||
${bold(`elm-spa`)} couldn't find a ${bold(pink(template))} template | ||
in the ${cyan('.elm-spa/templates')} folder. | ||
${suggestions.length ? suggest : ''} | ||
Visit ${green(`https://elm-spa.dev/guide/cli`)} for more details! | ||
`.trim(); | ||
}; |
@@ -30,13 +30,22 @@ "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"); | ||
exports.build = (env) => () => createMissingDefaultFiles() | ||
const _common_1 = require("./_common"); | ||
const elm = require('node-elm-compiler'); | ||
exports.build = ({ env, runElmMake }) => () => Promise.all([ | ||
createMissingDefaultFiles(), | ||
_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 () => { | ||
@@ -66,24 +75,48 @@ const toAction = async (filepath) => { | ||
}; | ||
const scanForStaticPages = async (entries) => { | ||
const getFilepathSegments = async (entries) => { | ||
const contents = await Promise.all(entries.map(e => File.read(e.filepath))); | ||
return contents | ||
.map((content, i) => utils_1.isStaticPage(content) ? i : undefined) | ||
.filter(a => typeof a === 'number') | ||
.map((i) => entries[i].segments); | ||
return Promise.all(entries.map(async (entry, i) => { | ||
const c = contents[i]; | ||
const kind = await (utils_1.isStandardPage(c) ? Promise.resolve('page') | ||
: utils_1.isStaticPage(c) ? Promise.resolve('static-page') | ||
: utils_1.isStaticView(c) ? Promise.resolve('view') | ||
: Promise.reject(invalidExportsMessage(entry))); | ||
return { kind, entry }; | ||
})); | ||
}; | ||
const invalidExportsMessage = (entry) => { | ||
const moduleName = `${terminal_1.bold}Pages.${entry.segments.join('.')}${terminal_1.reset}`; | ||
const cyan = (str) => `${terminal_1.colors.cyan}${str}${terminal_1.reset}`; | ||
return [ | ||
`${terminal_1.colors.RED}!${terminal_1.reset} Ran into a problem at ${terminal_1.bold}${terminal_1.colors.yellow}src/Pages/${entry.segments.join('/')}.elm${terminal_1.reset}`, | ||
``, | ||
`${terminal_1.bold}elm-spa${terminal_1.reset} expected one of these module definitions:`, | ||
``, | ||
` ${terminal_1.dot} module ${moduleName} exposing (${cyan('view')})`, | ||
` ${terminal_1.dot} module ${moduleName} exposing (${cyan('page')})`, | ||
` ${terminal_1.dot} module ${moduleName} exposing (${cyan('Model')}, ${cyan('Msg')}, ${cyan('page')})`, | ||
``, | ||
`Visit ${terminal_1.colors.green}https://elm-spa.dev/guide/pages${terminal_1.reset} for more details!` | ||
].join('\n'); | ||
}; | ||
const createGeneratedFiles = async () => { | ||
const entries = await getAllPageEntries(); | ||
const filepaths = entries.map(e => e.segments); | ||
const staticPages = await scanForStaticPages(entries); | ||
const isStatic = (path) => staticPages.map(p => p.join('.')).includes(path.join('.')); | ||
const paramFiles = filepaths.map(filepath => ({ | ||
const segments = entries.map(e => e.segments); | ||
const filepathSegments = await getFilepathSegments(entries); | ||
const kindForPage = (p) => filepathSegments | ||
.filter(item => item.entry.segments.join('.') == p.join('.')) | ||
.map(fps => fps.kind)[0] || 'page'; | ||
const paramFiles = segments.map(filepath => ({ | ||
filepath: ['Gen', 'Params', ...filepath], | ||
contents: params_1.default(filepath, { isStatic }) | ||
contents: params_1.default(filepath, utils_1.options(kindForPage)) | ||
})); | ||
const filesToCreate = [ | ||
...paramFiles, | ||
{ filepath: ['Gen', 'Route'], contents: routes_1.default(filepaths, { isStatic }) }, | ||
{ filepath: ['Gen', 'Pages'], contents: pages_1.default(filepaths, { isStatic }) }, | ||
{ filepath: ['Gen', 'Model'], contents: model_1.default(filepaths, { isStatic }) }, | ||
{ filepath: ['Gen', 'Msg'], contents: msg_1.default(filepaths, { isStatic }) } | ||
{ filepath: ['Page'], contents: page_1.default() }, | ||
{ filepath: ['Request'], contents: request_1.default() }, | ||
{ filepath: ['Gen', 'Route'], contents: routes_1.default(segments, utils_1.options(kindForPage)) }, | ||
{ filepath: ['Gen', 'Route'], contents: routes_1.default(segments, utils_1.options(kindForPage)) }, | ||
{ filepath: ['Gen', 'Pages'], contents: pages_1.default(segments, utils_1.options(kindForPage)) }, | ||
{ filepath: ['Gen', 'Model'], contents: model_1.default(segments, utils_1.options(kindForPage)) }, | ||
{ filepath: ['Gen', 'Msg'], contents: msg_1.default(segments, utils_1.options(kindForPage)) } | ||
]; | ||
@@ -105,19 +138,40 @@ return Promise.all(filesToCreate.map(({ filepath, contents }) => File.create(path_1.default.join(config_1.default.folders.generated, ...filepath) + '.elm', contents))); | ||
}; | ||
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/ryannhg/elm-spa/issues${terminal_1.reset}`, | ||
`-----`, | ||
error, | ||
`-----`, | ||
`${RED}!${terminal_1.reset} elm-spa failed to understand an error`, | ||
`Please send the output above to ${green}https://github.com/ryannhg/elm-spa/issues${terminal_1.reset}`, | ||
`` | ||
].join('\n\n')); | ||
} | ||
}); | ||
}; | ||
const colorElmError = (err) => { | ||
const errors = JSON.parse(err).errors || []; | ||
const colorElmError = (output) => { | ||
const { errors } = output; | ||
const strIf = (str) => (cond) => cond ? str : ''; | ||
@@ -140,16 +194,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`; | ||
}; |
@@ -93,5 +93,5 @@ "use strict"; | ||
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) | ||
}; |
@@ -9,2 +9,3 @@ "use strict"; | ||
homepage: 'Home_', | ||
redirecting: 'Redirecting_', | ||
notFound: 'NotFound' | ||
@@ -28,2 +29,6 @@ }; | ||
generated: path_1.default.join(cwd, '.elm-spa', 'generated'), | ||
templates: { | ||
defaults: path_1.default.join(root, 'src', 'templates', 'add'), | ||
user: path_1.default.join(cwd, '.elm-spa', 'templates') | ||
}, | ||
package: path_1.default.join(cwd, '.elm-spa', 'package'), | ||
@@ -33,12 +38,8 @@ public: path_1.default.join(cwd, 'public'), | ||
}, | ||
binaries: { | ||
elm: path_1.default.join(root, 'node_modules', '.bin', 'elm'), | ||
terser: path_1.default.join(root, 'node_modules', '.bin', 'terser') | ||
}, | ||
defaults: [ | ||
['Auth.elm'], | ||
['Effect.elm'], | ||
['Main.elm'], | ||
['Shared.elm'], | ||
[`Pages`, `${reserved.notFound}.elm`], | ||
['Page.elm'], | ||
['Request.elm'], | ||
['View.elm'] | ||
@@ -45,0 +46,0 @@ ] |
@@ -61,3 +61,3 @@ "use strict"; | ||
try { | ||
fs_2.default.mkdirSync(dest); | ||
fs_2.default.mkdirSync(dest, { recursive: true }); | ||
} | ||
@@ -64,0 +64,0 @@ catch (_) { } |
@@ -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]; |
@@ -5,2 +5,4 @@ "use strict"; | ||
const child_process_1 = require("child_process"); | ||
exports.run = (cmd) => new Promise((resolve, reject) => child_process_1.exec(cmd, (err, stdout, stderr) => err ? reject(stderr) : resolve(stdout))); | ||
exports.run = (cmd) => new Promise((resolve, reject) => child_process_1.exec(cmd, (err, stdout, stderr) => err | ||
? reject(stderr.split('npm ERR!')[0] || stderr) | ||
: resolve(stdout))); |
"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(); |
"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"); | ||
@@ -11,4 +15,4 @@ exports.default = (pages, options) => ` | ||
${utils_1.pagesModelDefinition(pages, options)} | ||
${utils_1.pagesModelDefinition([[config_1.default.reserved.redirecting]].concat(pages), options)} | ||
`.trimLeft(); |
@@ -11,4 +11,4 @@ "use strict"; | ||
${utils_1.pagesMsgDefinition(pages.filter(path => options.isStatic(path) === false))} | ||
${utils_1.pagesMsgDefinition(pages.filter(path => !options.isStaticView(path)), options)} | ||
`.trimLeft(); |
@@ -8,3 +8,4 @@ "use strict"; | ||
import Browser.Navigation exposing (Key) | ||
import Request exposing (Request) | ||
import Effect exposing (Effect) | ||
import ElmSpa.Page | ||
${utils_1.paramsImports(pages)} | ||
@@ -16,2 +17,3 @@ import Gen.Model as Model | ||
${utils_1.pagesImports(pages)} | ||
import Request exposing (Request) | ||
import Shared | ||
@@ -31,3 +33,3 @@ import Task | ||
init : Route -> Shared.Model -> Url -> Key -> ( Model, Cmd Msg, Cmd Shared.Msg ) | ||
init : Route -> Shared.Model -> Url -> Key -> ( Model, Effect Msg ) | ||
init route = | ||
@@ -37,5 +39,5 @@ ${utils_1.pagesInitBody(pages)} | ||
update : Msg -> Model -> Shared.Model -> Url -> Key -> ( Model, Cmd Msg, Cmd Shared.Msg ) | ||
update : Msg -> Model -> Shared.Model -> Url -> Key -> ( Model, Effect Msg ) | ||
update msg_ model_ = | ||
${utils_1.pagesUpdateBody(pages.filter(page => options.isStatic(page) === false), options)} | ||
${utils_1.pagesUpdateBody(pages.filter(page => !options.isStaticView(page)), options)} | ||
${pages.length > 1 ? utils_1.pagesUpdateCatchAll : ''} | ||
@@ -65,45 +67,20 @@ | ||
type alias Bundle params model msg = | ||
{ init : params -> Shared.Model -> Url -> Key -> ( Model, Cmd Msg, Cmd Shared.Msg ) | ||
, update : params -> msg -> model -> Shared.Model -> Url -> Key -> ( Model, Cmd Msg, Cmd Shared.Msg ) | ||
, view : params -> model -> Shared.Model -> Url -> Key -> View Msg | ||
, subscriptions : params -> model -> Shared.Model -> Url -> Key -> Sub Msg | ||
} | ||
ElmSpa.Page.Bundle params model msg Shared.Model (Effect Msg) Model Msg (View Msg) | ||
bundle : | ||
(Shared.Model -> Request params -> Page model msg) | ||
-> (params -> model -> Model) | ||
-> (msg -> Msg) | ||
-> Bundle params model msg | ||
bundle page toModel toMsg = | ||
let | ||
mapTriple : | ||
params | ||
-> ( model, Cmd msg, List Shared.Msg ) | ||
-> ( Model, Cmd Msg, Cmd Shared.Msg ) | ||
mapTriple params ( model, cmd, sharedMsgList ) = | ||
( toModel params model | ||
, Cmd.map toMsg cmd | ||
, sharedMsgList | ||
|> List.map (Task.succeed >> Task.perform identity) | ||
|> Cmd.batch | ||
) | ||
in | ||
{ init = | ||
\\params shared url key -> | ||
(page shared (Request.create params url key)).init () | ||
|> mapTriple params | ||
, update = | ||
\\params msg model shared url key -> | ||
(page shared (Request.create params url key)).update msg model | ||
|> mapTriple params | ||
, view = | ||
\\params model shared url key -> | ||
(page shared (Request.create params url key)).view model | ||
|> View.map toMsg | ||
, subscriptions = | ||
\\params model shared url key -> | ||
(page shared (Request.create params url key)).subscriptions model | ||
|> Sub.map toMsg | ||
} | ||
ElmSpa.Page.bundle | ||
{ redirecting = | ||
{ model = Model.Redirecting_ | ||
, view = View.none | ||
} | ||
, toRoute = Route.fromUrl | ||
, toUrl = Route.toHref | ||
, fromCmd = Effect.fromCmd | ||
, mapEffect = Effect.map toMsg | ||
, mapView = View.map toMsg | ||
, toModel = toModel | ||
, toMsg = toMsg | ||
, page = page | ||
} | ||
@@ -117,4 +94,4 @@ | ||
static view_ toModel = | ||
{ init = \\params _ _ _ -> ( toModel params, Cmd.none, Cmd.none ) | ||
, update = \\params _ _ _ _ _ -> ( toModel params, Cmd.none, Cmd.none ) | ||
{ init = \\params _ _ _ -> ( toModel params, Effect.none ) | ||
, update = \\params _ _ _ _ _ -> ( toModel params, Effect.none ) | ||
, view = \\_ _ _ _ _ -> View.map never view_ | ||
@@ -121,0 +98,0 @@ , subscriptions = \\_ _ _ _ _ -> Sub.none |
"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 @@ |
@@ -6,4 +6,10 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.isStaticPage = exports.exposesMsg = exports.exposesModel = exports.pagesSubscriptionsBody = exports.pagesViewBody = exports.pagesUpdateCatchAll = exports.pagesUpdateBody = exports.pagesInitBody = exports.pagesBundleDefinition = exports.pagesBundleAnnotation = exports.pagesMsgDefinition = exports.pagesModelDefinition = exports.pagesImports = exports.paramsImports = exports.routeToHrefSegments = exports.routeToHref = exports.routeParserList = exports.routeTypeDefinition = exports.routeTypeVariant = exports.routeParserMap = exports.routeParser = exports.paramsRouteParserMap = exports.routeParameters = exports.indent = exports.customType = exports.multilineRecord = exports.multilineList = exports.routeVariant = exports.urlArgumentToPages = void 0; | ||
exports.isStaticView = exports.isStaticPage = exports.isStandardPage = exports.exposesViewFunction = exports.exposesPageFunction = exports.exposesMsg = exports.exposesModel = exports.pagesSubscriptionsBody = exports.pagesViewBody = exports.pagesUpdateCatchAll = exports.pagesUpdateBody = exports.pagesInitBody = exports.pagesBundleDefinition = exports.pagesBundleAnnotation = exports.pagesMsgDefinition = exports.pagesModelDefinition = exports.pagesImports = exports.paramsImports = exports.routeToHrefSegments = exports.routeToHref = exports.routeParserList = exports.routeTypeDefinition = exports.routeTypeVariant = exports.routeParserMap = exports.routeParser = exports.paramsRouteParserMap = exports.routeParameters = exports.indent = exports.customType = exports.multilineRecord = exports.multilineList = exports.routeVariant = exports.urlArgumentToPages = exports.options = void 0; | ||
const config_1 = __importDefault(require("../config")); | ||
exports.options = (kind) => ({ | ||
kind, | ||
isStaticView: p => kind(p) === 'view', | ||
isStaticPage: p => kind(p) === 'static-page', | ||
isStandardPage: p => kind(p) === 'page', | ||
}); | ||
// [ 'Home_' ] => true | ||
@@ -116,19 +122,33 @@ const isHomepage = (path) => path.join('') === config_1.default.reserved.homepage; | ||
const pageModuleName = (path) => `Pages.${path.join('.')}`; | ||
exports.pagesModelDefinition = (paths, options) => exports.customType('Model', paths.map(path => options.isStatic(path) | ||
? `${modelVariant(path)} ${params(path)}` | ||
: `${modelVariant(path)} ${params(path)} ${model(path)}`)); | ||
exports.pagesMsgDefinition = (paths) => (paths.length === 0) | ||
exports.pagesModelDefinition = (paths, options) => exports.customType('Model', paths.map(path => (() => { | ||
if (path[0] === config_1.default.reserved.redirecting) | ||
return config_1.default.reserved.redirecting; | ||
switch (options.kind(path)) { | ||
case 'view': return `${modelVariant(path)} ${params(path)}`; | ||
case 'static-page': return `${modelVariant(path)} ${params(path)} ${model(path, options)}`; | ||
case 'page': return `${modelVariant(path)} ${params(path)} ${model(path, options)}`; | ||
} | ||
})())); | ||
exports.pagesMsgDefinition = (paths, options) => (paths.length === 0) | ||
? `type Msg = None` | ||
: exports.customType('Msg', paths.map(path => `${msgVariant(path)} ${msg(path)}`)); | ||
: exports.customType('Msg', paths.map(path => `${msgVariant(path)} ${msg(path, options)}`)); | ||
exports.pagesBundleAnnotation = (paths, options) => exports.indent(exports.multilineRecord(':', paths.map(path => [ | ||
bundleName(path), | ||
options.isStatic(path) | ||
? `Static ${params(path)}` | ||
: `Bundle ${params(path)} ${model(path)} ${msg(path)}` | ||
(() => { | ||
switch (options.kind(path)) { | ||
case 'view': return `Static ${params(path)}`; | ||
case 'static-page': return `Bundle ${params(path)} ${model(path, options)} ${msg(path, options)}`; | ||
case `page`: return `Bundle ${params(path)} ${model(path, options)} ${msg(path, options)}`; | ||
} | ||
})() | ||
]))); | ||
exports.pagesBundleDefinition = (paths, options) => exports.indent(exports.multilineRecord('=', paths.map(path => [ | ||
bundleName(path), | ||
options.isStatic(path) | ||
? `static ${pageModuleName(path)}.page Model.${modelVariant(path)}` | ||
: `bundle ${pageModuleName(path)}.page Model.${modelVariant(path)} Msg.${msgVariant(path)}` | ||
(() => { | ||
switch (options.kind(path)) { | ||
case 'view': return `static ${pageModuleName(path)}.view Model.${modelVariant(path)}`; | ||
case 'static-page': return `bundle ${pageModuleName(path)}.page Model.${modelVariant(path)} Msg.${msgVariant(path)}`; | ||
case `page`: return `bundle ${pageModuleName(path)}.page Model.${modelVariant(path)} Msg.${msgVariant(path)}`; | ||
} | ||
})() | ||
]))); | ||
@@ -138,6 +158,14 @@ const bundleName = (path) => path.map(fromPascalToCamelCase).join('__'); | ||
const params = (path) => `${paramsModule(path)}.Params`; | ||
const model = (path) => `Pages.${path.join('.')}.Model`; | ||
const model = (path, options) => { | ||
switch (options.kind(path)) { | ||
case 'view': return `()`; | ||
case 'static-page': return `()`; | ||
case 'page': return `Pages.${path.join('.')}.Model`; | ||
} | ||
}; | ||
const modelVariant = (path) => `${path.join('__')}`; | ||
const msgVariant = (path) => `${path.join('__')}`; | ||
const msg = (path) => `Pages.${path.join('.')}.Msg`; | ||
const msg = (path, options) => options.isStandardPage(path) | ||
? `Pages.${path.join('.')}.Msg` | ||
: `Never`; | ||
exports.pagesInitBody = (paths) => exports.indent(caseExpression(paths, { | ||
@@ -155,4 +183,4 @@ variable: 'route', | ||
_ -> | ||
\\_ _ _ -> ( model_, Cmd.none, Cmd.none )`; | ||
exports.pagesViewBody = (paths, options) => exports.indent(caseExpression(paths, { | ||
\\_ _ _ -> ( model_, Effect.none )`; | ||
exports.pagesViewBody = (paths, options) => exports.indent(caseExpressionWithRedirectingModel(`\\_ _ _ -> View.none`, paths, { | ||
variable: 'model_', | ||
@@ -162,3 +190,3 @@ condition: path => `${destructuredModel(path, options)}`, | ||
})); | ||
exports.pagesSubscriptionsBody = (paths, options) => exports.indent(caseExpression(paths, { | ||
exports.pagesSubscriptionsBody = (paths, options) => exports.indent(caseExpressionWithRedirectingModel(`\\_ _ _ -> Sub.none`, paths, { | ||
variable: 'model_', | ||
@@ -168,14 +196,45 @@ condition: path => `${destructuredModel(path, options)}`, | ||
})); | ||
const caseExpressionWithRedirectingModel = (fallback, items, options) => caseExpression([[config_1.default.reserved.redirecting]].concat(items), { | ||
variable: options.variable, | ||
condition: (item) => item[0] === config_1.default.reserved.redirecting | ||
? `Model.${config_1.default.reserved.redirecting}` | ||
: options.condition(item), | ||
result: (item) => item[0] === config_1.default.reserved.redirecting | ||
? fallback | ||
: options.result(item) | ||
}); | ||
const caseExpression = (items, options) => `case ${options.variable} of | ||
${items.map(item => ` ${options.condition(item)} ->\n ${options.result(item)}`).join('\n\n')}`; | ||
const destructuredModel = (path, options) => options.isStatic(path) | ||
? `Model.${modelVariant(path)} params` | ||
: `Model.${modelVariant(path)} params model`; | ||
const pageModelArguments = (path, options) => options.isStatic(path) | ||
? `params ()` | ||
: `params model`; | ||
// Used in place of sophisticated AST parsing | ||
const exposes = (keyword) => (elmSourceCode) => new RegExp(`module\\s(\\S)+\\sexposing(\\s)+\\([^\\)]*${keyword}[^\\)]*\\)`, 'm').test(elmSourceCode); | ||
const destructuredModel = (path, options) => { | ||
switch (options.kind(path)) { | ||
case 'view': return `Model.${modelVariant(path)} params`; | ||
case 'static-page': return `Model.${modelVariant(path)} params model`; | ||
case 'page': return `Model.${modelVariant(path)} params model`; | ||
} | ||
}; | ||
const pageModelArguments = (path, options) => { | ||
switch (options.kind(path)) { | ||
case 'view': return `params ()`; | ||
case 'static-page': return `params model`; | ||
case 'page': return `params model`; | ||
} | ||
}; | ||
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.isStaticPage = (sourceCode) => !exports.exposesModel(sourceCode) || !exports.exposesMsg(sourceCode); | ||
exports.exposesPageFunction = exposes('page'); | ||
exports.exposesViewFunction = exposes('view'); | ||
exports.isStandardPage = (src) => exports.exposesPageFunction(src) | ||
&& exports.exposesModel(src) | ||
&& exports.exposesMsg(src); | ||
exports.isStaticPage = (src) => exports.exposesPageFunction(src); | ||
exports.isStaticView = (src) => exports.exposesViewFunction(src); |
{ | ||
"name": "elm-spa", | ||
"version": "6.0.0--beta", | ||
"version": "6.0.0", | ||
"description": "single page apps made easy", | ||
@@ -43,4 +43,4 @@ "bin": "dist/src/index.js", | ||
"chokidar": "3.4.2", | ||
"elm": "0.19.1-3", | ||
"mime": "2.4.6", | ||
"node-elm-compiler": "5.0.5", | ||
"terser": "5.3.8", | ||
@@ -47,0 +47,0 @@ "websocket": "1.0.32" |
@@ -16,3 +16,3 @@ { | ||
"elm/url": "1.0.0", | ||
"ryannhg/elm-spa": "5.0.0" | ||
"ryannhg/elm-spa": "6.0.0" | ||
}, | ||
@@ -19,0 +19,0 @@ "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
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
91733
59
1349
0
+ 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)