mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2025-01-06 05:34:48 +03:00
Merge pull request #196 from dillonkearns/worker-threads
Worker threads
This commit is contained in:
commit
0dfdf9f7ed
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5"
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"defaultCommandTimeout": 4000
|
"defaultCommandTimeout": 4000,
|
||||||
|
"baseUrl": "http://localhost:1234"
|
||||||
}
|
}
|
5
cypress/fixtures/example.json
Normal file
5
cypress/fixtures/example.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
44
cypress/integration/elm-pages-dev-base-path.spec.js
Normal file
44
cypress/integration/elm-pages-dev-base-path.spec.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
context("dev server with base path", () => {
|
||||||
|
it("404 message", () => {
|
||||||
|
cy.visit("/qwer/asdf", { failOnStatusCode: false });
|
||||||
|
cy.contains("No route found for /asdf");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("navigates to root page", () => {
|
||||||
|
cy.visit("/qwer/");
|
||||||
|
cy.contains("This is the index page");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("loads file data sources", () => {
|
||||||
|
cy.writeFile(
|
||||||
|
"examples/end-to-end/my-json-data.json",
|
||||||
|
JSON.stringify({
|
||||||
|
greeting: "Hello, World!",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
cy.visit("/qwer/file-data");
|
||||||
|
|
||||||
|
cy.contains("Greeting: Hello, World!");
|
||||||
|
cy.writeFile(
|
||||||
|
"examples/end-to-end/my-json-data.json",
|
||||||
|
JSON.stringify({
|
||||||
|
greeting: "Goodbye, World!",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
cy.contains("Greeting: Goodbye, World!");
|
||||||
|
cy.writeFile(
|
||||||
|
"examples/end-to-end/my-json-data.json",
|
||||||
|
JSON.stringify({
|
||||||
|
greeting: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
cy.contains(`I encountered some errors while decoding this JSON:
|
||||||
|
|
||||||
|
At path /jsonFile/greeting
|
||||||
|
|
||||||
|
I expected a string here, but instead found this value:
|
||||||
|
|
||||||
|
null`);
|
||||||
|
});
|
||||||
|
});
|
7
cypress/integration/page-changes.spec.js
Normal file
7
cypress/integration/page-changes.spec.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
context("dev server with base path", () => {
|
||||||
|
it("navigating with a link successfully resolves data sources", () => {
|
||||||
|
cy.visit("/links");
|
||||||
|
cy.contains("Root page").click();
|
||||||
|
cy.contains("This is the index page.");
|
||||||
|
});
|
||||||
|
});
|
22
cypress/plugins/index.js
Normal file
22
cypress/plugins/index.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************************
|
||||||
|
// This example plugins/index.js can be used to load plugins
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off loading
|
||||||
|
// the plugins file with the 'pluginsFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/plugins-guide
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// This function is called when a project is opened or re-opened (e.g. due to
|
||||||
|
// the project's config changing)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Cypress.PluginConfig}
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
module.exports = (on, config) => {
|
||||||
|
// `on` is used to hook into various events Cypress emits
|
||||||
|
// `config` is the resolved Cypress config
|
||||||
|
}
|
25
cypress/support/commands.js
Normal file
25
cypress/support/commands.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
20
cypress/support/index.js
Normal file
20
cypress/support/index.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
2
elm.json
2
elm.json
@ -41,7 +41,9 @@
|
|||||||
"elm-community/list-extra": "8.3.0 <= v < 9.0.0",
|
"elm-community/list-extra": "8.3.0 <= v < 9.0.0",
|
||||||
"miniBill/elm-codec": "2.0.0 <= v < 3.0.0",
|
"miniBill/elm-codec": "2.0.0 <= v < 3.0.0",
|
||||||
"noahzgordon/elm-color-extra": "1.0.2 <= v < 2.0.0",
|
"noahzgordon/elm-color-extra": "1.0.2 <= v < 2.0.0",
|
||||||
|
"robinheghan/murmur3": "1.0.0 <= v < 2.0.0",
|
||||||
"tripokey/elm-fuzzy": "5.2.1 <= v < 6.0.0",
|
"tripokey/elm-fuzzy": "5.2.1 <= v < 6.0.0",
|
||||||
|
"vito/elm-ansi": "10.0.1 <= v < 11.0.0",
|
||||||
"zwilias/json-decode-exploration": "6.0.0 <= v < 7.0.0"
|
"zwilias/json-decode-exploration": "6.0.0 <= v < 7.0.0"
|
||||||
},
|
},
|
||||||
"test-dependencies": {
|
"test-dependencies": {
|
||||||
|
@ -32,8 +32,10 @@
|
|||||||
"miniBill/elm-codec": "2.0.0",
|
"miniBill/elm-codec": "2.0.0",
|
||||||
"noahzgordon/elm-color-extra": "1.0.2",
|
"noahzgordon/elm-color-extra": "1.0.2",
|
||||||
"pablohirafuji/elm-syntax-highlight": "3.4.0",
|
"pablohirafuji/elm-syntax-highlight": "3.4.0",
|
||||||
|
"robinheghan/murmur3": "1.0.0",
|
||||||
"rtfeldman/elm-css": "16.1.1",
|
"rtfeldman/elm-css": "16.1.1",
|
||||||
"tripokey/elm-fuzzy": "5.2.1",
|
"tripokey/elm-fuzzy": "5.2.1",
|
||||||
|
"vito/elm-ansi": "10.0.1",
|
||||||
"zwilias/json-decode-exploration": "6.0.0"
|
"zwilias/json-decode-exploration": "6.0.0"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
10
examples/docs/package-lock.json
generated
10
examples/docs/package-lock.json
generated
@ -29,6 +29,7 @@
|
|||||||
"commander": "^7.2.0",
|
"commander": "^7.2.0",
|
||||||
"connect": "^3.7.0",
|
"connect": "^3.7.0",
|
||||||
"cross-spawn": "7.0.3",
|
"cross-spawn": "7.0.3",
|
||||||
|
"elm-doc-preview": "^5.0.5",
|
||||||
"elm-hot": "^1.1.6",
|
"elm-hot": "^1.1.6",
|
||||||
"elm-optimize-level-2": "^0.1.5",
|
"elm-optimize-level-2": "^0.1.5",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
@ -36,6 +37,7 @@
|
|||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"kleur": "^4.1.4",
|
"kleur": "^4.1.4",
|
||||||
"micromatch": "^4.0.4",
|
"micromatch": "^4.0.4",
|
||||||
|
"node-worker-threads-pool": "^1.5.0",
|
||||||
"serve-static": "^1.14.1",
|
"serve-static": "^1.14.1",
|
||||||
"terser": "^5.7.0",
|
"terser": "^5.7.0",
|
||||||
"xhr2": "^0.2.1"
|
"xhr2": "^0.2.1"
|
||||||
@ -49,7 +51,8 @@
|
|||||||
"@types/micromatch": "^4.0.1",
|
"@types/micromatch": "^4.0.1",
|
||||||
"@types/node": "12.20.12",
|
"@types/node": "12.20.12",
|
||||||
"@types/serve-static": "^1.13.9",
|
"@types/serve-static": "^1.13.9",
|
||||||
"elm-review": "^2.5.1",
|
"cypress": "^7.4.0",
|
||||||
|
"elm-review": "^2.5.3",
|
||||||
"elm-test": "^0.19.1-revision7",
|
"elm-test": "^0.19.1-revision7",
|
||||||
"elm-tooling": "^1.3.0",
|
"elm-tooling": "^1.3.0",
|
||||||
"elm-verify-examples": "^5.0.0",
|
"elm-verify-examples": "^5.0.0",
|
||||||
@ -1091,9 +1094,11 @@
|
|||||||
"commander": "^7.2.0",
|
"commander": "^7.2.0",
|
||||||
"connect": "^3.7.0",
|
"connect": "^3.7.0",
|
||||||
"cross-spawn": "7.0.3",
|
"cross-spawn": "7.0.3",
|
||||||
|
"cypress": "^7.4.0",
|
||||||
|
"elm-doc-preview": "^5.0.5",
|
||||||
"elm-hot": "^1.1.6",
|
"elm-hot": "^1.1.6",
|
||||||
"elm-optimize-level-2": "^0.1.5",
|
"elm-optimize-level-2": "^0.1.5",
|
||||||
"elm-review": "^2.5.1",
|
"elm-review": "^2.5.3",
|
||||||
"elm-test": "^0.19.1-revision7",
|
"elm-test": "^0.19.1-revision7",
|
||||||
"elm-tooling": "^1.3.0",
|
"elm-tooling": "^1.3.0",
|
||||||
"elm-verify-examples": "^5.0.0",
|
"elm-verify-examples": "^5.0.0",
|
||||||
@ -1103,6 +1108,7 @@
|
|||||||
"kleur": "^4.1.4",
|
"kleur": "^4.1.4",
|
||||||
"micromatch": "^4.0.4",
|
"micromatch": "^4.0.4",
|
||||||
"mocha": "^8.4.0",
|
"mocha": "^8.4.0",
|
||||||
|
"node-worker-threads-pool": "^1.5.0",
|
||||||
"serve-static": "^1.14.1",
|
"serve-static": "^1.14.1",
|
||||||
"terser": "^5.7.0",
|
"terser": "^5.7.0",
|
||||||
"typescript": "^4.2.4",
|
"typescript": "^4.2.4",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module Shared exposing (Data, Model, Msg, SharedMsg(..), template)
|
module Shared exposing (Data, Model, Msg, template)
|
||||||
|
|
||||||
import Browser.Navigation
|
import Browser.Navigation
|
||||||
import DataSource
|
import DataSource
|
||||||
@ -8,13 +8,14 @@ import Html.Styled
|
|||||||
import Pages.Flags
|
import Pages.Flags
|
||||||
import Pages.PageUrl exposing (PageUrl)
|
import Pages.PageUrl exposing (PageUrl)
|
||||||
import Path exposing (Path)
|
import Path exposing (Path)
|
||||||
|
import Route exposing (Route)
|
||||||
import SharedTemplate exposing (SharedTemplate)
|
import SharedTemplate exposing (SharedTemplate)
|
||||||
import TableOfContents
|
import TableOfContents
|
||||||
import View exposing (View)
|
import View exposing (View)
|
||||||
import View.Header
|
import View.Header
|
||||||
|
|
||||||
|
|
||||||
template : SharedTemplate Msg Model Data SharedMsg msg
|
template : SharedTemplate Msg Model Data msg
|
||||||
template =
|
template =
|
||||||
{ init = init
|
{ init = init
|
||||||
, update = update
|
, update = update
|
||||||
@ -22,7 +23,6 @@ template =
|
|||||||
, data = data
|
, data = data
|
||||||
, subscriptions = subscriptions
|
, subscriptions = subscriptions
|
||||||
, onPageChange = Just OnPageChange
|
, onPageChange = Just OnPageChange
|
||||||
, sharedMsg = SharedMsg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -33,17 +33,13 @@ type Msg
|
|||||||
, fragment : Maybe String
|
, fragment : Maybe String
|
||||||
}
|
}
|
||||||
| ToggleMobileMenu
|
| ToggleMobileMenu
|
||||||
| SharedMsg SharedMsg
|
| IncrementFromChild
|
||||||
|
|
||||||
|
|
||||||
type alias Data =
|
type alias Data =
|
||||||
TableOfContents.TableOfContents TableOfContents.Data
|
TableOfContents.TableOfContents TableOfContents.Data
|
||||||
|
|
||||||
|
|
||||||
type SharedMsg
|
|
||||||
= IncrementFromChild
|
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ showMobileMenu : Bool
|
{ showMobileMenu : Bool
|
||||||
, counter : Int
|
, counter : Int
|
||||||
@ -83,8 +79,6 @@ update msg model =
|
|||||||
ToggleMobileMenu ->
|
ToggleMobileMenu ->
|
||||||
( { model | showMobileMenu = not model.showMobileMenu }, Cmd.none )
|
( { model | showMobileMenu = not model.showMobileMenu }, Cmd.none )
|
||||||
|
|
||||||
SharedMsg globalMsg ->
|
|
||||||
case globalMsg of
|
|
||||||
IncrementFromChild ->
|
IncrementFromChild ->
|
||||||
( { model | counter = model.counter + 1 }, Cmd.none )
|
( { model | counter = model.counter + 1 }, Cmd.none )
|
||||||
|
|
||||||
@ -103,7 +97,7 @@ view :
|
|||||||
Data
|
Data
|
||||||
->
|
->
|
||||||
{ path : Path
|
{ path : Path
|
||||||
, frontmatter : route
|
, route : Maybe Route
|
||||||
}
|
}
|
||||||
-> Model
|
-> Model
|
||||||
-> (Msg -> msg)
|
-> (Msg -> msg)
|
||||||
|
6
examples/end-to-end/.gitignore
vendored
Normal file
6
examples/end-to-end/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
node_modules/
|
||||||
|
elm-stuff/
|
||||||
|
dist/
|
||||||
|
.cache/
|
||||||
|
.elm-pages/
|
||||||
|
functions/render/elm-pages-cli.js
|
1
examples/end-to-end/README.md
Normal file
1
examples/end-to-end/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
# README
|
8
examples/end-to-end/elm-application.json
Normal file
8
examples/end-to-end/elm-application.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "dmy/elm-doc-preview",
|
||||||
|
"summary": "Offline documentation previewer",
|
||||||
|
"version": "5.0.0",
|
||||||
|
"exposed-modules": [
|
||||||
|
"Page"
|
||||||
|
]
|
||||||
|
}
|
8
examples/end-to-end/elm-tooling.json
Normal file
8
examples/end-to-end/elm-tooling.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"tools": {
|
||||||
|
"elm": "0.19.1",
|
||||||
|
"elm-format": "0.8.4",
|
||||||
|
"elm-json": "0.2.10",
|
||||||
|
"elm-test-rs": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
56
examples/end-to-end/elm.json
Normal file
56
examples/end-to-end/elm.json
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"type": "application",
|
||||||
|
"source-directories": [
|
||||||
|
"src",
|
||||||
|
"../../src",
|
||||||
|
".elm-pages",
|
||||||
|
"../../plugins"
|
||||||
|
],
|
||||||
|
"elm-version": "0.19.1",
|
||||||
|
"dependencies": {
|
||||||
|
"direct": {
|
||||||
|
"MartinSStewart/elm-serialize": "1.2.5",
|
||||||
|
"avh4/elm-color": "1.0.0",
|
||||||
|
"danyx23/elm-mimetype": "4.0.1",
|
||||||
|
"dillonkearns/elm-bcp47-language-tag": "1.0.1",
|
||||||
|
"dillonkearns/elm-markdown": "6.0.1",
|
||||||
|
"elm/browser": "1.0.2",
|
||||||
|
"elm/core": "1.0.5",
|
||||||
|
"elm/html": "1.0.0",
|
||||||
|
"elm/http": "2.0.0",
|
||||||
|
"elm/json": "1.1.3",
|
||||||
|
"elm/regex": "1.0.0",
|
||||||
|
"elm/time": "1.0.0",
|
||||||
|
"elm/url": "1.0.0",
|
||||||
|
"elm-community/dict-extra": "2.4.0",
|
||||||
|
"elm-community/list-extra": "8.3.0",
|
||||||
|
"matheus23/elm-default-tailwind-modules": "2.0.1",
|
||||||
|
"miniBill/elm-codec": "2.0.0",
|
||||||
|
"noahzgordon/elm-color-extra": "1.0.2",
|
||||||
|
"pablohirafuji/elm-syntax-highlight": "3.4.0",
|
||||||
|
"robinheghan/murmur3": "1.0.0",
|
||||||
|
"rtfeldman/elm-css": "16.1.1",
|
||||||
|
"tripokey/elm-fuzzy": "5.2.1",
|
||||||
|
"vito/elm-ansi": "10.0.1",
|
||||||
|
"zwilias/json-decode-exploration": "6.0.0"
|
||||||
|
},
|
||||||
|
"indirect": {
|
||||||
|
"bburdette/toop": "1.0.1",
|
||||||
|
"danfishgold/base64-bytes": "1.1.0",
|
||||||
|
"elm/bytes": "1.0.8",
|
||||||
|
"elm/file": "1.0.5",
|
||||||
|
"elm/parser": "1.1.0",
|
||||||
|
"elm/random": "1.0.0",
|
||||||
|
"elm/virtual-dom": "1.0.2",
|
||||||
|
"fredcy/elm-parseint": "2.0.1",
|
||||||
|
"mgold/elm-nonempty-list": "4.2.0",
|
||||||
|
"rtfeldman/elm-hex": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test-dependencies": {
|
||||||
|
"direct": {
|
||||||
|
"elm-explorations/test": "1.2.2"
|
||||||
|
},
|
||||||
|
"indirect": {}
|
||||||
|
}
|
||||||
|
}
|
1
examples/end-to-end/greeting.txt
Normal file
1
examples/end-to-end/greeting.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Hello there!
|
3
examples/end-to-end/my-json-data.json
Normal file
3
examples/end-to-end/my-json-data.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"greeting": "Hello, World!"
|
||||||
|
}
|
2562
examples/end-to-end/package-lock.json
generated
Normal file
2562
examples/end-to-end/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
examples/end-to-end/package.json
Normal file
18
examples/end-to-end/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "elm-pages-example",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Example site built with elm-pages.",
|
||||||
|
"scripts": {
|
||||||
|
"start": "elm-pages dev",
|
||||||
|
"serve": "npm run build && http-server ./dist -a localhost -p 3000 -c-1",
|
||||||
|
"build": "elm-pages build"
|
||||||
|
},
|
||||||
|
"author": "Dillon Kearns",
|
||||||
|
"license": "BSD-3",
|
||||||
|
"devDependencies": {
|
||||||
|
"elm-oembed": "0.0.6",
|
||||||
|
"elm-pages": "file:../..",
|
||||||
|
"elm-tooling": "^1.3.0",
|
||||||
|
"http-server": "^0.11.1"
|
||||||
|
}
|
||||||
|
}
|
39
examples/end-to-end/public/images/elm-logo.svg
Normal file
39
examples/end-to-end/public/images/elm-logo.svg
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 323.141 322.95" enable-background="new 0 0 323.141 322.95" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<polygon
|
||||||
|
fill="#F0AD00"
|
||||||
|
points="161.649,152.782 231.514,82.916 91.783,82.916"/>
|
||||||
|
|
||||||
|
<polygon
|
||||||
|
fill="#7FD13B"
|
||||||
|
points="8.867,0 79.241,70.375 232.213,70.375 161.838,0"/>
|
||||||
|
|
||||||
|
<rect
|
||||||
|
fill="#7FD13B"
|
||||||
|
x="192.99"
|
||||||
|
y="107.392"
|
||||||
|
transform="matrix(0.7071 0.7071 -0.7071 0.7071 186.4727 -127.2386)"
|
||||||
|
width="107.676"
|
||||||
|
height="108.167"/>
|
||||||
|
|
||||||
|
<polygon
|
||||||
|
fill="#60B5CC"
|
||||||
|
points="323.298,143.724 323.298,0 179.573,0"/>
|
||||||
|
|
||||||
|
<polygon
|
||||||
|
fill="#5A6378"
|
||||||
|
points="152.781,161.649 0,8.868 0,314.432"/>
|
||||||
|
|
||||||
|
<polygon
|
||||||
|
fill="#F0AD00"
|
||||||
|
points="255.522,246.655 323.298,314.432 323.298,178.879"/>
|
||||||
|
|
||||||
|
<polygon
|
||||||
|
fill="#60B5CC"
|
||||||
|
points="161.649,170.517 8.869,323.298 314.43,323.298"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
examples/end-to-end/public/images/github.svg
Normal file
1
examples/end-to-end/public/images/github.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub icon</title><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>
|
After Width: | Height: | Size: 827 B |
BIN
examples/end-to-end/public/images/icon-png.png
Normal file
BIN
examples/end-to-end/public/images/icon-png.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 976 B |
2
examples/end-to-end/public/images/icon.svg
Normal file
2
examples/end-to-end/public/images/icon.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<svg version="1.1" viewBox="251.0485 144.52063 56.114286 74.5" width="50px" height="74.5"><defs><linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="10%" style="stop-color:rgba(1.96%,45.88%,90.2%,1);stop-opacity:1"></stop><stop offset="100%" style="stop-color:rgba(0%,94.9%,37.65%,1);stop-opacity:1"></stop></linearGradient></defs><metadata></metadata><g id="Canvas_11" stroke="none" fill="url(#grad1)" stroke-opacity="1" fill-opacity="1" stroke-dasharray="none"><g id="Canvas_11: Layer 1"><g id="Group_38"><g id="Graphic_32"><path d="M 252.5485 146.02063 L 252.5485 217.52063 L 305.66277 217.52063 L 305.66277 161.68254 L 290.00087 146.02063 Z" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="3"></path></g><g id="Line_34"><line x1="266.07286" y1="182.8279" x2="290.75465" y2="183.00997" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line></g><g id="Line_35"><line x1="266.07286" y1="191.84156" x2="290.75465" y2="192.02363" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line></g><g id="Line_36"><line x1="266.07286" y1="200.85522" x2="290.75465" y2="201.0373" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line></g><g id="Line_37"><line x1="266.07286" y1="164.80058" x2="278.3874" y2="164.94049" stroke="black" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"></line></g></g></g></g></svg>
|
||||||
|
|
After Width: | Height: | Size: 1.4 KiB |
6
examples/end-to-end/public/index.js
Normal file
6
examples/end-to-end/public/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
load: function (elmLoaded) {},
|
||||||
|
flags: function () {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
79
examples/end-to-end/public/style.css
Normal file
79
examples/end-to-end/public/style.css
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
@import url("https://rsms.me/inter/inter.css");
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap");
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Inter var" !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre.elmsh {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
overflow: auto;
|
||||||
|
height: 100%;
|
||||||
|
width: 500px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: "IBM Plex Mono" !important;
|
||||||
|
}
|
||||||
|
code.elmsh {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.elmsh-line:before {
|
||||||
|
content: attr(data-elmsh-lc);
|
||||||
|
display: inline-block;
|
||||||
|
text-align: right;
|
||||||
|
width: 40px;
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
.elmsh {
|
||||||
|
color: #f8f8f2;
|
||||||
|
background: #1e1e1e;
|
||||||
|
}
|
||||||
|
.elmsh-hl {
|
||||||
|
background: #4864aa;
|
||||||
|
}
|
||||||
|
.elmsh-add {
|
||||||
|
background: #003800;
|
||||||
|
}
|
||||||
|
.elmsh-del {
|
||||||
|
background: #380000;
|
||||||
|
}
|
||||||
|
.elmsh-comm {
|
||||||
|
color: #d4d4d4;
|
||||||
|
}
|
||||||
|
.elmsh1 {
|
||||||
|
color: #74b0df;
|
||||||
|
}
|
||||||
|
.elmsh2 {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
.elmsh3 {
|
||||||
|
color: #ff00ff;
|
||||||
|
}
|
||||||
|
.elmsh4 {
|
||||||
|
color: #4f76ac;
|
||||||
|
}
|
||||||
|
.elmsh5 {
|
||||||
|
color: #3dc9b0;
|
||||||
|
}
|
||||||
|
.elmsh6 {
|
||||||
|
color: #74b0df;
|
||||||
|
}
|
||||||
|
.elmsh7 {
|
||||||
|
color: #ce9178;
|
||||||
|
}
|
||||||
|
.elmsh-elm-ts,
|
||||||
|
.elmsh-js-dk,
|
||||||
|
.elmsh-css-p {
|
||||||
|
font-style: italic;
|
||||||
|
color: #4f76ac;
|
||||||
|
}
|
||||||
|
.elmsh-js-ce {
|
||||||
|
font-style: italic;
|
||||||
|
color: #5bb498;
|
||||||
|
}
|
||||||
|
.elmsh-css-ar-i {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
43
examples/end-to-end/public/syntax.css
Normal file
43
examples/end-to-end/public/syntax.css
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
pre.elmsh {
|
||||||
|
padding: 10px;
|
||||||
|
margin: 0;
|
||||||
|
text-align: left;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
code.elmsh {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: 'Roboto Mono' !important;
|
||||||
|
font-size: 20px !important;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elmsh-line:before {
|
||||||
|
/* content: attr(data-elmsh-lc); */
|
||||||
|
display: inline-block;
|
||||||
|
text-align: right;
|
||||||
|
width: 40px;
|
||||||
|
padding: 0 20px 0 0;
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elmsh {
|
||||||
|
color: #f8f8f2;
|
||||||
|
background: #000;
|
||||||
|
}
|
||||||
|
.elmsh-hl {background: #343434;}
|
||||||
|
.elmsh-add {background: #003800;}
|
||||||
|
.elmsh-del {background: #380000;}
|
||||||
|
.elmsh-comm {color: #75715e;}
|
||||||
|
.elmsh1 {color: #ae81ff;}
|
||||||
|
.elmsh2 {color: #e6db74;}
|
||||||
|
.elmsh3 {color: #66d9ef;}
|
||||||
|
.elmsh4 {color: #f92672;}
|
||||||
|
.elmsh5 {color: #a6e22e;}
|
||||||
|
.elmsh6 {color: #ae81ff;}
|
||||||
|
.elmsh7 {color: #fd971f;}
|
||||||
|
|
18
examples/end-to-end/src/Api.elm
Normal file
18
examples/end-to-end/src/Api.elm
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Api exposing (routes)
|
||||||
|
|
||||||
|
import ApiRoute
|
||||||
|
import DataSource exposing (DataSource)
|
||||||
|
import DataSource.Http
|
||||||
|
import Html exposing (Html)
|
||||||
|
import Json.Encode
|
||||||
|
import OptimizedDecoder as Decode
|
||||||
|
import Route exposing (Route)
|
||||||
|
import Secrets
|
||||||
|
|
||||||
|
|
||||||
|
routes :
|
||||||
|
DataSource (List Route)
|
||||||
|
-> (Html Never -> String)
|
||||||
|
-> List (ApiRoute.Done ApiRoute.Response)
|
||||||
|
routes getStaticRoutes htmlToString =
|
||||||
|
[]
|
233
examples/end-to-end/src/MarkdownRenderer.elm
Normal file
233
examples/end-to-end/src/MarkdownRenderer.elm
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
module MarkdownRenderer exposing (renderer)
|
||||||
|
|
||||||
|
import Html.Styled as Html
|
||||||
|
import Html.Styled.Attributes as Attr exposing (css)
|
||||||
|
import Markdown.Block as Block exposing (ListItem(..), Task(..))
|
||||||
|
import Markdown.Html
|
||||||
|
import Markdown.Renderer
|
||||||
|
import SyntaxHighlight
|
||||||
|
import Tailwind.Utilities as Tw
|
||||||
|
|
||||||
|
|
||||||
|
renderer : Markdown.Renderer.Renderer (Html.Html msg)
|
||||||
|
renderer =
|
||||||
|
{ heading = heading
|
||||||
|
, paragraph = Html.p []
|
||||||
|
, thematicBreak = Html.hr [] []
|
||||||
|
, text = Html.text
|
||||||
|
, strong = \content -> Html.strong [ css [ Tw.font_bold ] ] content
|
||||||
|
, emphasis = \content -> Html.em [ css [ Tw.italic ] ] content
|
||||||
|
, blockQuote = Html.blockquote []
|
||||||
|
, codeSpan =
|
||||||
|
\content ->
|
||||||
|
Html.code
|
||||||
|
[ css
|
||||||
|
[ Tw.font_semibold
|
||||||
|
, Tw.font_medium
|
||||||
|
]
|
||||||
|
]
|
||||||
|
[ Html.text content ]
|
||||||
|
|
||||||
|
--, codeSpan = code
|
||||||
|
, link =
|
||||||
|
\{ destination } body ->
|
||||||
|
Html.a
|
||||||
|
[ Attr.href destination
|
||||||
|
, css
|
||||||
|
[ Tw.underline
|
||||||
|
]
|
||||||
|
]
|
||||||
|
body
|
||||||
|
, hardLineBreak = Html.br [] []
|
||||||
|
, image =
|
||||||
|
\image ->
|
||||||
|
case image.title of
|
||||||
|
Just _ ->
|
||||||
|
Html.img [ Attr.src image.src, Attr.alt image.alt ] []
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
Html.img [ Attr.src image.src, Attr.alt image.alt ] []
|
||||||
|
, unorderedList =
|
||||||
|
\items ->
|
||||||
|
Html.ul []
|
||||||
|
(items
|
||||||
|
|> List.map
|
||||||
|
(\item ->
|
||||||
|
case item of
|
||||||
|
Block.ListItem task children ->
|
||||||
|
let
|
||||||
|
checkbox =
|
||||||
|
case task of
|
||||||
|
Block.NoTask ->
|
||||||
|
Html.text ""
|
||||||
|
|
||||||
|
Block.IncompleteTask ->
|
||||||
|
Html.input
|
||||||
|
[ Attr.disabled True
|
||||||
|
, Attr.checked False
|
||||||
|
, Attr.type_ "checkbox"
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
|
||||||
|
Block.CompletedTask ->
|
||||||
|
Html.input
|
||||||
|
[ Attr.disabled True
|
||||||
|
, Attr.checked True
|
||||||
|
, Attr.type_ "checkbox"
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
in
|
||||||
|
Html.li [] (checkbox :: children)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
, orderedList =
|
||||||
|
\startingIndex items ->
|
||||||
|
Html.ol
|
||||||
|
(case startingIndex of
|
||||||
|
1 ->
|
||||||
|
[ Attr.start startingIndex ]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
(items
|
||||||
|
|> List.map
|
||||||
|
(\itemBlocks ->
|
||||||
|
Html.li []
|
||||||
|
itemBlocks
|
||||||
|
)
|
||||||
|
)
|
||||||
|
, html = Markdown.Html.oneOf []
|
||||||
|
, codeBlock = codeBlock
|
||||||
|
|
||||||
|
--\{ body, language } ->
|
||||||
|
-- let
|
||||||
|
-- classes =
|
||||||
|
-- -- Only the first word is used in the class
|
||||||
|
-- case Maybe.map String.words language of
|
||||||
|
-- Just (actualLanguage :: _) ->
|
||||||
|
-- [ Attr.class <| "language-" ++ actualLanguage ]
|
||||||
|
--
|
||||||
|
-- _ ->
|
||||||
|
-- []
|
||||||
|
-- in
|
||||||
|
-- Html.pre []
|
||||||
|
-- [ Html.code classes
|
||||||
|
-- [ Html.text body
|
||||||
|
-- ]
|
||||||
|
-- ]
|
||||||
|
, table = Html.table []
|
||||||
|
, tableHeader = Html.thead []
|
||||||
|
, tableBody = Html.tbody []
|
||||||
|
, tableRow = Html.tr []
|
||||||
|
, strikethrough =
|
||||||
|
\children -> Html.del [] children
|
||||||
|
, tableHeaderCell =
|
||||||
|
\maybeAlignment ->
|
||||||
|
let
|
||||||
|
attrs =
|
||||||
|
maybeAlignment
|
||||||
|
|> Maybe.map
|
||||||
|
(\alignment ->
|
||||||
|
case alignment of
|
||||||
|
Block.AlignLeft ->
|
||||||
|
"left"
|
||||||
|
|
||||||
|
Block.AlignCenter ->
|
||||||
|
"center"
|
||||||
|
|
||||||
|
Block.AlignRight ->
|
||||||
|
"right"
|
||||||
|
)
|
||||||
|
|> Maybe.map Attr.align
|
||||||
|
|> Maybe.map List.singleton
|
||||||
|
|> Maybe.withDefault []
|
||||||
|
in
|
||||||
|
Html.th attrs
|
||||||
|
, tableCell =
|
||||||
|
\maybeAlignment ->
|
||||||
|
let
|
||||||
|
attrs =
|
||||||
|
maybeAlignment
|
||||||
|
|> Maybe.map
|
||||||
|
(\alignment ->
|
||||||
|
case alignment of
|
||||||
|
Block.AlignLeft ->
|
||||||
|
"left"
|
||||||
|
|
||||||
|
Block.AlignCenter ->
|
||||||
|
"center"
|
||||||
|
|
||||||
|
Block.AlignRight ->
|
||||||
|
"right"
|
||||||
|
)
|
||||||
|
|> Maybe.map Attr.align
|
||||||
|
|> Maybe.map List.singleton
|
||||||
|
|> Maybe.withDefault []
|
||||||
|
in
|
||||||
|
Html.td attrs
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
rawTextToId : String -> String
|
||||||
|
rawTextToId rawText =
|
||||||
|
rawText
|
||||||
|
|> String.split " "
|
||||||
|
|> String.join "-"
|
||||||
|
|> String.toLower
|
||||||
|
|
||||||
|
|
||||||
|
heading : { level : Block.HeadingLevel, rawText : String, children : List (Html.Html msg) } -> Html.Html msg
|
||||||
|
heading { level, rawText, children } =
|
||||||
|
(case level of
|
||||||
|
Block.H1 ->
|
||||||
|
Html.h1
|
||||||
|
|
||||||
|
Block.H2 ->
|
||||||
|
Html.h2
|
||||||
|
|
||||||
|
Block.H3 ->
|
||||||
|
Html.h3
|
||||||
|
|
||||||
|
Block.H4 ->
|
||||||
|
Html.h4
|
||||||
|
|
||||||
|
Block.H5 ->
|
||||||
|
Html.h5
|
||||||
|
|
||||||
|
Block.H6 ->
|
||||||
|
Html.h6
|
||||||
|
)
|
||||||
|
[ Attr.id (rawTextToId rawText)
|
||||||
|
, Attr.attribute "name" (rawTextToId rawText)
|
||||||
|
, css
|
||||||
|
[ Tw.font_bold
|
||||||
|
, Tw.text_2xl
|
||||||
|
, Tw.mt_8
|
||||||
|
, Tw.mb_4
|
||||||
|
]
|
||||||
|
]
|
||||||
|
children
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
--code : String -> Element msg
|
||||||
|
--code snippet =
|
||||||
|
-- Element.el
|
||||||
|
-- [ Element.Background.color
|
||||||
|
-- (Element.rgba255 50 50 50 0.07)
|
||||||
|
-- , Element.Border.rounded 2
|
||||||
|
-- , Element.paddingXY 5 3
|
||||||
|
-- , Font.family [ Font.typeface "Roboto Mono", Font.monospace ]
|
||||||
|
-- ]
|
||||||
|
-- (Element.text snippet)
|
||||||
|
--
|
||||||
|
--
|
||||||
|
|
||||||
|
|
||||||
|
codeBlock : { body : String, language : Maybe String } -> Html.Html msg
|
||||||
|
codeBlock details =
|
||||||
|
SyntaxHighlight.elm details.body
|
||||||
|
|> Result.map (SyntaxHighlight.toBlockHtml (Just 1))
|
||||||
|
|> Result.map Html.fromUnstyled
|
||||||
|
|> Result.withDefault (Html.pre [] [ Html.code [] [ Html.text details.body ] ])
|
79
examples/end-to-end/src/Page/FileData.elm
Normal file
79
examples/end-to-end/src/Page/FileData.elm
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
module Page.FileData exposing (Data, Model, Msg, page)
|
||||||
|
|
||||||
|
import DataSource exposing (DataSource)
|
||||||
|
import DataSource.File
|
||||||
|
import Head
|
||||||
|
import Head.Seo as Seo
|
||||||
|
import Html.Styled exposing (text)
|
||||||
|
import OptimizedDecoder as Decode
|
||||||
|
import Page exposing (Page, PageWithState, StaticPayload)
|
||||||
|
import Pages.PageUrl exposing (PageUrl)
|
||||||
|
import Pages.Url
|
||||||
|
import Shared
|
||||||
|
import View exposing (View)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
type alias RouteParams =
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
page : Page RouteParams Data
|
||||||
|
page =
|
||||||
|
Page.single
|
||||||
|
{ head = head
|
||||||
|
, data = data
|
||||||
|
}
|
||||||
|
|> Page.buildNoState { view = view }
|
||||||
|
|
||||||
|
|
||||||
|
type alias Data =
|
||||||
|
{ greeting : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data : DataSource Data
|
||||||
|
data =
|
||||||
|
"my-json-data.json"
|
||||||
|
|> DataSource.File.jsonFile (Decode.field "greeting" Decode.string)
|
||||||
|
|> DataSource.map Data
|
||||||
|
|
||||||
|
|
||||||
|
head :
|
||||||
|
StaticPayload Data RouteParams
|
||||||
|
-> List Head.Tag
|
||||||
|
head static =
|
||||||
|
Seo.summary
|
||||||
|
{ canonicalUrlOverride = Nothing
|
||||||
|
, siteName = "elm-pages"
|
||||||
|
, image =
|
||||||
|
{ url = Pages.Url.external "TODO"
|
||||||
|
, alt = "elm-pages logo"
|
||||||
|
, dimensions = Nothing
|
||||||
|
, mimeType = Nothing
|
||||||
|
}
|
||||||
|
, description = "TODO"
|
||||||
|
, locale = Nothing
|
||||||
|
, title = "TODO title" -- metadata.title -- TODO
|
||||||
|
}
|
||||||
|
|> Seo.website
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
Maybe PageUrl
|
||||||
|
-> Shared.Model
|
||||||
|
-> StaticPayload Data RouteParams
|
||||||
|
-> View Msg
|
||||||
|
view maybeUrl sharedModel static =
|
||||||
|
{ title = "Index page"
|
||||||
|
, body =
|
||||||
|
[ text <| "Greeting: " ++ static.data.greeting
|
||||||
|
]
|
||||||
|
}
|
76
examples/end-to-end/src/Page/Index.elm
Normal file
76
examples/end-to-end/src/Page/Index.elm
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
module Page.Index exposing (Data, Model, Msg, page)
|
||||||
|
|
||||||
|
import DataSource exposing (DataSource)
|
||||||
|
import DataSource.File
|
||||||
|
import Head
|
||||||
|
import Head.Seo as Seo
|
||||||
|
import Html.Styled exposing (text)
|
||||||
|
import Page exposing (Page, PageWithState, StaticPayload)
|
||||||
|
import Pages.PageUrl exposing (PageUrl)
|
||||||
|
import Pages.Url
|
||||||
|
import Shared
|
||||||
|
import View exposing (View)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
type alias RouteParams =
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
page : Page RouteParams Data
|
||||||
|
page =
|
||||||
|
Page.single
|
||||||
|
{ head = head
|
||||||
|
, data = data
|
||||||
|
}
|
||||||
|
|> Page.buildNoState { view = view }
|
||||||
|
|
||||||
|
|
||||||
|
type alias Data =
|
||||||
|
String
|
||||||
|
|
||||||
|
|
||||||
|
data : DataSource Data
|
||||||
|
data =
|
||||||
|
DataSource.File.rawFile "greeting.txt"
|
||||||
|
|
||||||
|
|
||||||
|
head :
|
||||||
|
StaticPayload Data RouteParams
|
||||||
|
-> List Head.Tag
|
||||||
|
head static =
|
||||||
|
Seo.summary
|
||||||
|
{ canonicalUrlOverride = Nothing
|
||||||
|
, siteName = "elm-pages"
|
||||||
|
, image =
|
||||||
|
{ url = Pages.Url.external "TODO"
|
||||||
|
, alt = "elm-pages logo"
|
||||||
|
, dimensions = Nothing
|
||||||
|
, mimeType = Nothing
|
||||||
|
}
|
||||||
|
, description = "TODO"
|
||||||
|
, locale = Nothing
|
||||||
|
, title = "TODO title" -- metadata.title -- TODO
|
||||||
|
}
|
||||||
|
|> Seo.website
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
Maybe PageUrl
|
||||||
|
-> Shared.Model
|
||||||
|
-> StaticPayload Data RouteParams
|
||||||
|
-> View Msg
|
||||||
|
view maybeUrl sharedModel static =
|
||||||
|
{ title = "Index page"
|
||||||
|
, body =
|
||||||
|
[ text "This is the index page."
|
||||||
|
, text <| "Greeting: " ++ static.data
|
||||||
|
]
|
||||||
|
}
|
76
examples/end-to-end/src/Page/Links.elm
Normal file
76
examples/end-to-end/src/Page/Links.elm
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
module Page.Links exposing (Data, Model, Msg, page)
|
||||||
|
|
||||||
|
import DataSource exposing (DataSource)
|
||||||
|
import Head
|
||||||
|
import Head.Seo as Seo
|
||||||
|
import Html.Styled as Html exposing (text)
|
||||||
|
import Html.Styled.Attributes as Attr
|
||||||
|
import Page exposing (Page, PageWithState, StaticPayload)
|
||||||
|
import Pages.PageUrl exposing (PageUrl)
|
||||||
|
import Pages.Url
|
||||||
|
import Shared
|
||||||
|
import View exposing (View)
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type alias Msg =
|
||||||
|
Never
|
||||||
|
|
||||||
|
|
||||||
|
type alias RouteParams =
|
||||||
|
{}
|
||||||
|
|
||||||
|
|
||||||
|
page : Page RouteParams Data
|
||||||
|
page =
|
||||||
|
Page.single
|
||||||
|
{ head = head
|
||||||
|
, data = data
|
||||||
|
}
|
||||||
|
|> Page.buildNoState { view = view }
|
||||||
|
|
||||||
|
|
||||||
|
type alias Data =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
data : DataSource Data
|
||||||
|
data =
|
||||||
|
DataSource.succeed ()
|
||||||
|
|
||||||
|
|
||||||
|
head :
|
||||||
|
StaticPayload Data RouteParams
|
||||||
|
-> List Head.Tag
|
||||||
|
head static =
|
||||||
|
Seo.summary
|
||||||
|
{ canonicalUrlOverride = Nothing
|
||||||
|
, siteName = "elm-pages"
|
||||||
|
, image =
|
||||||
|
{ url = Pages.Url.external "TODO"
|
||||||
|
, alt = "elm-pages logo"
|
||||||
|
, dimensions = Nothing
|
||||||
|
, mimeType = Nothing
|
||||||
|
}
|
||||||
|
, description = "TODO"
|
||||||
|
, locale = Nothing
|
||||||
|
, title = "TODO title" -- metadata.title -- TODO
|
||||||
|
}
|
||||||
|
|> Seo.website
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
Maybe PageUrl
|
||||||
|
-> Shared.Model
|
||||||
|
-> StaticPayload Data RouteParams
|
||||||
|
-> View Msg
|
||||||
|
view maybeUrl sharedModel static =
|
||||||
|
{ title = "Links"
|
||||||
|
, body =
|
||||||
|
[ Html.a [ Attr.href "/" ]
|
||||||
|
[ text "Root page" ]
|
||||||
|
]
|
||||||
|
}
|
105
examples/end-to-end/src/Shared.elm
Normal file
105
examples/end-to-end/src/Shared.elm
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
module Shared exposing (Data, Model, Msg(..), SharedMsg(..), template)
|
||||||
|
|
||||||
|
import Browser.Navigation
|
||||||
|
import Css.Global
|
||||||
|
import DataSource
|
||||||
|
import DataSource.Http
|
||||||
|
import Html exposing (Html)
|
||||||
|
import Html.Styled
|
||||||
|
import OptimizedDecoder as D
|
||||||
|
import Pages.Flags
|
||||||
|
import Pages.PageUrl exposing (PageUrl)
|
||||||
|
import Path exposing (Path)
|
||||||
|
import Route exposing (Route)
|
||||||
|
import Secrets
|
||||||
|
import SharedTemplate exposing (SharedTemplate)
|
||||||
|
import Tailwind.Utilities
|
||||||
|
import View exposing (View)
|
||||||
|
|
||||||
|
|
||||||
|
template : SharedTemplate Msg Model Data msg
|
||||||
|
template =
|
||||||
|
{ init = init
|
||||||
|
, update = update
|
||||||
|
, view = view
|
||||||
|
, data = data
|
||||||
|
, subscriptions = subscriptions
|
||||||
|
, onPageChange = Just OnPageChange
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Msg
|
||||||
|
= OnPageChange
|
||||||
|
{ path : Path
|
||||||
|
, query : Maybe String
|
||||||
|
, fragment : Maybe String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Data =
|
||||||
|
()
|
||||||
|
|
||||||
|
|
||||||
|
type SharedMsg
|
||||||
|
= NoOp
|
||||||
|
|
||||||
|
|
||||||
|
type alias Model =
|
||||||
|
{ showMobileMenu : Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init :
|
||||||
|
Maybe Browser.Navigation.Key
|
||||||
|
-> Pages.Flags.Flags
|
||||||
|
->
|
||||||
|
Maybe
|
||||||
|
{ path :
|
||||||
|
{ path : Path
|
||||||
|
, query : Maybe String
|
||||||
|
, fragment : Maybe String
|
||||||
|
}
|
||||||
|
, metadata : route
|
||||||
|
, pageUrl : Maybe PageUrl
|
||||||
|
}
|
||||||
|
-> ( Model, Cmd Msg )
|
||||||
|
init _ flags maybePagePath =
|
||||||
|
( { showMobileMenu = False }
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||||
|
update msg model =
|
||||||
|
case msg of
|
||||||
|
OnPageChange _ ->
|
||||||
|
( { model | showMobileMenu = False }, Cmd.none )
|
||||||
|
|
||||||
|
|
||||||
|
subscriptions : Path -> Model -> Sub Msg
|
||||||
|
subscriptions _ _ =
|
||||||
|
Sub.none
|
||||||
|
|
||||||
|
|
||||||
|
data : DataSource.DataSource Data
|
||||||
|
data =
|
||||||
|
DataSource.succeed ()
|
||||||
|
|
||||||
|
|
||||||
|
view :
|
||||||
|
Data
|
||||||
|
->
|
||||||
|
{ path : Path
|
||||||
|
, route : Maybe Route
|
||||||
|
}
|
||||||
|
-> Model
|
||||||
|
-> (Msg -> msg)
|
||||||
|
-> View msg
|
||||||
|
-> { body : Html msg, title : String }
|
||||||
|
view stars page model toMsg pageView =
|
||||||
|
{ body =
|
||||||
|
Html.Styled.div []
|
||||||
|
pageView.body
|
||||||
|
|> Html.Styled.toUnstyled
|
||||||
|
, title = pageView.title
|
||||||
|
}
|
93
examples/end-to-end/src/Site.elm
Normal file
93
examples/end-to-end/src/Site.elm
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
module Site exposing (config)
|
||||||
|
|
||||||
|
import Cloudinary
|
||||||
|
import DataSource
|
||||||
|
import Head
|
||||||
|
import MimeType
|
||||||
|
import Pages.Manifest as Manifest
|
||||||
|
import Pages.Url
|
||||||
|
import Route exposing (Route)
|
||||||
|
import SiteConfig exposing (SiteConfig)
|
||||||
|
|
||||||
|
|
||||||
|
config : SiteConfig Data
|
||||||
|
config =
|
||||||
|
\routes ->
|
||||||
|
{ data = data
|
||||||
|
, canonicalUrl = canonicalUrl
|
||||||
|
, manifest = manifest
|
||||||
|
, head = head
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Data =
|
||||||
|
{ siteName : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data : DataSource.DataSource Data
|
||||||
|
data =
|
||||||
|
DataSource.map Data
|
||||||
|
--(StaticFile.request "site-name.txt" StaticFile.body)
|
||||||
|
(DataSource.succeed "site-name")
|
||||||
|
|
||||||
|
|
||||||
|
head : Data -> List Head.Tag
|
||||||
|
head static =
|
||||||
|
[ Head.icon [ ( 32, 32 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 32)
|
||||||
|
, Head.icon [ ( 16, 16 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 16)
|
||||||
|
, Head.appleTouchIcon (Just 180) (cloudinaryIcon MimeType.Png 180)
|
||||||
|
, Head.appleTouchIcon (Just 192) (cloudinaryIcon MimeType.Png 192)
|
||||||
|
, Head.sitemapLink "/sitemap.xml"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
canonicalUrl : String
|
||||||
|
canonicalUrl =
|
||||||
|
"https://elm-pages.com"
|
||||||
|
|
||||||
|
|
||||||
|
manifest : Data -> Manifest.Config
|
||||||
|
manifest static =
|
||||||
|
Manifest.init
|
||||||
|
{ name = static.siteName
|
||||||
|
, description = "elm-pages - " ++ tagline
|
||||||
|
, startUrl = Route.Index |> Route.toPath
|
||||||
|
, icons =
|
||||||
|
[ icon webp 192
|
||||||
|
, icon webp 512
|
||||||
|
, icon MimeType.Png 192
|
||||||
|
, icon MimeType.Png 512
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|> Manifest.withShortName "elm-pages"
|
||||||
|
|
||||||
|
|
||||||
|
tagline : String
|
||||||
|
tagline =
|
||||||
|
"A statically typed site generator"
|
||||||
|
|
||||||
|
|
||||||
|
webp : MimeType.MimeImage
|
||||||
|
webp =
|
||||||
|
MimeType.OtherImage "webp"
|
||||||
|
|
||||||
|
|
||||||
|
icon :
|
||||||
|
MimeType.MimeImage
|
||||||
|
-> Int
|
||||||
|
-> Manifest.Icon
|
||||||
|
icon format width =
|
||||||
|
{ src = cloudinaryIcon format width
|
||||||
|
, sizes = [ ( width, width ) ]
|
||||||
|
, mimeType = format |> Just
|
||||||
|
, purposes = [ Manifest.IconPurposeAny, Manifest.IconPurposeMaskable ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
cloudinaryIcon :
|
||||||
|
MimeType.MimeImage
|
||||||
|
-> Int
|
||||||
|
-> Pages.Url.Url
|
||||||
|
cloudinaryIcon mimeType width =
|
||||||
|
Cloudinary.urlSquare "v1603234028/elm-pages/elm-pages-icon" (Just mimeType) width
|
23
examples/end-to-end/src/View.elm
Normal file
23
examples/end-to-end/src/View.elm
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
module View exposing (View, map, placeholder)
|
||||||
|
|
||||||
|
import Html.Styled exposing (text)
|
||||||
|
|
||||||
|
|
||||||
|
type alias View msg =
|
||||||
|
{ title : String
|
||||||
|
, body : List (Html.Styled.Html msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
map : (msg1 -> msg2) -> View msg1 -> View msg2
|
||||||
|
map fn doc =
|
||||||
|
{ title = doc.title
|
||||||
|
, body = List.map (Html.Styled.map fn) doc.body
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
placeholder : String -> View msg
|
||||||
|
placeholder moduleName =
|
||||||
|
{ title = "Placeholder - " ++ moduleName
|
||||||
|
, body = [ text moduleName ]
|
||||||
|
}
|
@ -29,8 +29,10 @@
|
|||||||
"miniBill/elm-codec": "1.2.0",
|
"miniBill/elm-codec": "1.2.0",
|
||||||
"noahzgordon/elm-color-extra": "1.0.2",
|
"noahzgordon/elm-color-extra": "1.0.2",
|
||||||
"pablohirafuji/elm-syntax-highlight": "3.4.0",
|
"pablohirafuji/elm-syntax-highlight": "3.4.0",
|
||||||
|
"robinheghan/murmur3": "1.0.0",
|
||||||
"rtfeldman/elm-css": "16.1.1",
|
"rtfeldman/elm-css": "16.1.1",
|
||||||
"tripokey/elm-fuzzy": "5.2.1",
|
"tripokey/elm-fuzzy": "5.2.1",
|
||||||
|
"vito/elm-ansi": "10.0.1",
|
||||||
"zwilias/json-decode-exploration": "6.0.0"
|
"zwilias/json-decode-exploration": "6.0.0"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
@ -6,11 +6,12 @@ import Html exposing (Html)
|
|||||||
import Pages.Flags
|
import Pages.Flags
|
||||||
import Pages.PageUrl exposing (PageUrl)
|
import Pages.PageUrl exposing (PageUrl)
|
||||||
import Path exposing (Path)
|
import Path exposing (Path)
|
||||||
|
import Route exposing (Route)
|
||||||
import SharedTemplate exposing (SharedTemplate)
|
import SharedTemplate exposing (SharedTemplate)
|
||||||
import View exposing (View)
|
import View exposing (View)
|
||||||
|
|
||||||
|
|
||||||
template : SharedTemplate Msg Model Data SharedMsg msg
|
template : SharedTemplate Msg Model Data msg
|
||||||
template =
|
template =
|
||||||
{ init = init
|
{ init = init
|
||||||
, update = update
|
, update = update
|
||||||
@ -18,7 +19,6 @@ template =
|
|||||||
, data = data
|
, data = data
|
||||||
, subscriptions = subscriptions
|
, subscriptions = subscriptions
|
||||||
, onPageChange = Just OnPageChange
|
, onPageChange = Just OnPageChange
|
||||||
, sharedMsg = SharedMsg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ view :
|
|||||||
Data
|
Data
|
||||||
->
|
->
|
||||||
{ path : Path
|
{ path : Path
|
||||||
, frontmatter : route
|
, route : Maybe Route
|
||||||
}
|
}
|
||||||
-> Model
|
-> Model
|
||||||
-> (Msg -> msg)
|
-> (Msg -> msg)
|
||||||
|
@ -28,8 +28,10 @@
|
|||||||
"miniBill/elm-codec": "2.0.0",
|
"miniBill/elm-codec": "2.0.0",
|
||||||
"noahzgordon/elm-color-extra": "1.0.2",
|
"noahzgordon/elm-color-extra": "1.0.2",
|
||||||
"pablohirafuji/elm-syntax-highlight": "3.4.0",
|
"pablohirafuji/elm-syntax-highlight": "3.4.0",
|
||||||
|
"robinheghan/murmur3": "1.0.0",
|
||||||
"rtfeldman/elm-css": "16.1.1",
|
"rtfeldman/elm-css": "16.1.1",
|
||||||
"tripokey/elm-fuzzy": "5.2.1",
|
"tripokey/elm-fuzzy": "5.2.1",
|
||||||
|
"vito/elm-ansi": "10.0.1",
|
||||||
"zwilias/json-decode-exploration": "6.0.0"
|
"zwilias/json-decode-exploration": "6.0.0"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"elm-version": "0.19.1",
|
"elm-version": "0.19.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"direct": {
|
"direct": {
|
||||||
|
"MartinSStewart/elm-serialize": "1.2.5",
|
||||||
"avh4/elm-color": "1.0.0",
|
"avh4/elm-color": "1.0.0",
|
||||||
"billstclair/elm-xml-eeue56": "1.0.1",
|
"billstclair/elm-xml-eeue56": "1.0.1",
|
||||||
"danyx23/elm-mimetype": "4.0.1",
|
"danyx23/elm-mimetype": "4.0.1",
|
||||||
@ -46,6 +47,8 @@
|
|||||||
"zwilias/json-decode-exploration": "6.0.0"
|
"zwilias/json-decode-exploration": "6.0.0"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
|
"bburdette/toop": "1.0.1",
|
||||||
|
"danfishgold/base64-bytes": "1.1.0",
|
||||||
"elm/bytes": "1.0.8",
|
"elm/bytes": "1.0.8",
|
||||||
"elm/file": "1.0.5",
|
"elm/file": "1.0.5",
|
||||||
"elm/parser": "1.1.0",
|
"elm/parser": "1.1.0",
|
||||||
|
1483
examples/slides/package-lock.json
generated
1483
examples/slides/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -70,7 +70,7 @@ type alias PageWithState routeParams templateData templateModel templateMsg =
|
|||||||
StaticPayload templateData routeParams
|
StaticPayload templateData routeParams
|
||||||
-> List Head.Tag
|
-> List Head.Tag
|
||||||
, init : Maybe PageUrl -> Shared.Model -> StaticPayload templateData routeParams -> ( templateModel, Cmd templateMsg )
|
, init : Maybe PageUrl -> Shared.Model -> StaticPayload templateData routeParams -> ( templateModel, Cmd templateMsg )
|
||||||
, update : PageUrl -> StaticPayload templateData routeParams -> Maybe Browser.Navigation.Key -> templateMsg -> templateModel -> Shared.Model -> ( templateModel, Cmd templateMsg, Maybe Shared.SharedMsg )
|
, update : PageUrl -> StaticPayload templateData routeParams -> Maybe Browser.Navigation.Key -> templateMsg -> templateModel -> Shared.Model -> ( templateModel, Cmd templateMsg, Maybe Shared.Msg )
|
||||||
, subscriptions : Maybe PageUrl -> routeParams -> Path -> templateModel -> Shared.Model -> Sub templateMsg
|
, subscriptions : Maybe PageUrl -> routeParams -> Path -> templateModel -> Shared.Model -> Sub templateMsg
|
||||||
, handleRoute : { moduleName : List String, routePattern : RoutePattern } -> (routeParams -> List ( String, String )) -> routeParams -> DataSource (Maybe NotFoundReason)
|
, handleRoute : { moduleName : List String, routePattern : RoutePattern } -> (routeParams -> List ( String, String )) -> routeParams -> DataSource (Maybe NotFoundReason)
|
||||||
, kind : String
|
, kind : String
|
||||||
@ -188,7 +188,7 @@ buildWithSharedState :
|
|||||||
-> StaticPayload templateData routeParams
|
-> StaticPayload templateData routeParams
|
||||||
-> View templateMsg
|
-> View templateMsg
|
||||||
, init : Maybe PageUrl -> Shared.Model -> StaticPayload templateData routeParams -> ( templateModel, Cmd templateMsg )
|
, init : Maybe PageUrl -> Shared.Model -> StaticPayload templateData routeParams -> ( templateModel, Cmd templateMsg )
|
||||||
, update : PageUrl -> Maybe Browser.Navigation.Key -> Shared.Model -> StaticPayload templateData routeParams -> templateMsg -> templateModel -> ( templateModel, Cmd templateMsg, Maybe Shared.SharedMsg )
|
, update : PageUrl -> Maybe Browser.Navigation.Key -> Shared.Model -> StaticPayload templateData routeParams -> templateMsg -> templateModel -> ( templateModel, Cmd templateMsg, Maybe Shared.Msg )
|
||||||
, subscriptions : Maybe PageUrl -> routeParams -> Path -> templateModel -> Shared.Model -> Sub templateMsg
|
, subscriptions : Maybe PageUrl -> routeParams -> Path -> templateModel -> Shared.Model -> Sub templateMsg
|
||||||
}
|
}
|
||||||
-> Builder routeParams templateData
|
-> Builder routeParams templateData
|
||||||
|
@ -10,7 +10,7 @@ import Route exposing (Route)
|
|||||||
import View exposing (View)
|
import View exposing (View)
|
||||||
|
|
||||||
|
|
||||||
type alias SharedTemplate msg sharedModel sharedData sharedMsg mappedMsg =
|
type alias SharedTemplate msg sharedModel sharedData mappedMsg =
|
||||||
{ init :
|
{ init :
|
||||||
Maybe Browser.Navigation.Key
|
Maybe Browser.Navigation.Key
|
||||||
-> Flags
|
-> Flags
|
||||||
@ -30,7 +30,7 @@ type alias SharedTemplate msg sharedModel sharedData sharedMsg mappedMsg =
|
|||||||
sharedData
|
sharedData
|
||||||
->
|
->
|
||||||
{ path : Path
|
{ path : Path
|
||||||
, frontmatter : Maybe Route
|
, route : Maybe Route
|
||||||
}
|
}
|
||||||
-> sharedModel
|
-> sharedModel
|
||||||
-> (msg -> mappedMsg)
|
-> (msg -> mappedMsg)
|
||||||
@ -46,5 +46,4 @@ type alias SharedTemplate msg sharedModel sharedData sharedMsg mappedMsg =
|
|||||||
}
|
}
|
||||||
-> msg
|
-> msg
|
||||||
)
|
)
|
||||||
, sharedMsg : sharedMsg -> msg
|
|
||||||
}
|
}
|
||||||
|
38
generator/src/basepath-middleware.js
Normal file
38
generator/src/basepath-middleware.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
const parseUrl = require("url").parse;
|
||||||
|
|
||||||
|
// this middleware is only active when (config.base !== '/')
|
||||||
|
|
||||||
|
module.exports = function baseMiddleware(base) {
|
||||||
|
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
|
||||||
|
return function viteBaseMiddleware(req, res, next) {
|
||||||
|
const url = req.url;
|
||||||
|
const parsed = parseUrl(url);
|
||||||
|
const path = parsed.pathname || "/";
|
||||||
|
|
||||||
|
if (path.startsWith(base)) {
|
||||||
|
// rewrite url to remove base.. this ensures that other middleware does
|
||||||
|
// not need to consider base being prepended or not
|
||||||
|
req.url = url.replace(base, "/");
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path === "/" || path === "/index.html") {
|
||||||
|
// redirect root visit to based url
|
||||||
|
res.writeHead(302, {
|
||||||
|
Location: base,
|
||||||
|
});
|
||||||
|
res.end();
|
||||||
|
return;
|
||||||
|
} else if (req.headers.accept && req.headers.accept.includes("text/html")) {
|
||||||
|
// non-based page visit
|
||||||
|
res.statusCode = 404;
|
||||||
|
res.end(
|
||||||
|
`The server is configured with a public base URL of ${base} - ` +
|
||||||
|
`did you mean to visit ${base}${url.slice(1)} instead?`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
@ -1,18 +1,25 @@
|
|||||||
const fs = require("./dir-helpers.js");
|
const fs = require("./dir-helpers.js");
|
||||||
|
const fsPromises = require("fs").promises;
|
||||||
|
|
||||||
|
const { restoreColor } = require("./error-formatter");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const spawnCallback = require("cross-spawn").spawn;
|
const spawnCallback = require("cross-spawn").spawn;
|
||||||
const codegen = require("./codegen.js");
|
const codegen = require("./codegen.js");
|
||||||
const terser = require("terser");
|
const terser = require("terser");
|
||||||
const matter = require("gray-matter");
|
const os = require("os");
|
||||||
const globby = require("globby");
|
const { Worker, SHARE_ENV } = require("worker_threads");
|
||||||
const preRenderHtml = require("./pre-render-html.js");
|
const { ensureDirSync } = require("./file-helpers.js");
|
||||||
|
let pool = [];
|
||||||
|
let pagesReady;
|
||||||
|
let pages = new Promise((resolve, reject) => {
|
||||||
|
pagesReady = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
const DIR_PATH = path.join(process.cwd());
|
const DIR_PATH = path.join(process.cwd());
|
||||||
const OUTPUT_FILE_NAME = "elm.js";
|
const OUTPUT_FILE_NAME = "elm.js";
|
||||||
|
|
||||||
let foundErrors = false;
|
|
||||||
process.on("unhandledRejection", (error) => {
|
process.on("unhandledRejection", (error) => {
|
||||||
console.error(error);
|
console.error("Unhandled: ", error);
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -23,146 +30,103 @@ const ELM_FILE_PATH = path.join(
|
|||||||
);
|
);
|
||||||
|
|
||||||
async function ensureRequiredDirs() {
|
async function ensureRequiredDirs() {
|
||||||
await fs.tryMkdir(`dist`);
|
ensureDirSync(`dist`);
|
||||||
|
ensureDirSync(path.join(process.cwd(), ".elm-pages", "http-response-cache"));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run(options) {
|
async function run(options) {
|
||||||
await ensureRequiredDirs();
|
await ensureRequiredDirs();
|
||||||
XMLHttpRequest = require("xhr2");
|
// since init/update are never called in pre-renders, and DataSource.Http is called using undici
|
||||||
|
// we can provide a fake HTTP instead of xhr2 (which is otherwise needed for Elm HTTP requests from Node)
|
||||||
|
XMLHttpRequest = {};
|
||||||
|
|
||||||
const generateCode = codegen.generate();
|
const generateCode = codegen.generate(options.base);
|
||||||
|
|
||||||
const copyDone = copyAssets();
|
const copyDone = copyAssets();
|
||||||
await generateCode;
|
await generateCode;
|
||||||
const cliDone = runCli(options);
|
const cliDone = runCli(options);
|
||||||
const compileClientDone = compileElm(options);
|
const compileClientDone = compileElm(options);
|
||||||
|
|
||||||
|
try {
|
||||||
await Promise.all([copyDone, cliDone, compileClientDone]);
|
await Promise.all([copyDone, cliDone, compileClientDone]);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} basePath
|
||||||
|
*/
|
||||||
|
function initWorker(basePath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let newWorker = {
|
||||||
|
worker: new Worker(path.join(__dirname, "./render-worker.js"), {
|
||||||
|
env: SHARE_ENV,
|
||||||
|
workerData: { basePath },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
newWorker.worker.once("online", () => {
|
||||||
|
newWorker.worker.on("message", (message) => {
|
||||||
|
if (message.tag === "all-paths") {
|
||||||
|
pagesReady(JSON.parse(message.data));
|
||||||
|
} else if (message.tag === "error") {
|
||||||
|
process.exitCode = 1;
|
||||||
|
console.error(restoreColor(message.data.errorsJson));
|
||||||
|
buildNextPage(newWorker);
|
||||||
|
} else if (message.tag === "done") {
|
||||||
|
buildNextPage(newWorker);
|
||||||
|
} else {
|
||||||
|
throw `Unhandled tag ${message.tag}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
newWorker.worker.on("error", (error) => {
|
||||||
|
console.error("Unhandled worker exception", error);
|
||||||
|
process.exitCode = 1;
|
||||||
|
buildNextPage(newWorker);
|
||||||
|
});
|
||||||
|
resolve(newWorker);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
function prepareStaticPathsNew(thread) {
|
||||||
|
thread.worker.postMessage({
|
||||||
|
mode: "build",
|
||||||
|
tag: "render",
|
||||||
|
pathname: "/all-paths.json",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function buildNextPage(thread) {
|
||||||
|
let nextPage = (await pages).pop();
|
||||||
|
if (nextPage) {
|
||||||
|
thread.worker.postMessage({
|
||||||
|
mode: "build",
|
||||||
|
tag: "render",
|
||||||
|
pathname: nextPage,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
thread.worker.terminate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runCli(options) {
|
async function runCli(options) {
|
||||||
await compileCliApp(options);
|
await compileCliApp(options);
|
||||||
runElmApp();
|
const cpuCount = os.cpus().length;
|
||||||
}
|
console.log("Threads: ", cpuCount);
|
||||||
|
|
||||||
function runElmApp() {
|
const getPathsWorker = initWorker(options.base);
|
||||||
process.on("beforeExit", (code) => {
|
getPathsWorker.then(prepareStaticPathsNew);
|
||||||
if (foundErrors) {
|
const threadsToCreate = Math.max(1, cpuCount / 2 - 1);
|
||||||
process.exitCode = 1;
|
pool.push(getPathsWorker);
|
||||||
} else {
|
for (let index = 0; index < threadsToCreate - 1; index++) {
|
||||||
|
pool.push(initWorker(options.base));
|
||||||
}
|
}
|
||||||
|
pool.forEach((threadPromise) => {
|
||||||
|
threadPromise.then(buildNextPage);
|
||||||
});
|
});
|
||||||
|
|
||||||
return new Promise((resolve, _) => {
|
|
||||||
const mode /** @type { "dev" | "prod" } */ = "elm-to-html-beta";
|
|
||||||
const staticHttpCache = {};
|
|
||||||
const app = require(ELM_FILE_PATH).Elm.TemplateModulesBeta.init({
|
|
||||||
flags: { secrets: process.env, mode, staticHttpCache },
|
|
||||||
});
|
|
||||||
|
|
||||||
app.ports.toJsPort.subscribe(async (/** @type { FromElm } */ fromElm) => {
|
|
||||||
// console.log({ fromElm });
|
|
||||||
if (fromElm.command === "log") {
|
|
||||||
console.log(fromElm.value);
|
|
||||||
} else if (fromElm.tag === "InitialData") {
|
|
||||||
generateFiles(fromElm.args[0].filesToGenerate);
|
|
||||||
} else if (fromElm.tag === "PageProgress") {
|
|
||||||
outputString(fromElm);
|
|
||||||
} else if (fromElm.tag === "ReadFile") {
|
|
||||||
const filePath = fromElm.args[0];
|
|
||||||
try {
|
|
||||||
const fileContents = (await fs.readFile(filePath)).toString();
|
|
||||||
const parsedFile = matter(fileContents);
|
|
||||||
app.ports.fromJsPort.send({
|
|
||||||
tag: "GotFile",
|
|
||||||
data: {
|
|
||||||
filePath,
|
|
||||||
parsedFrontmatter: parsedFile.data,
|
|
||||||
withoutFrontmatter: parsedFile.content,
|
|
||||||
rawFile: fileContents,
|
|
||||||
jsonFile: jsonOrNull(fileContents),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
app.ports.fromJsPort.send({
|
|
||||||
tag: "BuildError",
|
|
||||||
data: { filePath },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (fromElm.tag === "Glob") {
|
|
||||||
const globPattern = fromElm.args[0];
|
|
||||||
const matchedPaths = await globby(globPattern);
|
|
||||||
|
|
||||||
app.ports.fromJsPort.send({
|
|
||||||
tag: "GotGlob",
|
|
||||||
data: { pattern: globPattern, result: matchedPaths },
|
|
||||||
});
|
|
||||||
} else if (fromElm.tag === "Errors") {
|
|
||||||
console.error(fromElm.args[0].errorString);
|
|
||||||
foundErrors = true;
|
|
||||||
} else {
|
|
||||||
console.log(fromElm);
|
|
||||||
throw "Unknown port tag.";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{ path: string; content: string; }[]} filesToGenerate
|
|
||||||
*/
|
|
||||||
async function generateFiles(filesToGenerate) {
|
|
||||||
filesToGenerate.forEach(async ({ path: pathToGenerate, content }) => {
|
|
||||||
const fullPath = `dist/${pathToGenerate}`;
|
|
||||||
console.log(`Generating file /${pathToGenerate}`);
|
|
||||||
await fs.tryMkdir(path.dirname(fullPath));
|
|
||||||
fs.writeFile(fullPath, content);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} route
|
|
||||||
*/
|
|
||||||
function cleanRoute(route) {
|
|
||||||
return route.replace(/(^\/|\/$)/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} cleanedRoute
|
|
||||||
*/
|
|
||||||
function pathToRoot(cleanedRoute) {
|
|
||||||
return cleanedRoute === ""
|
|
||||||
? cleanedRoute
|
|
||||||
: cleanedRoute
|
|
||||||
.split("/")
|
|
||||||
.map((_) => "..")
|
|
||||||
.join("/")
|
|
||||||
.replace(/\.$/, "./");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} route
|
|
||||||
*/
|
|
||||||
function baseRoute(route) {
|
|
||||||
const cleanedRoute = cleanRoute(route);
|
|
||||||
return cleanedRoute === "" ? "./" : pathToRoot(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function outputString(/** @type { PageProgress } */ fromElm) {
|
|
||||||
const args = fromElm.args[0];
|
|
||||||
console.log(`Pre-rendered /${args.route}`);
|
|
||||||
const normalizedRoute = args.route.replace(/index$/, "");
|
|
||||||
// await fs.mkdir(`./dist/${normalizedRoute}`, { recursive: true });
|
|
||||||
await fs.tryMkdir(`./dist/${normalizedRoute}`);
|
|
||||||
const contentJsonString = JSON.stringify({
|
|
||||||
is404: args.is404,
|
|
||||||
staticData: args.contentJson,
|
|
||||||
});
|
|
||||||
fs.writeFile(
|
|
||||||
`dist/${normalizedRoute}/index.html`,
|
|
||||||
preRenderHtml(args, contentJsonString, false)
|
|
||||||
);
|
|
||||||
fs.writeFile(`dist/${normalizedRoute}/content.json`, contentJsonString);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compileElm(options) {
|
async function compileElm(options) {
|
||||||
@ -175,19 +139,33 @@ async function compileElm(options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
|
function elmOptimizeLevel2(elmEntrypointPath, outputPath, cwd) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const fullOutputPath = cwd ? path.join(cwd, outputPath) : outputPath;
|
const fullOutputPath = cwd ? path.join(cwd, outputPath) : outputPath;
|
||||||
if (fs.existsSync(fullOutputPath)) {
|
const subprocess = spawnCallback(
|
||||||
fs.rmSync(fullOutputPath, {
|
`elm-optimize-level-2`,
|
||||||
force: true /* ignore errors if file doesn't exist */,
|
[elmEntrypointPath, "--output", outputPath],
|
||||||
});
|
{
|
||||||
}
|
// ignore stdout
|
||||||
const subprocess = runElm(options, elmEntrypointPath, outputPath, cwd);
|
// stdio: ["inherit", "ignore", "inherit"],
|
||||||
|
|
||||||
|
cwd: cwd,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
let commandOutput = "";
|
||||||
|
|
||||||
|
subprocess.stderr.on("data", function (data) {
|
||||||
|
commandOutput += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
subprocess.on("exit", async (code) => {
|
||||||
|
if (code !== 0) {
|
||||||
|
process.exitCode = 1;
|
||||||
|
reject(commandOutput);
|
||||||
|
}
|
||||||
|
});
|
||||||
subprocess.on("close", async (code) => {
|
subprocess.on("close", async (code) => {
|
||||||
const fileOutputExists = await fs.exists(fullOutputPath);
|
if (code == 0 && (await fs.fileExists(fullOutputPath))) {
|
||||||
if (code == 0 && fileOutputExists) {
|
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
@ -202,30 +180,55 @@ function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
|
|||||||
* @param {string} outputPath
|
* @param {string} outputPath
|
||||||
* @param {string} cwd
|
* @param {string} cwd
|
||||||
*/
|
*/
|
||||||
function runElm(options, elmEntrypointPath, outputPath, cwd) {
|
async function spawnElmMake(options, elmEntrypointPath, outputPath, cwd) {
|
||||||
if (options.debug) {
|
if (options.debug) {
|
||||||
console.log("Running elm make");
|
await runElmMake(elmEntrypointPath, outputPath, cwd);
|
||||||
return spawnCallback(
|
|
||||||
`elm`,
|
|
||||||
["make", elmEntrypointPath, "--output", outputPath, "--debug"],
|
|
||||||
{
|
|
||||||
// ignore stdout
|
|
||||||
stdio: ["inherit", "ignore", "inherit"],
|
|
||||||
cwd: cwd,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
console.log("Running elm-optimize-level-2");
|
await elmOptimizeLevel2(elmEntrypointPath, outputPath, cwd);
|
||||||
return spawnCallback(
|
}
|
||||||
`elm-optimize-level-2`,
|
}
|
||||||
[elmEntrypointPath, "--output", outputPath],
|
|
||||||
|
function runElmMake(elmEntrypointPath, outputPath, cwd) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const subprocess = spawnCallback(
|
||||||
|
`elm`,
|
||||||
|
[
|
||||||
|
"make",
|
||||||
|
elmEntrypointPath,
|
||||||
|
"--output",
|
||||||
|
outputPath,
|
||||||
|
"--debug",
|
||||||
|
"--report",
|
||||||
|
"json",
|
||||||
|
],
|
||||||
{
|
{
|
||||||
// ignore stdout
|
// ignore stdout
|
||||||
stdio: ["inherit", "ignore", "inherit"],
|
// stdio: ["inherit", "ignore", "inherit"],
|
||||||
|
|
||||||
cwd: cwd,
|
cwd: cwd,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
const fullOutputPath = cwd ? path.join(cwd, outputPath) : outputPath;
|
||||||
|
if (await fs.fileExists(fullOutputPath)) {
|
||||||
|
await fsPromises.unlink(fullOutputPath, {
|
||||||
|
force: true /* ignore errors if file doesn't exist */,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
let commandOutput = "";
|
||||||
|
|
||||||
|
subprocess.stderr.on("data", function (data) {
|
||||||
|
commandOutput += data;
|
||||||
|
});
|
||||||
|
|
||||||
|
subprocess.on("close", async (code) => {
|
||||||
|
if (code == 0 && (await fs.fileExists(fullOutputPath))) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
process.exitCode = 1;
|
||||||
|
reject(restoreColor(JSON.parse(commandOutput).errors));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,7 +237,7 @@ function runElm(options, elmEntrypointPath, outputPath, cwd) {
|
|||||||
async function runTerser(filePath) {
|
async function runTerser(filePath) {
|
||||||
console.log("Running terser");
|
console.log("Running terser");
|
||||||
const minifiedElm = await terser.minify(
|
const minifiedElm = await terser.minify(
|
||||||
(await fs.readFile(filePath)).toString(),
|
(await fsPromises.readFile(filePath)).toString(),
|
||||||
{
|
{
|
||||||
ecma: 5,
|
ecma: 5,
|
||||||
|
|
||||||
@ -268,16 +271,18 @@ async function runTerser(filePath) {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (minifiedElm.code) {
|
if (minifiedElm.code) {
|
||||||
await fs.writeFile(filePath, minifiedElm.code);
|
await fsPromises.writeFile(filePath, minifiedElm.code);
|
||||||
} else {
|
} else {
|
||||||
throw "Error running terser.";
|
throw "Error running terser.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyAssets() {
|
async function copyAssets() {
|
||||||
fs.writeFile(
|
await fsPromises.writeFile(
|
||||||
"dist/elm-pages.js",
|
"dist/elm-pages.js",
|
||||||
fs.readFileSync(path.join(__dirname, "../static-code/elm-pages.js"))
|
await fsPromises.readFile(
|
||||||
|
path.join(__dirname, "../static-code/elm-pages.js")
|
||||||
|
)
|
||||||
);
|
);
|
||||||
fs.copyDirFlat("public", "dist");
|
fs.copyDirFlat("public", "dist");
|
||||||
}
|
}
|
||||||
@ -290,13 +295,25 @@ async function compileCliApp(options) {
|
|||||||
"./elm-stuff/elm-pages"
|
"./elm-stuff/elm-pages"
|
||||||
);
|
);
|
||||||
|
|
||||||
const elmFileContent = await fs.readFile(ELM_FILE_PATH, "utf-8");
|
const elmFileContent = await fsPromises.readFile(ELM_FILE_PATH, "utf-8");
|
||||||
await fs.writeFile(
|
await fsPromises.writeFile(
|
||||||
ELM_FILE_PATH,
|
ELM_FILE_PATH,
|
||||||
elmFileContent.replace(
|
elmFileContent
|
||||||
|
.replace(
|
||||||
/return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
|
/return \$elm\$json\$Json\$Encode\$string\(.REPLACE_ME_WITH_JSON_STRINGIFY.\)/g,
|
||||||
"return " + (options.debug ? "_Json_wrap(x)" : "x")
|
"return " + (options.debug ? "_Json_wrap(x)" : "x")
|
||||||
)
|
)
|
||||||
|
.replace(
|
||||||
|
"return ports ? { ports: ports } : {};",
|
||||||
|
`const die = function() {
|
||||||
|
managers = null
|
||||||
|
model = null
|
||||||
|
stepper = null
|
||||||
|
ports = null
|
||||||
|
}
|
||||||
|
|
||||||
|
return ports ? { ports: ports, die: die } : { die: die };`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,14 +333,3 @@ async function compileCliApp(options) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = { run };
|
module.exports = { run };
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} string
|
|
||||||
*/
|
|
||||||
function jsonOrNull(string) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(string);
|
|
||||||
} catch (e) {
|
|
||||||
return { invalidJson: e.toString() };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -18,8 +18,14 @@ async function main() {
|
|||||||
program
|
program
|
||||||
.command("build")
|
.command("build")
|
||||||
.option("--debug", "Skip terser and run elm make with --debug")
|
.option("--debug", "Skip terser and run elm make with --debug")
|
||||||
|
.option(
|
||||||
|
"--base <basePath>",
|
||||||
|
"build site to be served under a base path",
|
||||||
|
"/"
|
||||||
|
)
|
||||||
.description("run a full site build")
|
.description("run a full site build")
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
|
options.base = normalizeUrl(options.base);
|
||||||
await build.run(options);
|
await build.run(options);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -27,8 +33,9 @@ async function main() {
|
|||||||
.command("dev")
|
.command("dev")
|
||||||
.description("start a dev server")
|
.description("start a dev server")
|
||||||
.option("--port <number>", "serve site at localhost:<port>", "1234")
|
.option("--port <number>", "serve site at localhost:<port>", "1234")
|
||||||
|
.option("--base <basePath>", "serve site under a base path", "/")
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
console.log({ options });
|
options.base = normalizeUrl(options.base);
|
||||||
await dev.start(options);
|
await dev.start(options);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -51,7 +58,7 @@ async function main() {
|
|||||||
.description("open the docs for locally generated modules")
|
.description("open the docs for locally generated modules")
|
||||||
.option("--port <number>", "serve site at localhost:<port>", "8000")
|
.option("--port <number>", "serve site at localhost:<port>", "8000")
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
await codegen.generate();
|
await codegen.generate("/");
|
||||||
const DocServer = require("elm-doc-preview");
|
const DocServer = require("elm-doc-preview");
|
||||||
const server = new DocServer({
|
const server = new DocServer({
|
||||||
port: options.port,
|
port: options.port,
|
||||||
@ -65,4 +72,17 @@ async function main() {
|
|||||||
program.parse(process.argv);
|
program.parse(process.argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} pagePath
|
||||||
|
*/
|
||||||
|
function normalizeUrl(pagePath) {
|
||||||
|
if (!pagePath.startsWith("/")) {
|
||||||
|
pagePath = "/" + pagePath;
|
||||||
|
}
|
||||||
|
if (!pagePath.endsWith("/")) {
|
||||||
|
pagePath = pagePath + "/";
|
||||||
|
}
|
||||||
|
return pagePath;
|
||||||
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
@ -8,64 +8,66 @@ const path = require("path");
|
|||||||
const { ensureDirSync, deleteIfExists } = require("./file-helpers.js");
|
const { ensureDirSync, deleteIfExists } = require("./file-helpers.js");
|
||||||
global.builtAt = new Date();
|
global.builtAt = new Date();
|
||||||
|
|
||||||
async function generate() {
|
/**
|
||||||
await writeFiles();
|
* @param {string} basePath
|
||||||
}
|
*/
|
||||||
|
async function generate(basePath) {
|
||||||
async function writeFiles() {
|
const cliCode = generateTemplateModuleConnector(basePath, "cli");
|
||||||
const cliCode = generateTemplateModuleConnector("cli");
|
const browserCode = generateTemplateModuleConnector(basePath, "browser");
|
||||||
const browserCode = generateTemplateModuleConnector("browser");
|
|
||||||
ensureDirSync("./elm-stuff");
|
ensureDirSync("./elm-stuff");
|
||||||
ensureDirSync("./.elm-pages");
|
ensureDirSync("./.elm-pages");
|
||||||
ensureDirSync("./elm-stuff/elm-pages/.elm-pages");
|
ensureDirSync("./elm-stuff/elm-pages/.elm-pages");
|
||||||
|
|
||||||
fs.copyFileSync(path.join(__dirname, `./Page.elm`), `./.elm-pages/Page.elm`);
|
const uiFileContent = elmPagesUiFile();
|
||||||
fs.copyFileSync(
|
await Promise.all([
|
||||||
|
fs.promises.copyFile(
|
||||||
|
path.join(__dirname, `./Page.elm`),
|
||||||
|
`./.elm-pages/Page.elm`
|
||||||
|
),
|
||||||
|
fs.promises.copyFile(
|
||||||
path.join(__dirname, `./elm-application.json`),
|
path.join(__dirname, `./elm-application.json`),
|
||||||
`./elm-stuff/elm-pages/elm-application.json`
|
`./elm-stuff/elm-pages/elm-application.json`
|
||||||
);
|
),
|
||||||
fs.copyFileSync(
|
fs.promises.copyFile(
|
||||||
path.join(__dirname, `./Page.elm`),
|
path.join(__dirname, `./Page.elm`),
|
||||||
`./elm-stuff/elm-pages/.elm-pages/Page.elm`
|
`./elm-stuff/elm-pages/.elm-pages/Page.elm`
|
||||||
);
|
),
|
||||||
fs.copyFileSync(
|
fs.promises.copyFile(
|
||||||
path.join(__dirname, `./SharedTemplate.elm`),
|
path.join(__dirname, `./SharedTemplate.elm`),
|
||||||
`./.elm-pages/SharedTemplate.elm`
|
`./.elm-pages/SharedTemplate.elm`
|
||||||
);
|
),
|
||||||
fs.copyFileSync(
|
fs.promises.copyFile(
|
||||||
path.join(__dirname, `./SharedTemplate.elm`),
|
path.join(__dirname, `./SharedTemplate.elm`),
|
||||||
`./elm-stuff/elm-pages/.elm-pages/SharedTemplate.elm`
|
`./elm-stuff/elm-pages/.elm-pages/SharedTemplate.elm`
|
||||||
);
|
),
|
||||||
fs.copyFileSync(
|
fs.promises.copyFile(
|
||||||
path.join(__dirname, `./SiteConfig.elm`),
|
path.join(__dirname, `./SiteConfig.elm`),
|
||||||
`./.elm-pages/SiteConfig.elm`
|
`./.elm-pages/SiteConfig.elm`
|
||||||
);
|
),
|
||||||
fs.copyFileSync(
|
fs.promises.copyFile(
|
||||||
path.join(__dirname, `./SiteConfig.elm`),
|
path.join(__dirname, `./SiteConfig.elm`),
|
||||||
`./elm-stuff/elm-pages/.elm-pages/SiteConfig.elm`
|
`./elm-stuff/elm-pages/.elm-pages/SiteConfig.elm`
|
||||||
);
|
),
|
||||||
|
fs.promises.writeFile("./.elm-pages/Pages.elm", uiFileContent),
|
||||||
const uiFileContent = elmPagesUiFile();
|
|
||||||
fs.writeFileSync("./.elm-pages/Pages.elm", uiFileContent);
|
|
||||||
|
|
||||||
// write `Pages.elm` with cli interface
|
// write `Pages.elm` with cli interface
|
||||||
fs.writeFileSync(
|
fs.promises.writeFile(
|
||||||
"./elm-stuff/elm-pages/.elm-pages/Pages.elm",
|
"./elm-stuff/elm-pages/.elm-pages/Pages.elm",
|
||||||
elmPagesCliFile()
|
elmPagesCliFile()
|
||||||
);
|
),
|
||||||
fs.writeFileSync(
|
fs.promises.writeFile(
|
||||||
"./elm-stuff/elm-pages/.elm-pages/TemplateModulesBeta.elm",
|
"./elm-stuff/elm-pages/.elm-pages/TemplateModulesBeta.elm",
|
||||||
cliCode.mainModule
|
cliCode.mainModule
|
||||||
);
|
),
|
||||||
fs.writeFileSync(
|
fs.promises.writeFile(
|
||||||
"./elm-stuff/elm-pages/.elm-pages/Route.elm",
|
"./elm-stuff/elm-pages/.elm-pages/Route.elm",
|
||||||
cliCode.routesModule
|
cliCode.routesModule
|
||||||
);
|
),
|
||||||
fs.writeFileSync(
|
fs.promises.writeFile(
|
||||||
"./.elm-pages/TemplateModulesBeta.elm",
|
"./.elm-pages/TemplateModulesBeta.elm",
|
||||||
browserCode.mainModule
|
browserCode.mainModule
|
||||||
);
|
),
|
||||||
fs.writeFileSync("./.elm-pages/Route.elm", browserCode.routesModule);
|
fs.promises.writeFile("./.elm-pages/Route.elm", browserCode.routesModule),
|
||||||
|
]);
|
||||||
|
|
||||||
// write modified elm.json to elm-stuff/elm-pages/
|
// write modified elm.json to elm-stuff/elm-pages/
|
||||||
copyModifiedElmJson();
|
copyModifiedElmJson();
|
||||||
|
@ -37,7 +37,10 @@ async function spawnElmMake(elmEntrypointPath, outputPath, cwd) {
|
|||||||
|
|
||||||
async function compileElmForBrowser() {
|
async function compileElmForBrowser() {
|
||||||
await runElm("./.elm-pages/TemplateModulesBeta.elm", pathToClientElm);
|
await runElm("./.elm-pages/TemplateModulesBeta.elm", pathToClientElm);
|
||||||
return inject(await fs.promises.readFile(pathToClientElm, "utf-8"));
|
return fs.promises.writeFile(
|
||||||
|
"./.elm-pages/cache/elm.js",
|
||||||
|
inject(await fs.promises.readFile(pathToClientElm, "utf-8"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const chokidar = require("chokidar");
|
const chokidar = require("chokidar");
|
||||||
const compiledElmPath = path.join(process.cwd(), "elm-stuff/elm-pages/elm.js");
|
|
||||||
const renderer = require("../../generator/src/render");
|
|
||||||
const { spawnElmMake, compileElmForBrowser } = require("./compile-elm.js");
|
const { spawnElmMake, compileElmForBrowser } = require("./compile-elm.js");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
const codegen = require("./codegen.js");
|
const codegen = require("./codegen.js");
|
||||||
@ -10,14 +8,26 @@ const kleur = require("kleur");
|
|||||||
const serveStatic = require("serve-static");
|
const serveStatic = require("serve-static");
|
||||||
const connect = require("connect");
|
const connect = require("connect");
|
||||||
const { restoreColor } = require("./error-formatter");
|
const { restoreColor } = require("./error-formatter");
|
||||||
let Elm;
|
const { Worker, SHARE_ENV } = require("worker_threads");
|
||||||
|
const os = require("os");
|
||||||
|
const { ensureDirSync } = require("./file-helpers.js");
|
||||||
|
const baseMiddleware = require("./basepath-middleware.js");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ port: string; base: string }} options
|
||||||
|
*/
|
||||||
async function start(options) {
|
async function start(options) {
|
||||||
|
let threadReadyQueue = [];
|
||||||
|
let pool = [];
|
||||||
|
ensureDirSync(path.join(process.cwd(), ".elm-pages", "http-response-cache"));
|
||||||
|
const cpuCount = os.cpus().length;
|
||||||
|
|
||||||
const port = options.port;
|
const port = options.port;
|
||||||
global.staticHttpCache = {};
|
|
||||||
let elmMakeRunning = true;
|
let elmMakeRunning = true;
|
||||||
|
|
||||||
const serve = serveStatic("public/", { index: false });
|
const serve = serveStatic("public/", { index: false });
|
||||||
|
fs.mkdirSync(".elm-pages/cache", { recursive: true });
|
||||||
|
const serveCachedFiles = serveStatic(".elm-pages/cache", { index: false });
|
||||||
const generatedFilesDirectory = "elm-stuff/elm-pages/generated-files";
|
const generatedFilesDirectory = "elm-stuff/elm-pages/generated-files";
|
||||||
fs.mkdirSync(generatedFilesDirectory, { recursive: true });
|
fs.mkdirSync(generatedFilesDirectory, { recursive: true });
|
||||||
const serveStaticCode = serveStatic(
|
const serveStaticCode = serveStatic(
|
||||||
@ -33,17 +43,15 @@ async function start(options) {
|
|||||||
ignored: [/\.swp$/],
|
ignored: [/\.swp$/],
|
||||||
ignoreInitial: true,
|
ignoreInitial: true,
|
||||||
});
|
});
|
||||||
watchElmSourceDirs();
|
|
||||||
|
|
||||||
await codegen.generate();
|
await codegen.generate(options.base);
|
||||||
let clientElmMakeProcess = compileElmForBrowser();
|
let clientElmMakeProcess = compileElmForBrowser();
|
||||||
let pendingCliCompile = compileCliApp();
|
let pendingCliCompile = compileCliApp();
|
||||||
|
watchElmSourceDirs(true);
|
||||||
|
|
||||||
async function setup() {
|
async function setup() {
|
||||||
await codegen.generate();
|
|
||||||
await Promise.all([clientElmMakeProcess, pendingCliCompile])
|
await Promise.all([clientElmMakeProcess, pendingCliCompile])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Dev server ready");
|
|
||||||
elmMakeRunning = false;
|
elmMakeRunning = false;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -54,24 +62,33 @@ async function start(options) {
|
|||||||
`<http://localhost:${port}>`
|
`<http://localhost:${port}>`
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
|
const poolSize = Math.max(1, cpuCount / 2 - 1);
|
||||||
|
for (let index = 0; index < poolSize; index++) {
|
||||||
|
pool.push(initWorker(options.base));
|
||||||
|
}
|
||||||
|
runPendingWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
setup();
|
setup();
|
||||||
|
|
||||||
function watchElmSourceDirs() {
|
/**
|
||||||
|
* @param {boolean} initialRun
|
||||||
|
*/
|
||||||
|
async function watchElmSourceDirs(initialRun) {
|
||||||
|
if (initialRun) {
|
||||||
|
} else {
|
||||||
console.log("elm.json changed - reloading watchers");
|
console.log("elm.json changed - reloading watchers");
|
||||||
watcher.removeAllListeners();
|
watcher.removeAllListeners();
|
||||||
const sourceDirs = JSON.parse(fs.readFileSync("./elm.json").toString())[
|
}
|
||||||
"source-directories"
|
const sourceDirs = JSON.parse(
|
||||||
];
|
(await fs.promises.readFile("./elm.json")).toString()
|
||||||
console.log("Watching...", { sourceDirs });
|
)["source-directories"].filter(
|
||||||
|
(sourceDir) => path.resolve(sourceDir) !== path.resolve(".elm-pages")
|
||||||
|
);
|
||||||
|
|
||||||
watcher.add(sourceDirs);
|
watcher.add(sourceDirs);
|
||||||
watcher.add("./public/*.css");
|
watcher.add("./public/*.css");
|
||||||
}
|
watcher.add("./port-data-source.js");
|
||||||
|
|
||||||
function requireUncached() {
|
|
||||||
delete require.cache[require.resolve(compiledElmPath)];
|
|
||||||
Elm = require(compiledElmPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function compileCliApp() {
|
async function compileCliApp() {
|
||||||
@ -80,11 +97,13 @@ async function start(options) {
|
|||||||
"elm.js",
|
"elm.js",
|
||||||
"elm-stuff/elm-pages/"
|
"elm-stuff/elm-pages/"
|
||||||
);
|
);
|
||||||
requireUncached();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = connect()
|
const app = connect()
|
||||||
.use(timeMiddleware())
|
.use(timeMiddleware())
|
||||||
|
.use(baseMiddleware(options.base))
|
||||||
|
.use(awaitElmMiddleware)
|
||||||
|
.use(serveCachedFiles)
|
||||||
.use(serveStaticCode)
|
.use(serveStaticCode)
|
||||||
.use(serve)
|
.use(serve)
|
||||||
.use(processRequest);
|
.use(processRequest);
|
||||||
@ -95,27 +114,17 @@ async function start(options) {
|
|||||||
* @param {connect.NextHandleFunction} next
|
* @param {connect.NextHandleFunction} next
|
||||||
*/
|
*/
|
||||||
async function processRequest(request, response, next) {
|
async function processRequest(request, response, next) {
|
||||||
if (request.url && request.url.startsWith("/elm.js")) {
|
if (request.url && request.url.startsWith("/stream")) {
|
||||||
try {
|
|
||||||
await pendingCliCompile;
|
|
||||||
const clientElmJs = await clientElmMakeProcess;
|
|
||||||
response.writeHead(200, { "Content-Type": "text/javascript" });
|
|
||||||
response.end(clientElmJs);
|
|
||||||
} catch (elmCompilerError) {
|
|
||||||
response.writeHead(500, { "Content-Type": "application/json" });
|
|
||||||
response.end(elmCompilerError);
|
|
||||||
}
|
|
||||||
} else if (request.url && request.url.startsWith("/stream")) {
|
|
||||||
handleStream(request, response);
|
handleStream(request, response);
|
||||||
} else {
|
} else {
|
||||||
handleNavigationRequest(request, response, next);
|
await handleNavigationRequest(request, response, next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watcher.on("all", async function (eventName, pathThatChanged) {
|
watcher.on("all", async function (eventName, pathThatChanged) {
|
||||||
console.log({ pathThatChanged });
|
// console.log({ pathThatChanged });
|
||||||
if (pathThatChanged === "elm.json") {
|
if (pathThatChanged === "elm.json") {
|
||||||
watchElmSourceDirs();
|
watchElmSourceDirs(false);
|
||||||
} else if (pathThatChanged.endsWith(".css")) {
|
} else if (pathThatChanged.endsWith(".css")) {
|
||||||
clients.forEach((client) => {
|
clients.forEach((client) => {
|
||||||
client.response.write(`data: style.css\n\n`);
|
client.response.write(`data: style.css\n\n`);
|
||||||
@ -126,7 +135,7 @@ async function start(options) {
|
|||||||
let codegenError = null;
|
let codegenError = null;
|
||||||
if (needToRerunCodegen(eventName, pathThatChanged)) {
|
if (needToRerunCodegen(eventName, pathThatChanged)) {
|
||||||
try {
|
try {
|
||||||
await codegen.generate();
|
await codegen.generate(options.base);
|
||||||
clientElmMakeProcess = compileElmForBrowser();
|
clientElmMakeProcess = compileElmForBrowser();
|
||||||
pendingCliCompile = compileCliApp();
|
pendingCliCompile = compileCliApp();
|
||||||
|
|
||||||
@ -156,7 +165,6 @@ async function start(options) {
|
|||||||
clientElmMakeProcess = compileElmForBrowser();
|
clientElmMakeProcess = compileElmForBrowser();
|
||||||
pendingCliCompile = compileCliApp();
|
pendingCliCompile = compileCliApp();
|
||||||
}
|
}
|
||||||
let timestamp = Date.now();
|
|
||||||
|
|
||||||
Promise.all([clientElmMakeProcess, pendingCliCompile])
|
Promise.all([clientElmMakeProcess, pendingCliCompile])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -170,22 +178,23 @@ async function start(options) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const changedPathRelative = path.relative(process.cwd(), pathThatChanged);
|
// TODO use similar logic in the workers? Or don't use cache at all?
|
||||||
|
// const changedPathRelative = path.relative(process.cwd(), pathThatChanged);
|
||||||
Object.keys(global.staticHttpCache).forEach((dataSourceKey) => {
|
//
|
||||||
if (dataSourceKey.includes(`file://${changedPathRelative}`)) {
|
// Object.keys(global.staticHttpCache).forEach((dataSourceKey) => {
|
||||||
delete global.staticHttpCache[dataSourceKey];
|
// if (dataSourceKey.includes(`file://${changedPathRelative}`)) {
|
||||||
} else if (
|
// delete global.staticHttpCache[dataSourceKey];
|
||||||
(eventName === "add" ||
|
// } else if (
|
||||||
eventName === "unlink" ||
|
// (eventName === "add" ||
|
||||||
eventName === "change" ||
|
// eventName === "unlink" ||
|
||||||
eventName === "addDir" ||
|
// eventName === "change" ||
|
||||||
eventName === "unlinkDir") &&
|
// eventName === "addDir" ||
|
||||||
dataSourceKey.startsWith("glob://")
|
// eventName === "unlinkDir") &&
|
||||||
) {
|
// dataSourceKey.startsWith("glob://")
|
||||||
delete global.staticHttpCache[dataSourceKey];
|
// ) {
|
||||||
}
|
// delete global.staticHttpCache[dataSourceKey];
|
||||||
});
|
// }
|
||||||
|
// });
|
||||||
clients.forEach((client) => {
|
clients.forEach((client) => {
|
||||||
client.response.write(`data: content.json\n\n`);
|
client.response.write(`data: content.json\n\n`);
|
||||||
});
|
});
|
||||||
@ -219,6 +228,55 @@ async function start(options) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} pathname
|
||||||
|
* @param {((value: any) => any) | null | undefined} onOk
|
||||||
|
* @param {((reason: any) => PromiseLike<never>) | null | undefined} onErr
|
||||||
|
*/
|
||||||
|
function runRenderThread(pathname, onOk, onErr) {
|
||||||
|
let cleanUpThread = () => {};
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const readyThread = await waitForThread();
|
||||||
|
console.log(`Rendering ${pathname}`, readyThread.worker.threadId);
|
||||||
|
cleanUpThread = () => {
|
||||||
|
cleanUp(readyThread);
|
||||||
|
};
|
||||||
|
|
||||||
|
readyThread.ready = false;
|
||||||
|
readyThread.worker.postMessage({
|
||||||
|
mode: "dev-server",
|
||||||
|
pathname,
|
||||||
|
});
|
||||||
|
readyThread.worker.on("message", (message) => {
|
||||||
|
if (message.tag === "done") {
|
||||||
|
resolve(message.data);
|
||||||
|
} else if (message.tag === "watch") {
|
||||||
|
// console.log("@@@ WATCH", message.data);
|
||||||
|
message.data.forEach((pattern) => watcher.add(pattern));
|
||||||
|
} else if (message.tag === "error") {
|
||||||
|
reject(message.data);
|
||||||
|
} else {
|
||||||
|
throw `Unhandled message: ${message}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
readyThread.worker.on("error", (error) => {
|
||||||
|
reject(error.context);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(onOk)
|
||||||
|
.catch(onErr)
|
||||||
|
.finally(() => {
|
||||||
|
cleanUpThread();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanUp(thread) {
|
||||||
|
thread.worker.removeAllListeners("message");
|
||||||
|
thread.worker.removeAllListeners("error");
|
||||||
|
thread.ready = true;
|
||||||
|
runPendingWork();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {http.IncomingMessage} req
|
* @param {http.IncomingMessage} req
|
||||||
* @param {http.ServerResponse} res
|
* @param {http.ServerResponse} res
|
||||||
@ -229,15 +287,9 @@ async function start(options) {
|
|||||||
const pathname = urlParts.pathname || "";
|
const pathname = urlParts.pathname || "";
|
||||||
try {
|
try {
|
||||||
await pendingCliCompile;
|
await pendingCliCompile;
|
||||||
const renderResult = await renderer(
|
await runRenderThread(
|
||||||
Elm,
|
|
||||||
pathname,
|
pathname,
|
||||||
req,
|
function (renderResult) {
|
||||||
function (pattern) {
|
|
||||||
console.log(`Watching data source ${pattern}`);
|
|
||||||
watcher.add(pattern);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
const is404 = renderResult.is404;
|
const is404 = renderResult.is404;
|
||||||
switch (renderResult.kind) {
|
switch (renderResult.kind) {
|
||||||
case "json": {
|
case "json": {
|
||||||
@ -257,7 +309,9 @@ async function start(options) {
|
|||||||
case "api-response": {
|
case "api-response": {
|
||||||
let mimeType = serveStatic.mime.lookup(pathname || "text/html");
|
let mimeType = serveStatic.mime.lookup(pathname || "text/html");
|
||||||
mimeType =
|
mimeType =
|
||||||
mimeType === "application/octet-stream" ? "text/html" : mimeType;
|
mimeType === "application/octet-stream"
|
||||||
|
? "text/html"
|
||||||
|
: mimeType;
|
||||||
res.writeHead(renderResult.statusCode, {
|
res.writeHead(renderResult.statusCode, {
|
||||||
"Content-Type": mimeType,
|
"Content-Type": mimeType,
|
||||||
});
|
});
|
||||||
@ -267,8 +321,10 @@ async function start(options) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.log(restoreColor(error));
|
|
||||||
|
function (error) {
|
||||||
|
console.log(restoreColor(error.errorsJson));
|
||||||
|
|
||||||
if (req.url.includes("content.json")) {
|
if (req.url.includes("content.json")) {
|
||||||
res.writeHead(500, { "Content-Type": "application/json" });
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
@ -278,6 +334,74 @@ async function start(options) {
|
|||||||
res.end(errorHtml());
|
res.end(errorHtml());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(restoreColor(error.errorsJson));
|
||||||
|
|
||||||
|
if (req.url.includes("content.json")) {
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
res.end(JSON.stringify(error.errorsJson));
|
||||||
|
} else {
|
||||||
|
res.writeHead(500, { "Content-Type": "text/html" });
|
||||||
|
res.end(errorHtml());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function awaitElmMiddleware(req, res, next) {
|
||||||
|
if (req.url && req.url.startsWith("/elm.js")) {
|
||||||
|
try {
|
||||||
|
await pendingCliCompile;
|
||||||
|
await clientElmMakeProcess;
|
||||||
|
next();
|
||||||
|
} catch (elmCompilerError) {
|
||||||
|
res.writeHead(500, { "Content-Type": "application/json" });
|
||||||
|
res.end(elmCompilerError);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<{ ready:boolean; worker: Worker }>}
|
||||||
|
* */
|
||||||
|
function waitForThread() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
threadReadyQueue.push(resolve);
|
||||||
|
runPendingWork();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function runPendingWork() {
|
||||||
|
const readyThreads = pool.filter((thread) => thread.ready);
|
||||||
|
readyThreads.forEach((readyThread) => {
|
||||||
|
const startTask = threadReadyQueue.shift();
|
||||||
|
if (startTask) {
|
||||||
|
// if we don't use setImmediate here, the remaining work will be done sequentially by a single worker
|
||||||
|
// using setImmediate delegates a ready thread to each pending task until it runs out of ready workers
|
||||||
|
// so the delegation is done sequentially, and the actual work is then executed
|
||||||
|
setImmediate(() => {
|
||||||
|
startTask(readyThread);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} basePath
|
||||||
|
*/
|
||||||
|
function initWorker(basePath) {
|
||||||
|
let newWorker = {
|
||||||
|
worker: new Worker(path.join(__dirname, "./render-worker.js"), {
|
||||||
|
env: SHARE_ENV,
|
||||||
|
workerData: { basePath },
|
||||||
|
}),
|
||||||
|
ready: false,
|
||||||
|
};
|
||||||
|
newWorker.worker.once("online", () => {
|
||||||
|
newWorker.ready = true;
|
||||||
|
});
|
||||||
|
return newWorker;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ const util = require("util");
|
|||||||
const fsSync = require("fs");
|
const fsSync = require("fs");
|
||||||
const fs = {
|
const fs = {
|
||||||
writeFile: util.promisify(fsSync.writeFile),
|
writeFile: util.promisify(fsSync.writeFile),
|
||||||
|
writeFileSync: fsSync.writeFileSync,
|
||||||
rm: util.promisify(fsSync.unlinkSync),
|
rm: util.promisify(fsSync.unlinkSync),
|
||||||
mkdir: util.promisify(fsSync.mkdir),
|
mkdir: util.promisify(fsSync.mkdir),
|
||||||
readFile: util.promisify(fsSync.readFile),
|
readFile: util.promisify(fsSync.readFile),
|
||||||
@ -21,6 +22,22 @@ async function tryMkdir(dirName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fileExists(file) {
|
||||||
|
return fsSync.promises
|
||||||
|
.access(file, fsSync.constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} data
|
||||||
|
*/
|
||||||
|
function writeFileSyncSafe(filePath, data) {
|
||||||
|
fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, data);
|
||||||
|
}
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,14 +78,17 @@ async function copyDirNested(src, dest) {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
writeFile: fs.writeFile,
|
writeFile: fs.writeFile,
|
||||||
|
writeFileSync: fs.writeFileSync,
|
||||||
readFile: fs.readFile,
|
readFile: fs.readFile,
|
||||||
readFileSync: fsSync.readFileSync,
|
readFileSync: fsSync.readFileSync,
|
||||||
copyFile: fs.copyFile,
|
copyFile: fs.copyFile,
|
||||||
exists: fs.exists,
|
exists: fs.exists,
|
||||||
|
writeFileSyncSafe,
|
||||||
tryMkdir,
|
tryMkdir,
|
||||||
copyDirFlat,
|
copyDirFlat,
|
||||||
copyDirNested,
|
copyDirNested,
|
||||||
rmSync: fs.rm,
|
rmSync: fs.rm,
|
||||||
rm: fs.rm,
|
rm: fs.rm,
|
||||||
existsSync: fs.existsSync,
|
existsSync: fs.existsSync,
|
||||||
|
fileExists: fileExists,
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,9 @@ function parseMsg(msg) {
|
|||||||
if (typeof msg === "string") {
|
if (typeof msg === "string") {
|
||||||
return msg;
|
return msg;
|
||||||
} else {
|
} else {
|
||||||
if (msg.underline) {
|
if (msg.underline && msg.color) {
|
||||||
|
return kleur[msg.color.toLowerCase()]().underline(msg.string);
|
||||||
|
} else if (msg.underline) {
|
||||||
return kleur.underline(msg.string);
|
return kleur.underline(msg.string);
|
||||||
} else if (msg.color) {
|
} else if (msg.color) {
|
||||||
return kleur[msg.color.toLowerCase()](msg.string);
|
return kleur[msg.color.toLowerCase()](msg.string);
|
||||||
@ -39,11 +41,10 @@ function parseMsg(msg) {
|
|||||||
*
|
*
|
||||||
* This function takes in the array of compiler errors and maps over them to generate a formatted compiler error
|
* This function takes in the array of compiler errors and maps over them to generate a formatted compiler error
|
||||||
**/
|
**/
|
||||||
const restoreColor = (error) => {
|
const restoreColor = (errors) => {
|
||||||
console.log(error);
|
|
||||||
try {
|
try {
|
||||||
return JSON.parse(error)
|
return errors
|
||||||
.errors.map(({ problems, path }) =>
|
.map(({ problems, path }) =>
|
||||||
problems.map(restoreProblem(path)).join("\n\n\n")
|
problems.map(restoreProblem(path)).join("\n\n\n")
|
||||||
)
|
)
|
||||||
.join("\n\n\n\n\n");
|
.join("\n\n\n\n\n");
|
||||||
@ -57,9 +58,7 @@ const restoreColor = (error) => {
|
|||||||
*
|
*
|
||||||
* This function takes in the array of compiler errors and maps over them to generate a formatted compiler error
|
* This function takes in the array of compiler errors and maps over them to generate a formatted compiler error
|
||||||
**/
|
**/
|
||||||
const restoreProblem =
|
const restoreProblem = (path) => ({ title, message }) =>
|
||||||
(path) =>
|
|
||||||
({ title, message }) =>
|
|
||||||
[parseHeader(title, path), ...message.map(parseMsg)].join("");
|
[parseHeader(title, path), ...message.map(parseMsg)].join("");
|
||||||
|
|
||||||
module.exports = { restoreColor };
|
module.exports = { restoreColor };
|
||||||
|
@ -4,9 +4,10 @@ const mm = require("micromatch");
|
|||||||
const routeHelpers = require("./route-codegen-helpers");
|
const routeHelpers = require("./route-codegen-helpers");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {string} basePath
|
||||||
* @param {'browser' | 'cli'} phase
|
* @param {'browser' | 'cli'} phase
|
||||||
*/
|
*/
|
||||||
function generateTemplateModuleConnector(phase) {
|
function generateTemplateModuleConnector(basePath, phase) {
|
||||||
const templates = globby.sync(["src/Page/**/*.elm"], {}).map((file) => {
|
const templates = globby.sync(["src/Page/**/*.elm"], {}).map((file) => {
|
||||||
const captures = mm.capture("src/Page/**/*.elm", file);
|
const captures = mm.capture("src/Page/**/*.elm", file);
|
||||||
if (captures) {
|
if (captures) {
|
||||||
@ -128,7 +129,7 @@ type PageData
|
|||||||
|
|
||||||
view :
|
view :
|
||||||
{ path : Path
|
{ path : Path
|
||||||
, frontmatter : Maybe Route
|
, route : Maybe Route
|
||||||
}
|
}
|
||||||
-> Maybe PageUrl
|
-> Maybe PageUrl
|
||||||
-> Shared.Data
|
-> Shared.Data
|
||||||
@ -138,7 +139,7 @@ view :
|
|||||||
, head : List Head.Tag
|
, head : List Head.Tag
|
||||||
}
|
}
|
||||||
view page maybePageUrl globalData pageData =
|
view page maybePageUrl globalData pageData =
|
||||||
case ( page.frontmatter, pageData ) of
|
case ( page.route, pageData ) of
|
||||||
${templates
|
${templates
|
||||||
.map(
|
.map(
|
||||||
(name) =>
|
(name) =>
|
||||||
@ -350,7 +351,7 @@ update sharedData pageData navigationKey msg model =
|
|||||||
|> (\\( a, b, c ) ->
|
|> (\\( a, b, c ) ->
|
||||||
case c of
|
case c of
|
||||||
Just sharedMsg ->
|
Just sharedMsg ->
|
||||||
( a, b, Shared.template.update (Shared.template.sharedMsg sharedMsg) model.global )
|
( a, b, Shared.template.update sharedMsg model.global )
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
( a, b, ( model.global, Cmd.none ) )
|
( a, b, ( model.global, Cmd.none ) )
|
||||||
@ -430,8 +431,13 @@ main =
|
|||||||
, fromJsPort = fromJsPort identity
|
, fromJsPort = fromJsPort identity
|
||||||
, data = dataForRoute
|
, data = dataForRoute
|
||||||
, sharedData = Shared.template.data
|
, sharedData = Shared.template.data
|
||||||
, apiRoutes = \\htmlToString -> routePatterns :: manifestHandler :: Api.routes getStaticRoutes htmlToString
|
, apiRoutes = \\htmlToString -> pathsToGenerateHandler :: routePatterns :: manifestHandler :: Api.routes getStaticRoutes htmlToString
|
||||||
, pathPatterns = routePatterns3
|
, pathPatterns = routePatterns3
|
||||||
|
, basePath = [ ${basePath
|
||||||
|
.split("/")
|
||||||
|
.filter((segment) => segment !== "")
|
||||||
|
.map((segment) => `"${segment}"`)
|
||||||
|
.join(", ")} ]
|
||||||
}
|
}
|
||||||
|
|
||||||
dataForRoute : Maybe Route -> DataSource PageData
|
dataForRoute : Maybe Route -> DataSource PageData
|
||||||
@ -593,6 +599,37 @@ getStaticRoutes =
|
|||||||
|> DataSource.map List.concat
|
|> DataSource.map List.concat
|
||||||
|
|
||||||
|
|
||||||
|
pathsToGenerateHandler : ApiRoute.Done ApiRoute.Response
|
||||||
|
pathsToGenerateHandler =
|
||||||
|
ApiRoute.succeed
|
||||||
|
(DataSource.map2
|
||||||
|
(\\pageRoutes apiRoutes ->
|
||||||
|
{ body =
|
||||||
|
(pageRoutes ++ (apiRoutes |> List.map (\\api -> "/" ++ api)))
|
||||||
|
|> Json.Encode.list Json.Encode.string
|
||||||
|
|> Json.Encode.encode 0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(DataSource.map
|
||||||
|
(List.map
|
||||||
|
(\\route ->
|
||||||
|
route
|
||||||
|
|> Route.toPath
|
||||||
|
|> Path.toAbsolute
|
||||||
|
)
|
||||||
|
)
|
||||||
|
getStaticRoutes
|
||||||
|
)
|
||||||
|
((manifestHandler :: Api.routes getStaticRoutes (\\_ -> ""))
|
||||||
|
|> List.map ApiRoute.getBuildTimeRoutes
|
||||||
|
|> DataSource.combine
|
||||||
|
|> DataSource.map List.concat
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> ApiRoute.literal "all-paths.json"
|
||||||
|
|> ApiRoute.single
|
||||||
|
|
||||||
|
|
||||||
manifestHandler : ApiRoute.Done ApiRoute.Response
|
manifestHandler : ApiRoute.Done ApiRoute.Response
|
||||||
manifestHandler =
|
manifestHandler =
|
||||||
ApiRoute.succeed
|
ApiRoute.succeed
|
||||||
@ -655,9 +692,29 @@ type Route
|
|||||||
{-| -}
|
{-| -}
|
||||||
urlToRoute : { url | path : String } -> Maybe Route
|
urlToRoute : { url | path : String } -> Maybe Route
|
||||||
urlToRoute url =
|
urlToRoute url =
|
||||||
Router.firstMatch matchers url.path
|
url.path
|
||||||
|
|> withoutBaseUrl
|
||||||
|
|> Router.firstMatch matchers
|
||||||
|
|
||||||
|
|
||||||
|
baseUrl : String
|
||||||
|
baseUrl =
|
||||||
|
"${basePath}"
|
||||||
|
|
||||||
|
|
||||||
|
baseUrlAsPath : List String
|
||||||
|
baseUrlAsPath =
|
||||||
|
baseUrl
|
||||||
|
|> String.split "/"
|
||||||
|
|> List.filter (not << String.isEmpty)
|
||||||
|
|
||||||
|
|
||||||
|
withoutBaseUrl path =
|
||||||
|
if (path |> String.startsWith baseUrl) then
|
||||||
|
String.dropLeft (String.length baseUrl) path
|
||||||
|
else
|
||||||
|
path
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
matchers : List (Router.Matcher Route)
|
matchers : List (Router.Matcher Route)
|
||||||
matchers =
|
matchers =
|
||||||
@ -711,13 +768,20 @@ routeToPath route =
|
|||||||
{-| -}
|
{-| -}
|
||||||
toPath : Route -> Path
|
toPath : Route -> Path
|
||||||
toPath route =
|
toPath route =
|
||||||
route |> routeToPath |> String.join "/" |> Path.fromString
|
(baseUrlAsPath ++ (route |> routeToPath)) |> String.join "/" |> Path.fromString
|
||||||
|
|
||||||
|
|
||||||
|
{-| -}
|
||||||
|
toString : Route -> String
|
||||||
|
toString route =
|
||||||
|
route |> toPath |> Path.toAbsolute
|
||||||
|
|
||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
toLink : (List (Attribute msg) -> tag) -> Route -> tag
|
toLink : (List (Attribute msg) -> tag) -> Route -> tag
|
||||||
toLink toAnchorTag route =
|
toLink toAnchorTag route =
|
||||||
toAnchorTag
|
toAnchorTag
|
||||||
[ Attr.href ("/" ++ (routeToPath route |> String.join "/"))
|
[ route |> toString |> Attr.href
|
||||||
, Attr.attribute "elm-pages:prefetch" ""
|
, Attr.attribute "elm-pages:prefetch" ""
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1,18 +1,20 @@
|
|||||||
const cliVersion = require("../../package.json").version;
|
const cliVersion = require("../../package.json").version;
|
||||||
const seo = require("./seo-renderer.js");
|
const seo = require("./seo-renderer.js");
|
||||||
const elmPagesJsMinified = require("./elm-pages-js-minified.js");
|
const elmPagesJsMinified = require("./elm-pages-js-minified.js");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
/** @typedef { { head: any[]; errors: any[]; contentJson: any[]; html: string; route: string; title: string; } } Arg */
|
/** @typedef { { head: any[]; errors: any[]; contentJson: any[]; html: string; route: string; title: string; } } Arg */
|
||||||
/** @typedef { { tag : 'PageProgress'; args : Arg[] } } PageProgress */
|
/** @typedef { { tag : 'PageProgress'; args : Arg[] } } PageProgress */
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
/**
|
/**
|
||||||
|
* @param {string} basePath
|
||||||
* @param {Arg} fromElm
|
* @param {Arg} fromElm
|
||||||
* @param {string} contentJsonString
|
* @param {string} contentJsonString
|
||||||
* @param {boolean} devServer
|
* @param {boolean} devServer
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function wrapHtml(fromElm, contentJsonString, devServer) {
|
function wrapHtml(basePath, fromElm, contentJsonString, devServer) {
|
||||||
const devServerOnly = (/** @type {string} */ devServerOnlyString) =>
|
const devServerOnly = (/** @type {string} */ devServerOnlyString) =>
|
||||||
devServer ? devServerOnlyString : "";
|
devServer ? devServerOnlyString : "";
|
||||||
const seoData = seo.gather(fromElm.head);
|
const seoData = seo.gather(fromElm.head);
|
||||||
@ -20,24 +22,29 @@ module.exports =
|
|||||||
return `<!DOCTYPE html>
|
return `<!DOCTYPE html>
|
||||||
${seoData.rootElement}
|
${seoData.rootElement}
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="/style.css">
|
<link rel="stylesheet" href="${path.join(basePath, "style.css")}">
|
||||||
${devServerOnly(devServerStyleTag())}
|
${devServerOnly(devServerStyleTag())}
|
||||||
<link rel="preload" href="/elm.js" as="script">
|
<link rel="preload" href="${path.join(basePath, "elm.js")}" as="script">
|
||||||
<link rel="modulepreload" href="/index.js">
|
<link rel="modulepreload" href="${path.join(basePath, "index.js")}">
|
||||||
${devServerOnly(
|
${devServerOnly(
|
||||||
/* html */ `<script defer="defer" src="/hmr.js" type="text/javascript"></script>`
|
/* html */ `<script defer="defer" src="${path.join(
|
||||||
|
basePath,
|
||||||
|
"hmr.js"
|
||||||
|
)}" type="text/javascript"></script>`
|
||||||
)}
|
)}
|
||||||
<script defer="defer" src="/elm.js" type="text/javascript"></script>
|
<script defer="defer" src="${path.join(
|
||||||
<base href="${baseRoute(fromElm.route)}">
|
basePath,
|
||||||
|
"elm.js"
|
||||||
|
)}" type="text/javascript"></script>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import userInit from"/index.js";
|
import userInit from"${path.join(basePath, "index.js")}";
|
||||||
${elmPagesJsMinified}
|
${elmPagesJsMinified}
|
||||||
</script>
|
</script>
|
||||||
<title>${fromElm.title}</title>
|
<title>${fromElm.title}</title>
|
||||||
<meta name="generator" content="elm-pages v${cliVersion}">
|
<meta name="generator" content="elm-pages v${cliVersion}">
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
@ -53,14 +60,6 @@ ${elmPagesJsMinified}
|
|||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} route
|
|
||||||
*/
|
|
||||||
function baseRoute(route) {
|
|
||||||
const cleanedRoute = cleanRoute(route);
|
|
||||||
return cleanedRoute === "" ? "./" : pathToRoot(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} route
|
* @param {string} route
|
||||||
*/
|
*/
|
||||||
|
97
generator/src/render-worker.js
Normal file
97
generator/src/render-worker.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
const renderer = require("../../generator/src/render");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("./dir-helpers.js");
|
||||||
|
const compiledElmPath = path.join(process.cwd(), "elm-stuff/elm-pages/elm.js");
|
||||||
|
const { parentPort, threadId, workerData } = require("worker_threads");
|
||||||
|
let Elm;
|
||||||
|
|
||||||
|
global.staticHttpCache = {};
|
||||||
|
|
||||||
|
async function run({ mode, pathname }) {
|
||||||
|
console.time(`${threadId} ${pathname}`);
|
||||||
|
try {
|
||||||
|
const req = null;
|
||||||
|
const renderResult = await renderer(
|
||||||
|
workerData.basePath,
|
||||||
|
requireElm(mode),
|
||||||
|
mode,
|
||||||
|
pathname,
|
||||||
|
req,
|
||||||
|
function (patterns) {
|
||||||
|
if (mode === "dev-server" && patterns.size > 0) {
|
||||||
|
parentPort.postMessage({ tag: "watch", data: [...patterns] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mode === "dev-server") {
|
||||||
|
parentPort.postMessage({ tag: "done", data: renderResult });
|
||||||
|
} else if (mode === "build") {
|
||||||
|
outputString(renderResult, pathname);
|
||||||
|
} else {
|
||||||
|
throw `Unknown mode ${mode}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
parentPort.postMessage({ tag: "error", data: error });
|
||||||
|
}
|
||||||
|
console.timeEnd(`${threadId} ${pathname}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireElm(mode) {
|
||||||
|
if (mode === "build") {
|
||||||
|
if (!Elm) {
|
||||||
|
const warnOriginal = console.warn;
|
||||||
|
console.warn = function () {};
|
||||||
|
|
||||||
|
Elm = require(compiledElmPath);
|
||||||
|
console.warn = warnOriginal;
|
||||||
|
}
|
||||||
|
return Elm;
|
||||||
|
} else {
|
||||||
|
delete require.cache[require.resolve(compiledElmPath)];
|
||||||
|
return require(compiledElmPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function outputString(
|
||||||
|
/** @type { { kind: 'page'; data: PageProgress } | { kind: 'api'; data: Object } } */ fromElm,
|
||||||
|
/** @type string */ pathname
|
||||||
|
) {
|
||||||
|
switch (fromElm.kind) {
|
||||||
|
case "html": {
|
||||||
|
const args = fromElm;
|
||||||
|
console.log(`Pre-rendered /${args.route}`);
|
||||||
|
const normalizedRoute = args.route.replace(/index$/, "");
|
||||||
|
await fs.tryMkdir(`./dist/${normalizedRoute}`);
|
||||||
|
const contentJsonString = JSON.stringify({
|
||||||
|
is404: args.is404,
|
||||||
|
staticData: args.contentJson,
|
||||||
|
path: args.route,
|
||||||
|
});
|
||||||
|
fs.writeFileSync(`dist/${normalizedRoute}/index.html`, args.htmlString);
|
||||||
|
fs.writeFileSync(
|
||||||
|
`dist/${normalizedRoute}/content.json`,
|
||||||
|
contentJsonString
|
||||||
|
);
|
||||||
|
// parentPort.postMessage({ tag: "done" });
|
||||||
|
parentPort.postMessage({ tag: "done" });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "api-response": {
|
||||||
|
const body = fromElm.body;
|
||||||
|
console.log(`Generated ${pathname}`);
|
||||||
|
fs.writeFileSyncSafe(path.join("dist", pathname), body);
|
||||||
|
if (pathname === "/all-paths.json") {
|
||||||
|
parentPort.postMessage({ tag: "all-paths", data: body });
|
||||||
|
} else {
|
||||||
|
parentPort.postMessage({ tag: "done" });
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentPort.on("message", run);
|
||||||
|
|
||||||
|
/** @typedef { { tag : 'PageProgress'; args : Arg[] } } PageProgress */
|
@ -5,25 +5,45 @@ const matter = require("gray-matter");
|
|||||||
const globby = require("globby");
|
const globby = require("globby");
|
||||||
const fsPromises = require("fs").promises;
|
const fsPromises = require("fs").promises;
|
||||||
const preRenderHtml = require("./pre-render-html.js");
|
const preRenderHtml = require("./pre-render-html.js");
|
||||||
|
const { lookupOrPerform } = require("./request-cache.js");
|
||||||
|
const kleur = require("kleur");
|
||||||
|
kleur.enabled = true;
|
||||||
|
|
||||||
let foundErrors = false;
|
|
||||||
process.on("unhandledRejection", (error) => {
|
process.on("unhandledRejection", (error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
|
let foundErrors;
|
||||||
|
let pendingDataSourceResponses;
|
||||||
|
let pendingDataSourceCount;
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
* @param {string} basePath
|
||||||
* @param {Object} elmModule
|
* @param {Object} elmModule
|
||||||
* @param {string} path
|
* @param {string} path
|
||||||
* @param {import('aws-lambda').APIGatewayProxyEvent} request
|
* @param {import('aws-lambda').APIGatewayProxyEvent} request
|
||||||
* @param {(pattern: string) => void} addDataSourceWatcher
|
* @param {(pattern: string) => void} addDataSourceWatcher
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async function run(elmModule, path, request, addDataSourceWatcher) {
|
async function run(
|
||||||
XMLHttpRequest = require("xhr2");
|
basePath,
|
||||||
const result = await runElmApp(
|
|
||||||
elmModule,
|
elmModule,
|
||||||
|
mode,
|
||||||
|
path,
|
||||||
|
request,
|
||||||
|
addDataSourceWatcher
|
||||||
|
) {
|
||||||
|
foundErrors = false;
|
||||||
|
pendingDataSourceResponses = [];
|
||||||
|
pendingDataSourceCount = 0;
|
||||||
|
// since init/update are never called in pre-renders, and DataSource.Http is called using undici
|
||||||
|
// we can provide a fake HTTP instead of xhr2 (which is otherwise needed for Elm HTTP requests from Node)
|
||||||
|
XMLHttpRequest = {};
|
||||||
|
const result = await runElmApp(
|
||||||
|
basePath,
|
||||||
|
elmModule,
|
||||||
|
mode,
|
||||||
path,
|
path,
|
||||||
request,
|
request,
|
||||||
addDataSourceWatcher
|
addDataSourceWatcher
|
||||||
@ -32,27 +52,36 @@ module.exports =
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {string} basePath
|
||||||
* @param {Object} elmModule
|
* @param {Object} elmModule
|
||||||
* @param {string} pagePath
|
* @param {string} pagePath
|
||||||
|
* @param {string} mode
|
||||||
* @param {import('aws-lambda').APIGatewayProxyEvent} request
|
* @param {import('aws-lambda').APIGatewayProxyEvent} request
|
||||||
* @param {(pattern: string) => void} addDataSourceWatcher
|
* @param {(pattern: string) => void} addDataSourceWatcher
|
||||||
* @returns {Promise<({is404: boolean} & ( { kind: 'json'; contentJson: string} | { kind: 'html'; htmlString: string } | { kind: 'api-response'; body: string; }) )>}
|
* @returns {Promise<({is404: boolean} & ( { kind: 'json'; contentJson: string} | { kind: 'html'; htmlString: string } | { kind: 'api-response'; body: string; }) )>}
|
||||||
*/
|
*/
|
||||||
function runElmApp(elmModule, pagePath, request, addDataSourceWatcher) {
|
function runElmApp(
|
||||||
|
basePath,
|
||||||
|
elmModule,
|
||||||
|
mode,
|
||||||
|
pagePath,
|
||||||
|
request,
|
||||||
|
addDataSourceWatcher
|
||||||
|
) {
|
||||||
|
const isDevServer = mode !== "build";
|
||||||
|
let patternsToWatch = new Set();
|
||||||
let app = null;
|
let app = null;
|
||||||
let killApp;
|
let killApp;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const isJson = pagePath.match(/content\.json\/?$/);
|
const isJson = pagePath.match(/content\.json\/?$/);
|
||||||
const route = pagePath.replace(/content\.json\/?$/, "");
|
const route = pagePath.replace(/content\.json\/?$/, "");
|
||||||
|
|
||||||
const mode = "elm-to-html-beta";
|
|
||||||
const modifiedRequest = { ...request, path: route };
|
const modifiedRequest = { ...request, path: route };
|
||||||
// console.log("StaticHttp cache keys", Object.keys(global.staticHttpCache));
|
// console.log("StaticHttp cache keys", Object.keys(global.staticHttpCache));
|
||||||
app = elmModule.Elm.TemplateModulesBeta.init({
|
app = elmModule.Elm.TemplateModulesBeta.init({
|
||||||
flags: {
|
flags: {
|
||||||
secrets: process.env,
|
secrets: process.env,
|
||||||
mode,
|
staticHttpCache: global.staticHttpCache || {},
|
||||||
staticHttpCache: global.staticHttpCache,
|
|
||||||
request: {
|
request: {
|
||||||
payload: modifiedRequest,
|
payload: modifiedRequest,
|
||||||
kind: "single-page",
|
kind: "single-page",
|
||||||
@ -62,7 +91,7 @@ function runElmApp(elmModule, pagePath, request, addDataSourceWatcher) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
killApp = () => {
|
killApp = () => {
|
||||||
// app.ports.toJsPort.unsubscribe(portHandler);
|
app.ports.toJsPort.unsubscribe(portHandler);
|
||||||
app.die();
|
app.die();
|
||||||
app = null;
|
app = null;
|
||||||
// delete require.cache[require.resolve(compiledElmPath)];
|
// delete require.cache[require.resolve(compiledElmPath)];
|
||||||
@ -71,13 +100,11 @@ function runElmApp(elmModule, pagePath, request, addDataSourceWatcher) {
|
|||||||
async function portHandler(/** @type { FromElm } */ fromElm) {
|
async function portHandler(/** @type { FromElm } */ fromElm) {
|
||||||
if (fromElm.command === "log") {
|
if (fromElm.command === "log") {
|
||||||
console.log(fromElm.value);
|
console.log(fromElm.value);
|
||||||
} else if (fromElm.tag === "InitialData") {
|
|
||||||
const args = fromElm.args[0];
|
|
||||||
// console.log(`InitialData`, args);
|
|
||||||
writeGeneratedFiles(args.filesToGenerate);
|
|
||||||
} else if (fromElm.tag === "ApiResponse") {
|
} else if (fromElm.tag === "ApiResponse") {
|
||||||
const args = fromElm.args[0];
|
const args = fromElm.args[0];
|
||||||
|
if (mode === "build") {
|
||||||
global.staticHttpCache = args.staticHttpCache;
|
global.staticHttpCache = args.staticHttpCache;
|
||||||
|
}
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
kind: "api-response",
|
kind: "api-response",
|
||||||
@ -87,9 +114,10 @@ function runElmApp(elmModule, pagePath, request, addDataSourceWatcher) {
|
|||||||
});
|
});
|
||||||
} else if (fromElm.tag === "PageProgress") {
|
} else if (fromElm.tag === "PageProgress") {
|
||||||
const args = fromElm.args[0];
|
const args = fromElm.args[0];
|
||||||
|
if (mode === "build") {
|
||||||
global.staticHttpCache = args.staticHttpCache;
|
global.staticHttpCache = args.staticHttpCache;
|
||||||
|
}
|
||||||
|
|
||||||
// delete require.cache[require.resolve(compiledElmPath)];
|
|
||||||
if (isJson) {
|
if (isJson) {
|
||||||
resolve({
|
resolve({
|
||||||
kind: "json",
|
kind: "json",
|
||||||
@ -100,50 +128,29 @@ function runElmApp(elmModule, pagePath, request, addDataSourceWatcher) {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
resolve(outputString(fromElm));
|
resolve(outputString(basePath, fromElm, isDevServer));
|
||||||
}
|
}
|
||||||
} else if (fromElm.tag === "ReadFile") {
|
} else if (fromElm.tag === "ReadFile") {
|
||||||
const filePath = fromElm.args[0];
|
const filePath = fromElm.args[0];
|
||||||
try {
|
try {
|
||||||
addDataSourceWatcher(filePath);
|
patternsToWatch.add(filePath);
|
||||||
|
|
||||||
const fileContents = (
|
runJob(app, filePath);
|
||||||
await fsPromises.readFile(path.join(process.cwd(), filePath))
|
|
||||||
).toString();
|
|
||||||
const parsedFile = matter(fileContents);
|
|
||||||
app.ports.fromJsPort.send({
|
|
||||||
tag: "GotFile",
|
|
||||||
data: {
|
|
||||||
filePath,
|
|
||||||
parsedFrontmatter: parsedFile.data,
|
|
||||||
withoutFrontmatter: parsedFile.content,
|
|
||||||
rawFile: fileContents,
|
|
||||||
jsonFile: jsonOrNull(fileContents),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
app.ports.fromJsPort.send({
|
sendError(app, {
|
||||||
tag: "BuildError",
|
title: "DataSource.File Error",
|
||||||
data: { filePath },
|
message: `A DataSource.File read failed because I couldn't find this file: ${kleur.yellow(
|
||||||
|
filePath
|
||||||
|
)}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (fromElm.tag === "DoHttp") {
|
||||||
|
const requestToPerform = fromElm.args[0];
|
||||||
|
runHttpJob(app, mode, requestToPerform);
|
||||||
} else if (fromElm.tag === "Glob") {
|
} else if (fromElm.tag === "Glob") {
|
||||||
const globPattern = fromElm.args[0];
|
const globPattern = fromElm.args[0];
|
||||||
addDataSourceWatcher(globPattern);
|
patternsToWatch.add(globPattern);
|
||||||
const matchedPaths = await globby(globPattern);
|
runGlobJob(app, globPattern);
|
||||||
|
|
||||||
app.ports.fromJsPort.send({
|
|
||||||
tag: "GotGlob",
|
|
||||||
data: { pattern: globPattern, result: matchedPaths },
|
|
||||||
});
|
|
||||||
} else if (fromElm.tag === "Port") {
|
|
||||||
const portName = fromElm.args[0];
|
|
||||||
console.log({ portName });
|
|
||||||
|
|
||||||
app.ports.fromJsPort.send({
|
|
||||||
tag: "GotPort",
|
|
||||||
data: { portName, portResponse: "Hello from ports!" },
|
|
||||||
});
|
|
||||||
} else if (fromElm.tag === "Errors") {
|
} else if (fromElm.tag === "Errors") {
|
||||||
foundErrors = true;
|
foundErrors = true;
|
||||||
reject(fromElm.args[0]);
|
reject(fromElm.args[0]);
|
||||||
@ -153,23 +160,34 @@ function runElmApp(elmModule, pagePath, request, addDataSourceWatcher) {
|
|||||||
}
|
}
|
||||||
app.ports.toJsPort.subscribe(portHandler);
|
app.ports.toJsPort.subscribe(portHandler);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
addDataSourceWatcher(patternsToWatch);
|
||||||
killApp();
|
killApp();
|
||||||
killApp = null;
|
killApp = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
async function outputString(/** @type { PageProgress } */ fromElm) {
|
* @param {string} basePath
|
||||||
|
* @param {PageProgress} fromElm
|
||||||
|
* @param {boolean} isDevServer
|
||||||
|
*/
|
||||||
|
async function outputString(
|
||||||
|
basePath,
|
||||||
|
/** @type { PageProgress } */ fromElm,
|
||||||
|
isDevServer
|
||||||
|
) {
|
||||||
const args = fromElm.args[0];
|
const args = fromElm.args[0];
|
||||||
let contentJson = {};
|
let contentJson = {};
|
||||||
contentJson["staticData"] = args.contentJson;
|
contentJson["staticData"] = args.contentJson;
|
||||||
contentJson["is404"] = args.is404;
|
contentJson["is404"] = args.is404;
|
||||||
|
contentJson["path"] = args.route;
|
||||||
const normalizedRoute = args.route.replace(/index$/, "");
|
const normalizedRoute = args.route.replace(/index$/, "");
|
||||||
const contentJsonString = JSON.stringify(contentJson);
|
const contentJsonString = JSON.stringify(contentJson);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
is404: args.is404,
|
is404: args.is404,
|
||||||
route: normalizedRoute,
|
route: normalizedRoute,
|
||||||
htmlString: preRenderHtml(args, contentJsonString, true),
|
htmlString: preRenderHtml(basePath, args, contentJsonString, isDevServer),
|
||||||
|
contentJson: args.contentJson,
|
||||||
kind: "html",
|
kind: "html",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -193,3 +211,206 @@ function jsonOrNull(string) {
|
|||||||
return { invalidJson: e.toString() };
|
return { invalidJson: e.toString() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runJob(app, filePath) {
|
||||||
|
pendingDataSourceCount += 1;
|
||||||
|
try {
|
||||||
|
const fileContents = (
|
||||||
|
await fsPromises.readFile(path.join(process.cwd(), filePath))
|
||||||
|
).toString();
|
||||||
|
const parsedFile = matter(fileContents);
|
||||||
|
|
||||||
|
pendingDataSourceResponses.push({
|
||||||
|
request: {
|
||||||
|
masked: {
|
||||||
|
url: `file://${filePath}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: [],
|
||||||
|
body: { tag: "EmptyBody", args: [] },
|
||||||
|
},
|
||||||
|
unmasked: {
|
||||||
|
url: `file://${filePath}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: [],
|
||||||
|
body: { tag: "EmptyBody", args: [] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
response: JSON.stringify({
|
||||||
|
parsedFrontmatter: parsedFile.data,
|
||||||
|
withoutFrontmatter: parsedFile.content,
|
||||||
|
rawFile: fileContents,
|
||||||
|
jsonFile: jsonOrNull(fileContents),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
sendError(app, {
|
||||||
|
title: "Error reading file",
|
||||||
|
message: `A DataSource.File read failed because I couldn't find this file: ${kleur.yellow(
|
||||||
|
filePath
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
pendingDataSourceCount -= 1;
|
||||||
|
flushIfDone(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runHttpJob(app, mode, requestToPerform) {
|
||||||
|
pendingDataSourceCount += 1;
|
||||||
|
try {
|
||||||
|
const responseFilePath = await lookupOrPerform(
|
||||||
|
mode,
|
||||||
|
requestToPerform.unmasked
|
||||||
|
);
|
||||||
|
|
||||||
|
pendingDataSourceResponses.push({
|
||||||
|
request: requestToPerform,
|
||||||
|
response: (
|
||||||
|
await fsPromises.readFile(responseFilePath, "utf8")
|
||||||
|
).toString(),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
sendError(app, error);
|
||||||
|
} finally {
|
||||||
|
pendingDataSourceCount -= 1;
|
||||||
|
flushIfDone(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runGlobJob(app, globPattern) {
|
||||||
|
try {
|
||||||
|
// if (pendingDataSourceCount > 0) {
|
||||||
|
// console.log(`Waiting for ${pendingDataSourceCount} pending data sources`);
|
||||||
|
// }
|
||||||
|
pendingDataSourceCount += 1;
|
||||||
|
|
||||||
|
pendingDataSourceResponses.push(await globTask(globPattern));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Error running glob pattern ${globPattern}`);
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
pendingDataSourceCount -= 1;
|
||||||
|
flushIfDone(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushIfDone(app) {
|
||||||
|
if (foundErrors) {
|
||||||
|
pendingDataSourceResponses = [];
|
||||||
|
} else if (pendingDataSourceCount === 0) {
|
||||||
|
// console.log(
|
||||||
|
// `Flushing ${pendingDataSourceResponses.length} items in ${timeUntilThreshold}ms`
|
||||||
|
// );
|
||||||
|
|
||||||
|
flushQueue(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushQueue(app) {
|
||||||
|
const temp = pendingDataSourceResponses;
|
||||||
|
pendingDataSourceResponses = [];
|
||||||
|
// console.log("@@@ FLUSHING", temp.length);
|
||||||
|
app.ports.fromJsPort.send({
|
||||||
|
tag: "GotBatch",
|
||||||
|
data: temp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
*/
|
||||||
|
async function readFileTask(app, filePath) {
|
||||||
|
// console.log(`Read file ${filePath}`);
|
||||||
|
try {
|
||||||
|
const fileContents = (
|
||||||
|
await fsPromises.readFile(path.join(process.cwd(), filePath))
|
||||||
|
).toString();
|
||||||
|
// console.log(`DONE reading file ${filePath}`);
|
||||||
|
const parsedFile = matter(fileContents);
|
||||||
|
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
masked: {
|
||||||
|
url: `file://${filePath}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: [],
|
||||||
|
body: { tag: "EmptyBody", args: [] },
|
||||||
|
},
|
||||||
|
unmasked: {
|
||||||
|
url: `file://${filePath}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: [],
|
||||||
|
body: { tag: "EmptyBody", args: [] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
response: JSON.stringify({
|
||||||
|
parsedFrontmatter: parsedFile.data,
|
||||||
|
withoutFrontmatter: parsedFile.content,
|
||||||
|
rawFile: fileContents,
|
||||||
|
jsonFile: jsonOrNull(fileContents),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
sendError(app, {
|
||||||
|
title: "Error reading file",
|
||||||
|
message: `A DataSource.File read failed because I couldn't find this file: ${kleur.yellow(
|
||||||
|
filePath
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} globPattern
|
||||||
|
* @returns {Promise<Object>}
|
||||||
|
*/
|
||||||
|
async function globTask(globPattern) {
|
||||||
|
try {
|
||||||
|
const matchedPaths = await globby(globPattern);
|
||||||
|
// console.log("Got glob path", matchedPaths);
|
||||||
|
|
||||||
|
return {
|
||||||
|
request: {
|
||||||
|
masked: {
|
||||||
|
url: `glob://${globPattern}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: [],
|
||||||
|
body: { tag: "EmptyBody", args: [] },
|
||||||
|
},
|
||||||
|
unmasked: {
|
||||||
|
url: `glob://${globPattern}`,
|
||||||
|
method: "GET",
|
||||||
|
headers: [],
|
||||||
|
body: { tag: "EmptyBody", args: [] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
response: JSON.stringify(matchedPaths),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Error performing glob '${globPattern}'`);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function requireUncached(mode, filePath) {
|
||||||
|
if (mode === "dev-server") {
|
||||||
|
// for the build command, we can skip clearing the cache because it won't change while the build is running
|
||||||
|
// in the dev server, we want to clear the cache to get a the latest code each time it runs
|
||||||
|
delete require.cache[require.resolve(filePath)];
|
||||||
|
}
|
||||||
|
return require(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ ports: { fromJsPort: { send: (arg0: { tag: string; data: any; }) => void; }; }; }} app
|
||||||
|
* @param {{ message: string; title: string; }} error
|
||||||
|
*/
|
||||||
|
function sendError(app, error) {
|
||||||
|
foundErrors = true;
|
||||||
|
|
||||||
|
app.ports.fromJsPort.send({
|
||||||
|
tag: "BuildError",
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
180
generator/src/request-cache.js
Normal file
180
generator/src/request-cache.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const undici = require("undici");
|
||||||
|
const fs = require("fs");
|
||||||
|
const objectHash = require("object-hash");
|
||||||
|
const kleur = require("kleur");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To cache HTTP requests on disk with quick lookup and insertion, we store the hashed request.
|
||||||
|
* This uses SHA1 hashes. They are uni-directional hashes, which works for this use case. Most importantly,
|
||||||
|
* they're unique enough and can be expressed in a case-insensitive way so it works on Windows filesystems.
|
||||||
|
* And they are 40 hex characters, so the length won't be too long no matter what the request payload.
|
||||||
|
* @param {Object} request
|
||||||
|
*/
|
||||||
|
function requestToString(request) {
|
||||||
|
return objectHash(request);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Object} request
|
||||||
|
*/
|
||||||
|
function fullPath(request) {
|
||||||
|
return path.join(
|
||||||
|
process.cwd(),
|
||||||
|
".elm-pages",
|
||||||
|
"http-response-cache",
|
||||||
|
requestToString(request)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} mode
|
||||||
|
* @param {{url: string; headers: {[x: string]: string}; method: string; body: Body } } rawRequest
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
function lookupOrPerform(mode, rawRequest) {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
const request = toRequest(rawRequest);
|
||||||
|
const responsePath = fullPath(request);
|
||||||
|
|
||||||
|
if (fs.existsSync(responsePath)) {
|
||||||
|
// console.log("Skipping request, found file.");
|
||||||
|
resolve(responsePath);
|
||||||
|
} else {
|
||||||
|
let portDataSource = {};
|
||||||
|
let portDataSourceFound = false;
|
||||||
|
try {
|
||||||
|
portDataSource = requireUncached(
|
||||||
|
mode,
|
||||||
|
path.join(process.cwd(), "port-data-source.js")
|
||||||
|
);
|
||||||
|
portDataSourceFound = true;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (request.url.startsWith("port://")) {
|
||||||
|
try {
|
||||||
|
const portName = request.url.replace(/^port:\/\//, "");
|
||||||
|
// console.time(JSON.stringify(request.url));
|
||||||
|
if (!portDataSource[portName]) {
|
||||||
|
if (portDataSourceFound) {
|
||||||
|
throw `DataSource.Port.send "${portName}" is not defined. Be sure to export a function with that name from port-data-source.js`;
|
||||||
|
} else {
|
||||||
|
throw `DataSource.Port.send "${portName}" was called, but I couldn't find the port definitions file 'port-data-source.js'.`;
|
||||||
|
}
|
||||||
|
} else if (typeof portDataSource[portName] !== "function") {
|
||||||
|
throw `DataSource.Port.send "${portName}" is not a function. Be sure to export a function with that name from port-data-source.js`;
|
||||||
|
}
|
||||||
|
await fs.promises.writeFile(
|
||||||
|
responsePath,
|
||||||
|
JSON.stringify(
|
||||||
|
await portDataSource[portName](rawRequest.body.args[0])
|
||||||
|
)
|
||||||
|
);
|
||||||
|
resolve(responsePath);
|
||||||
|
|
||||||
|
// console.timeEnd(JSON.stringify(requestToPerform.masked));
|
||||||
|
} catch (error) {
|
||||||
|
console.trace(error);
|
||||||
|
reject({
|
||||||
|
title: "DataSource.Port Error",
|
||||||
|
message: error.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
undici
|
||||||
|
.stream(
|
||||||
|
request.url,
|
||||||
|
{
|
||||||
|
method: request.method,
|
||||||
|
body: request.body,
|
||||||
|
headers: {
|
||||||
|
"User-Agent": "request",
|
||||||
|
...request.headers,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(response) => {
|
||||||
|
const writeStream = fs.createWriteStream(responsePath);
|
||||||
|
writeStream.on("finish", async () => {
|
||||||
|
resolve(responsePath);
|
||||||
|
});
|
||||||
|
|
||||||
|
return writeStream;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((error) => {
|
||||||
|
let errorMessage = error.toString();
|
||||||
|
if (error.code === "ENOTFOUND") {
|
||||||
|
errorMessage = `Could not reach URL.`;
|
||||||
|
}
|
||||||
|
reject({
|
||||||
|
title: "DataSource.Http Error",
|
||||||
|
message: `${kleur
|
||||||
|
.yellow()
|
||||||
|
.underline(request.url)} ${errorMessage}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{url: string; headers: {[x: string]: string}; method: string; body: Body } } elmRequest
|
||||||
|
*/
|
||||||
|
function toRequest(elmRequest) {
|
||||||
|
const elmHeaders = Object.fromEntries(elmRequest.headers);
|
||||||
|
let contentType = toContentType(elmRequest.body);
|
||||||
|
let headers = { ...contentType, ...elmHeaders };
|
||||||
|
return {
|
||||||
|
url: elmRequest.url,
|
||||||
|
method: elmRequest.method,
|
||||||
|
headers,
|
||||||
|
body: toBody(elmRequest.body),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Body} body
|
||||||
|
*/
|
||||||
|
function toBody(body) {
|
||||||
|
switch (body.tag) {
|
||||||
|
case "EmptyBody": {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
case "StringBody": {
|
||||||
|
return body.args[1];
|
||||||
|
}
|
||||||
|
case "JsonBody": {
|
||||||
|
return JSON.stringify(body.args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Body} body
|
||||||
|
* @returns Object
|
||||||
|
*/
|
||||||
|
function toContentType(body) {
|
||||||
|
switch (body.tag) {
|
||||||
|
case "EmptyBody": {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case "StringBody": {
|
||||||
|
return { "Content-Type": body.args[0] };
|
||||||
|
}
|
||||||
|
case "JsonBody": {
|
||||||
|
return { "Content-Type": "application/json" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @typedef { { tag: 'EmptyBody'} | { tag: 'StringBody'; args: [string, string] } | {tag: 'JsonBody'; args: [ Object ] } } Body */
|
||||||
|
function requireUncached(mode, filePath) {
|
||||||
|
if (mode === "dev-server") {
|
||||||
|
// for the build command, we can skip clearing the cache because it won't change while the build is running
|
||||||
|
// in the dev server, we want to clear the cache to get a the latest code each time it runs
|
||||||
|
delete require.cache[require.resolve(filePath)];
|
||||||
|
}
|
||||||
|
return require(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { lookupOrPerform };
|
@ -1,10 +1,13 @@
|
|||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = async function () {
|
||||||
var elmJson = JSON.parse(fs.readFileSync("./elm.json").toString());
|
var elmJson = JSON.parse(
|
||||||
|
(await fs.promises.readFile("./elm.json")).toString()
|
||||||
|
);
|
||||||
|
|
||||||
// write new elm.json
|
// write new elm.json
|
||||||
fs.writeFileSync(
|
|
||||||
|
await writeFileIfChanged(
|
||||||
"./elm-stuff/elm-pages/elm.json",
|
"./elm-stuff/elm-pages/elm.json",
|
||||||
JSON.stringify(rewriteElmJson(elmJson))
|
JSON.stringify(rewriteElmJson(elmJson))
|
||||||
);
|
);
|
||||||
@ -28,3 +31,18 @@ function rewriteElmJson(elmJson) {
|
|||||||
elmJson["source-directories"].push(".elm-pages");
|
elmJson["source-directories"].push(".elm-pages");
|
||||||
return elmJson;
|
return elmJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function writeFileIfChanged(filePath, content) {
|
||||||
|
if (
|
||||||
|
!(await fileExists(filePath)) ||
|
||||||
|
(await fs.promises.readFile(filePath, "utf8")) !== content
|
||||||
|
) {
|
||||||
|
await fs.promises.writeFile(filePath, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function fileExists(file) {
|
||||||
|
return fs.promises
|
||||||
|
.access(file, fs.constants.F_OK)
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
}
|
||||||
|
@ -14,7 +14,6 @@ function loadContentAndInitializeApp() {
|
|||||||
const app = Elm.TemplateModulesBeta.init({
|
const app = Elm.TemplateModulesBeta.init({
|
||||||
flags: {
|
flags: {
|
||||||
secrets: null,
|
secrets: null,
|
||||||
baseUrl: document.baseURI,
|
|
||||||
isPrerendering: false,
|
isPrerendering: false,
|
||||||
isDevServer: false,
|
isDevServer: false,
|
||||||
isElmDebugMode: false,
|
isElmDebugMode: false,
|
||||||
@ -45,7 +44,6 @@ function prefetchIfNeeded(/** @type {HTMLAnchorElement} */ target) {
|
|||||||
!prefetchedPages.includes(target.pathname)
|
!prefetchedPages.includes(target.pathname)
|
||||||
) {
|
) {
|
||||||
prefetchedPages.push(target.pathname);
|
prefetchedPages.push(target.pathname);
|
||||||
console.log("Preloading...", target.pathname);
|
|
||||||
const link = document.createElement("link");
|
const link = document.createElement("link");
|
||||||
link.setAttribute("as", "fetch");
|
link.setAttribute("as", "fetch");
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ async function handleEvent(sendContentJsonPort, evt) {
|
|||||||
showCompiling("");
|
showCompiling("");
|
||||||
elmJsFetch().then(thenApplyHmr);
|
elmJsFetch().then(thenApplyHmr);
|
||||||
} else if (evt.data === "style.css") {
|
} else if (evt.data === "style.css") {
|
||||||
|
// https://stackoverflow.com/a/43161591
|
||||||
const links = document.getElementsByTagName("link");
|
const links = document.getElementsByTagName("link");
|
||||||
for (var i = 0; i < links.length; i++) {
|
for (var i = 0; i < links.length; i++) {
|
||||||
const link = links[i];
|
const link = links[i];
|
||||||
|
@ -6,11 +6,12 @@ import Html exposing (Html)
|
|||||||
import Pages.Flags
|
import Pages.Flags
|
||||||
import Pages.PageUrl exposing (PageUrl)
|
import Pages.PageUrl exposing (PageUrl)
|
||||||
import Path exposing (Path)
|
import Path exposing (Path)
|
||||||
|
import Route exposing (Route)
|
||||||
import SharedTemplate exposing (SharedTemplate)
|
import SharedTemplate exposing (SharedTemplate)
|
||||||
import View exposing (View)
|
import View exposing (View)
|
||||||
|
|
||||||
|
|
||||||
template : SharedTemplate Msg Model Data SharedMsg msg
|
template : SharedTemplate Msg Model Data msg
|
||||||
template =
|
template =
|
||||||
{ init = init
|
{ init = init
|
||||||
, update = update
|
, update = update
|
||||||
@ -18,7 +19,6 @@ template =
|
|||||||
, data = data
|
, data = data
|
||||||
, subscriptions = subscriptions
|
, subscriptions = subscriptions
|
||||||
, onPageChange = Just OnPageChange
|
, onPageChange = Just OnPageChange
|
||||||
, sharedMsg = SharedMsg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +88,7 @@ view :
|
|||||||
Data
|
Data
|
||||||
->
|
->
|
||||||
{ path : Path
|
{ path : Path
|
||||||
, frontmatter : route
|
, route : Maybe Route
|
||||||
}
|
}
|
||||||
-> Model
|
-> Model
|
||||||
-> (Msg -> msg)
|
-> (Msg -> msg)
|
||||||
|
1667
package-lock.json
generated
1667
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
19
package.json
19
package.json
@ -22,35 +22,36 @@
|
|||||||
"author": "Dillon Kearns",
|
"author": "Dillon Kearns",
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^3.5.1",
|
"chokidar": "3.5.2",
|
||||||
"commander": "^7.2.0",
|
"commander": "8.0.0",
|
||||||
"connect": "^3.7.0",
|
"connect": "^3.7.0",
|
||||||
"cross-spawn": "7.0.3",
|
"cross-spawn": "7.0.3",
|
||||||
"elm-doc-preview": "^5.0.5",
|
"elm-doc-preview": "^5.0.5",
|
||||||
"elm-hot": "^1.1.6",
|
"elm-hot": "^1.1.6",
|
||||||
"elm-optimize-level-2": "^0.1.5",
|
"elm-optimize-level-2": "^0.1.5",
|
||||||
"fs-extra": "^10.0.0",
|
"fs-extra": "^10.0.0",
|
||||||
"globby": "^11.0.3",
|
"globby": "11.0.4",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"kleur": "^4.1.4",
|
"kleur": "^4.1.4",
|
||||||
"micromatch": "^4.0.4",
|
"micromatch": "^4.0.4",
|
||||||
|
"object-hash": "^2.2.0",
|
||||||
"serve-static": "^1.14.1",
|
"serve-static": "^1.14.1",
|
||||||
"terser": "^5.7.0",
|
"terser": "5.7.1",
|
||||||
"xhr2": "^0.2.1"
|
"undici": "4.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cross-spawn": "^6.0.2",
|
"@types/cross-spawn": "^6.0.2",
|
||||||
"@types/fs-extra": "^9.0.11",
|
"@types/fs-extra": "9.0.12",
|
||||||
"@types/micromatch": "^4.0.1",
|
"@types/micromatch": "^4.0.1",
|
||||||
"@types/node": "12.20.12",
|
"@types/node": "12.20.12",
|
||||||
"@types/serve-static": "^1.13.9",
|
"@types/serve-static": "1.13.10",
|
||||||
"cypress": "^7.4.0",
|
"cypress": "^8.0.0",
|
||||||
"elm-review": "^2.5.3",
|
"elm-review": "^2.5.3",
|
||||||
"elm-test": "^0.19.1-revision7",
|
"elm-test": "^0.19.1-revision7",
|
||||||
"elm-tooling": "^1.3.0",
|
"elm-tooling": "^1.3.0",
|
||||||
"elm-verify-examples": "^5.0.0",
|
"elm-verify-examples": "^5.0.0",
|
||||||
"mocha": "^8.4.0",
|
"mocha": "^8.4.0",
|
||||||
"typescript": "^4.2.4"
|
"typescript": "4.3.5"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"generator/src/",
|
"generator/src/",
|
||||||
|
1592
plugins/Markdown/Scaffolded.elm
Normal file
1592
plugins/Markdown/Scaffolded.elm
Normal file
File diff suppressed because it is too large
Load Diff
102
plugins/Shiki.elm
Normal file
102
plugins/Shiki.elm
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
module Shiki exposing (Highlighted, decoder, view)
|
||||||
|
|
||||||
|
import Html exposing (Html)
|
||||||
|
import Html.Attributes as Attr exposing (class)
|
||||||
|
import Html.Lazy
|
||||||
|
import OptimizedDecoder as Decode exposing (Decoder)
|
||||||
|
|
||||||
|
|
||||||
|
type alias ShikiToken =
|
||||||
|
{ content : String
|
||||||
|
, color : Maybe String
|
||||||
|
, fontStyle : Maybe ( String, String )
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias Highlighted =
|
||||||
|
{ lines : List (List ShikiToken)
|
||||||
|
, fg : String
|
||||||
|
, bg : String
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
decoder : Decoder Highlighted
|
||||||
|
decoder =
|
||||||
|
Decode.map3 Highlighted
|
||||||
|
(Decode.field "tokens" (Decode.list (Decode.list shikiTokenDecoder)))
|
||||||
|
(Decode.field "fg" Decode.string)
|
||||||
|
(Decode.field "bg" Decode.string)
|
||||||
|
|
||||||
|
|
||||||
|
shikiTokenDecoder : Decode.Decoder ShikiToken
|
||||||
|
shikiTokenDecoder =
|
||||||
|
Decode.map3 ShikiToken
|
||||||
|
(Decode.field "content" Decode.string)
|
||||||
|
(Decode.optionalField "color" Decode.string)
|
||||||
|
(Decode.optionalField "fontStyle" fontStyleDecoder |> Decode.map (Maybe.andThen identity))
|
||||||
|
|
||||||
|
|
||||||
|
fontStyleDecoder : Decoder (Maybe ( String, String ))
|
||||||
|
fontStyleDecoder =
|
||||||
|
Decode.int
|
||||||
|
|> Decode.map
|
||||||
|
(\styleNumber ->
|
||||||
|
case styleNumber of
|
||||||
|
1 ->
|
||||||
|
Just ( "font-style", "italic" )
|
||||||
|
|
||||||
|
2 ->
|
||||||
|
Just ( "font-style", "bold" )
|
||||||
|
|
||||||
|
4 ->
|
||||||
|
Just ( "font-style", "underline" )
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Nothing
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
{-| <https://github.com/shikijs/shiki/blob/2a31dc50f4fbdb9a63990ccd15e08cccc9c1566a/packages/shiki/src/renderer.ts#L16>
|
||||||
|
-}
|
||||||
|
view : List (Html.Attribute msg) -> Highlighted -> Html msg
|
||||||
|
view attrs highlighted =
|
||||||
|
highlighted.lines
|
||||||
|
|> List.indexedMap
|
||||||
|
(\lineIndex line ->
|
||||||
|
let
|
||||||
|
isLastLine =
|
||||||
|
List.length highlighted.lines == (lineIndex + 1)
|
||||||
|
in
|
||||||
|
Html.span [ class "line" ]
|
||||||
|
((line
|
||||||
|
|> List.map
|
||||||
|
(\token ->
|
||||||
|
Html.span
|
||||||
|
[ Attr.style "color" (token.color |> Maybe.withDefault highlighted.fg)
|
||||||
|
, token.fontStyle
|
||||||
|
|> Maybe.map
|
||||||
|
(\( key, value ) ->
|
||||||
|
Attr.style key value
|
||||||
|
)
|
||||||
|
|> Maybe.withDefault (Attr.title "")
|
||||||
|
]
|
||||||
|
[ Html.text token.content ]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
++ [ if isLastLine then
|
||||||
|
Html.text ""
|
||||||
|
|
||||||
|
else
|
||||||
|
Html.text "\n"
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> Html.code []
|
||||||
|
|> List.singleton
|
||||||
|
|> Html.pre
|
||||||
|
([ Attr.style "background-color" highlighted.bg
|
||||||
|
, Attr.style "white-space" "pre-wrap"
|
||||||
|
, Attr.style "overflow-wrap" "break-word"
|
||||||
|
]
|
||||||
|
++ attrs
|
||||||
|
)
|
64
plugins/Timestamps.elm
Normal file
64
plugins/Timestamps.elm
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
module Timestamps exposing (Timestamps, data, format)
|
||||||
|
|
||||||
|
import DataSource exposing (DataSource)
|
||||||
|
import DataSource.Port
|
||||||
|
import DateFormat
|
||||||
|
import Json.Encode
|
||||||
|
import List.Extra
|
||||||
|
import OptimizedDecoder as Decode exposing (Decoder)
|
||||||
|
import Result.Extra
|
||||||
|
import Time
|
||||||
|
|
||||||
|
|
||||||
|
type alias Timestamps =
|
||||||
|
{ updated : Time.Posix
|
||||||
|
, created : Time.Posix
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
data : String -> DataSource Timestamps
|
||||||
|
data filePath =
|
||||||
|
DataSource.Port.send "gitTimestamps"
|
||||||
|
(Json.Encode.string filePath)
|
||||||
|
(Decode.string
|
||||||
|
|> Decode.map (String.trim >> String.split "\n")
|
||||||
|
|> Decode.map (List.map secondsStringToPosix)
|
||||||
|
|> Decode.map Result.Extra.combine
|
||||||
|
|> Decode.andThen Decode.fromResult
|
||||||
|
|> Decode.map (firstAndLast Timestamps >> Result.fromMaybe "Error")
|
||||||
|
|> Decode.andThen Decode.fromResult
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
firstAndLast : (a -> a -> b) -> List a -> Maybe b
|
||||||
|
firstAndLast constructor list =
|
||||||
|
Maybe.map2 constructor
|
||||||
|
(List.head list)
|
||||||
|
(List.Extra.last list)
|
||||||
|
|
||||||
|
|
||||||
|
secondsStringToPosix : String -> Result String Time.Posix
|
||||||
|
secondsStringToPosix posixTime =
|
||||||
|
posixTime
|
||||||
|
|> String.trim
|
||||||
|
|> String.toInt
|
||||||
|
|> Maybe.map (\unixTimeInSeconds -> (unixTimeInSeconds * 1000) |> Time.millisToPosix)
|
||||||
|
|> Result.fromMaybe "Expected int"
|
||||||
|
|
||||||
|
|
||||||
|
format : Time.Posix -> String
|
||||||
|
format posix =
|
||||||
|
DateFormat.format
|
||||||
|
[ DateFormat.monthNameFull
|
||||||
|
, DateFormat.text " "
|
||||||
|
, DateFormat.dayOfMonthNumber
|
||||||
|
, DateFormat.text ", "
|
||||||
|
, DateFormat.yearNumber
|
||||||
|
]
|
||||||
|
pacificZone
|
||||||
|
posix
|
||||||
|
|
||||||
|
|
||||||
|
pacificZone : Time.Zone
|
||||||
|
pacificZone =
|
||||||
|
Time.customZone (-60 * 7) []
|
@ -9,13 +9,13 @@
|
|||||||
"elm/core": "1.0.5",
|
"elm/core": "1.0.5",
|
||||||
"elm/json": "1.1.3",
|
"elm/json": "1.1.3",
|
||||||
"elm/project-metadata-utils": "1.0.2",
|
"elm/project-metadata-utils": "1.0.2",
|
||||||
"jfmengels/elm-review": "2.4.2",
|
"jfmengels/elm-review": "2.5.0",
|
||||||
"jfmengels/elm-review-common": "1.0.4",
|
"jfmengels/elm-review-common": "1.0.4",
|
||||||
"jfmengels/elm-review-debug": "1.0.6",
|
"jfmengels/elm-review-debug": "1.0.6",
|
||||||
"jfmengels/elm-review-performance": "1.0.0",
|
"jfmengels/elm-review-performance": "1.0.1",
|
||||||
"jfmengels/elm-review-unused": "1.1.11",
|
"jfmengels/elm-review-unused": "1.1.15",
|
||||||
"sparksp/elm-review-imports": "1.0.1",
|
"sparksp/elm-review-imports": "1.0.1",
|
||||||
"stil4m/elm-syntax": "7.2.5"
|
"stil4m/elm-syntax": "7.2.6"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
"elm/html": "1.0.0",
|
"elm/html": "1.0.0",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"elm/random": "1.0.0",
|
"elm/random": "1.0.0",
|
||||||
"elm/time": "1.0.0",
|
"elm/time": "1.0.0",
|
||||||
"elm/virtual-dom": "1.0.2",
|
"elm/virtual-dom": "1.0.2",
|
||||||
"elm-community/list-extra": "8.3.0",
|
"elm-community/list-extra": "8.3.1",
|
||||||
"elm-explorations/test": "1.2.2",
|
"elm-explorations/test": "1.2.2",
|
||||||
"miniBill/elm-unicode": "1.0.2",
|
"miniBill/elm-unicode": "1.0.2",
|
||||||
"rtfeldman/elm-hex": "1.0.0",
|
"rtfeldman/elm-hex": "1.0.0",
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
module ApiRoute exposing (Done, Handler, Response, buildTimeRoutes, capture, int, literal, single, slash, succeed)
|
module ApiRoute exposing
|
||||||
|
( Done, Handler, Response, buildTimeRoutes, capture, int, literal, single, slash, succeed
|
||||||
|
, getBuildTimeRoutes
|
||||||
|
)
|
||||||
|
|
||||||
{-|
|
{-|
|
||||||
|
|
||||||
@ -104,13 +107,8 @@ slash (Handler pattern handler toString constructor) =
|
|||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
capture :
|
capture :
|
||||||
Handler
|
Handler (String -> a) constructor
|
||||||
(String -> a)
|
-> Handler a (String -> constructor)
|
||||||
constructor
|
|
||||||
->
|
|
||||||
Handler
|
|
||||||
a
|
|
||||||
(String -> constructor)
|
|
||||||
capture (Handler pattern previousHandler toString constructor) =
|
capture (Handler pattern previousHandler toString constructor) =
|
||||||
Handler
|
Handler
|
||||||
(pattern ++ "(.*)")
|
(pattern ++ "(.*)")
|
||||||
@ -138,13 +136,8 @@ capture (Handler pattern previousHandler toString constructor) =
|
|||||||
|
|
||||||
{-| -}
|
{-| -}
|
||||||
int :
|
int :
|
||||||
Handler
|
Handler (Int -> a) constructor
|
||||||
(Int -> a)
|
-> Handler a (Int -> constructor)
|
||||||
constructor
|
|
||||||
->
|
|
||||||
Handler
|
|
||||||
a
|
|
||||||
(Int -> constructor)
|
|
||||||
int (Handler pattern previousHandler toString constructor) =
|
int (Handler pattern previousHandler toString constructor) =
|
||||||
Handler
|
Handler
|
||||||
(pattern ++ "(\\d+)")
|
(pattern ++ "(\\d+)")
|
||||||
@ -170,6 +163,11 @@ int (Handler pattern previousHandler toString constructor) =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
getBuildTimeRoutes : Done response -> DataSource (List String)
|
||||||
|
getBuildTimeRoutes (Done handler) =
|
||||||
|
handler.buildTimeRoutes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--captureRest : Handler (List String -> a) b -> Handler a b
|
--captureRest : Handler (List String -> a) b -> Handler a b
|
||||||
--captureRest previousHandler =
|
--captureRest previousHandler =
|
||||||
|
@ -29,7 +29,7 @@ errorToString error =
|
|||||||
banner : String -> List Terminal.Text
|
banner : String -> List Terminal.Text
|
||||||
banner title =
|
banner title =
|
||||||
[ Terminal.cyan <|
|
[ Terminal.cyan <|
|
||||||
Terminal.text ("-- " ++ String.toUpper title ++ " ----------------------------------------------------- elm-pages")
|
("-- " ++ String.toUpper title ++ " ----------------------------------------------------- elm-pages")
|
||||||
, Terminal.text "\n\n"
|
, Terminal.text "\n\n"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -418,7 +418,7 @@ resolve =
|
|||||||
-}
|
-}
|
||||||
combine : List (DataSource value) -> DataSource (List value)
|
combine : List (DataSource value) -> DataSource (List value)
|
||||||
combine =
|
combine =
|
||||||
List.foldl (map2 (::)) (succeed [])
|
List.foldr (map2 (::)) (succeed [])
|
||||||
|
|
||||||
|
|
||||||
{-| Like map, but it takes in two `Request`s.
|
{-| Like map, but it takes in two `Request`s.
|
||||||
|
@ -59,7 +59,7 @@ frontmatter frontmatterDecoder =
|
|||||||
import DataSource.File as File
|
import DataSource.File as File
|
||||||
import OptimizedDecoder as Decode exposing (Decoder)
|
import OptimizedDecoder as Decode exposing (Decoder)
|
||||||
|
|
||||||
blogPost : DataSource ( String, BlogPostMetadata )
|
blogPost : DataSource BlogPostMetadata
|
||||||
blogPost =
|
blogPost =
|
||||||
File.bodyWithFrontmatter blogPostDecoder
|
File.bodyWithFrontmatter blogPostDecoder
|
||||||
"blog/hello-world.md"
|
"blog/hello-world.md"
|
||||||
@ -70,7 +70,7 @@ frontmatter frontmatterDecoder =
|
|||||||
, tags : List String
|
, tags : List String
|
||||||
}
|
}
|
||||||
|
|
||||||
blogPostDecoder : Decoder BlogPostMetadata
|
blogPostDecoder : String -> Decoder BlogPostMetadata
|
||||||
blogPostDecoder body =
|
blogPostDecoder body =
|
||||||
Decode.map2 (BlogPostMetadata body)
|
Decode.map2 (BlogPostMetadata body)
|
||||||
(Decode.field "title" Decode.string)
|
(Decode.field "title" Decode.string)
|
||||||
|
20
src/DataSource/Port.elm
Normal file
20
src/DataSource/Port.elm
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module DataSource.Port exposing (send)
|
||||||
|
|
||||||
|
import DataSource
|
||||||
|
import DataSource.Http
|
||||||
|
import Json.Encode
|
||||||
|
import OptimizedDecoder exposing (Decoder)
|
||||||
|
import Secrets
|
||||||
|
|
||||||
|
|
||||||
|
send : String -> Json.Encode.Value -> Decoder b -> DataSource.DataSource b
|
||||||
|
send portName input decoder =
|
||||||
|
DataSource.Http.request
|
||||||
|
(Secrets.succeed
|
||||||
|
{ url = "port://" ++ portName
|
||||||
|
, method = "GET"
|
||||||
|
, headers = []
|
||||||
|
, body = DataSource.jsonBody input
|
||||||
|
}
|
||||||
|
)
|
||||||
|
decoder
|
@ -148,14 +148,17 @@ prerenderedOptionsView moduleContext routes =
|
|||||||
Html.li
|
Html.li
|
||||||
[ Attr.style "list-style" "inside"
|
[ Attr.style "list-style" "inside"
|
||||||
]
|
]
|
||||||
[ Html.a
|
[ --Html.a
|
||||||
[ Attr.href "/blog/extensible-markdown-parsing-in-elm"
|
-- [-- Attr.href "/blog/extensible-markdown-parsing-in-elm"
|
||||||
]
|
-- -- TODO get href data
|
||||||
[ Html.code
|
-- ]
|
||||||
|
-- [
|
||||||
|
Html.code
|
||||||
[]
|
[]
|
||||||
[ Html.text (recordToString record)
|
[ Html.text (recordToString record)
|
||||||
]
|
]
|
||||||
]
|
|
||||||
|
--]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -53,26 +53,22 @@ type alias Path =
|
|||||||
|
|
||||||
|
|
||||||
init :
|
init :
|
||||||
Maybe ( { currentUrl : Url, baseUrl : Url }, ContentJson )
|
Maybe ( Path, ContentJson )
|
||||||
-> ContentCache
|
-> ContentCache
|
||||||
init maybeInitialPageContent =
|
init maybeInitialPageContent =
|
||||||
Dict.fromList []
|
|
||||||
|> (\dict ->
|
|
||||||
case maybeInitialPageContent of
|
case maybeInitialPageContent of
|
||||||
Nothing ->
|
Nothing ->
|
||||||
dict
|
Dict.empty
|
||||||
|
|
||||||
Just ( urls, contentJson ) ->
|
Just ( urls, contentJson ) ->
|
||||||
dict
|
Dict.singleton urls (Parsed contentJson)
|
||||||
|> Dict.insert (pathForUrl urls) (Parsed contentJson)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
{-| Get from the Cache... if it's not already parsed, it will
|
{-| Get from the Cache... if it's not already parsed, it will
|
||||||
parse it before returning it and store the parsed version in the Cache
|
parse it before returning it and store the parsed version in the Cache
|
||||||
-}
|
-}
|
||||||
lazyLoad :
|
lazyLoad :
|
||||||
{ currentUrl : Url, baseUrl : Url }
|
{ currentUrl : Url, basePath : List String }
|
||||||
-> ContentCache
|
-> ContentCache
|
||||||
-> Task Http.Error ( Url, ContentJson, ContentCache )
|
-> Task Http.Error ( Url, ContentJson, ContentCache )
|
||||||
lazyLoad urls cache =
|
lazyLoad urls cache =
|
||||||
@ -141,6 +137,7 @@ httpTask url =
|
|||||||
type alias ContentJson =
|
type alias ContentJson =
|
||||||
{ staticData : RequestsAndPending
|
{ staticData : RequestsAndPending
|
||||||
, is404 : Bool
|
, is404 : Bool
|
||||||
|
, path : Maybe String
|
||||||
, notFoundReason : Maybe NotFoundReason.Payload
|
, notFoundReason : Maybe NotFoundReason.Payload
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,9 +148,10 @@ contentJsonDecoder =
|
|||||||
|> Decode.andThen
|
|> Decode.andThen
|
||||||
(\is404Value ->
|
(\is404Value ->
|
||||||
if is404Value then
|
if is404Value then
|
||||||
Decode.map3 ContentJson
|
Decode.map4 ContentJson
|
||||||
(Decode.succeed Dict.empty)
|
(Decode.succeed Dict.empty)
|
||||||
(Decode.succeed is404Value)
|
(Decode.succeed is404Value)
|
||||||
|
(Decode.field "path" Decode.string |> Decode.map Just)
|
||||||
(Decode.at [ "staticData", "notFoundReason" ]
|
(Decode.at [ "staticData", "notFoundReason" ]
|
||||||
(Decode.string
|
(Decode.string
|
||||||
|> Decode.andThen
|
|> Decode.andThen
|
||||||
@ -176,16 +174,17 @@ contentJsonDecoder =
|
|||||||
)
|
)
|
||||||
|
|
||||||
else
|
else
|
||||||
Decode.map3 ContentJson
|
Decode.map4 ContentJson
|
||||||
(Decode.field "staticData" RequestsAndPending.decoder)
|
(Decode.field "staticData" RequestsAndPending.decoder)
|
||||||
(Decode.succeed is404Value)
|
(Decode.succeed is404Value)
|
||||||
(Decode.succeed Nothing)
|
(Decode.succeed Nothing)
|
||||||
|
(Decode.succeed Nothing)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
update :
|
update :
|
||||||
ContentCache
|
ContentCache
|
||||||
-> { currentUrl : Url, baseUrl : Url }
|
-> { currentUrl : Url, basePath : List String }
|
||||||
-> ContentJson
|
-> ContentJson
|
||||||
-> ContentCache
|
-> ContentCache
|
||||||
update cache urls rawContent =
|
update cache urls rawContent =
|
||||||
@ -199,6 +198,7 @@ update cache urls rawContent =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
{ staticData = rawContent.staticData
|
{ staticData = rawContent.staticData
|
||||||
, is404 = rawContent.is404
|
, is404 = rawContent.is404
|
||||||
|
, path = rawContent.path
|
||||||
, notFoundReason = rawContent.notFoundReason
|
, notFoundReason = rawContent.notFoundReason
|
||||||
}
|
}
|
||||||
|> Parsed
|
|> Parsed
|
||||||
@ -207,18 +207,18 @@ update cache urls rawContent =
|
|||||||
cache
|
cache
|
||||||
|
|
||||||
|
|
||||||
pathForUrl : { currentUrl : Url, baseUrl : Url } -> Path
|
pathForUrl : { currentUrl : Url, basePath : List String } -> Path
|
||||||
pathForUrl { currentUrl, baseUrl } =
|
pathForUrl { currentUrl, basePath } =
|
||||||
currentUrl.path
|
currentUrl.path
|
||||||
|> String.dropLeft (String.length baseUrl.path)
|
|
||||||
|> String.chopForwardSlashes
|
|> String.chopForwardSlashes
|
||||||
|> String.split "/"
|
|> String.split "/"
|
||||||
|> List.filter ((/=) "")
|
|> List.filter ((/=) "")
|
||||||
|
|> List.drop (List.length basePath)
|
||||||
|
|
||||||
|
|
||||||
is404 :
|
is404 :
|
||||||
ContentCache
|
ContentCache
|
||||||
-> { currentUrl : Url, baseUrl : Url }
|
-> { currentUrl : Url, basePath : List String }
|
||||||
-> Bool
|
-> Bool
|
||||||
is404 dict urls =
|
is404 dict urls =
|
||||||
dict
|
dict
|
||||||
@ -234,7 +234,7 @@ is404 dict urls =
|
|||||||
|
|
||||||
notFoundReason :
|
notFoundReason :
|
||||||
ContentCache
|
ContentCache
|
||||||
-> { currentUrl : Url, baseUrl : Url }
|
-> { currentUrl : Url, basePath : List String }
|
||||||
-> Maybe NotFoundReason.Payload
|
-> Maybe NotFoundReason.Payload
|
||||||
notFoundReason dict urls =
|
notFoundReason dict urls =
|
||||||
dict
|
dict
|
||||||
|
@ -19,7 +19,6 @@ import Pages.ProgramConfig exposing (ProgramConfig)
|
|||||||
import Pages.StaticHttpRequest as StaticHttpRequest
|
import Pages.StaticHttpRequest as StaticHttpRequest
|
||||||
import Path exposing (Path)
|
import Path exposing (Path)
|
||||||
import QueryParams
|
import QueryParams
|
||||||
import RequestsAndPending exposing (RequestsAndPending)
|
|
||||||
import Task
|
import Task
|
||||||
import Url exposing (Url)
|
import Url exposing (Url)
|
||||||
|
|
||||||
@ -34,10 +33,10 @@ mainView :
|
|||||||
-> { title : String, body : Html userMsg }
|
-> { title : String, body : Html userMsg }
|
||||||
mainView config model =
|
mainView config model =
|
||||||
let
|
let
|
||||||
urls : { currentUrl : Url, baseUrl : Url }
|
urls : { currentUrl : Url, basePath : List String }
|
||||||
urls =
|
urls =
|
||||||
{ currentUrl = model.url
|
{ currentUrl = model.url
|
||||||
, baseUrl = model.baseUrl
|
, basePath = config.basePath
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
case ContentCache.notFoundReason model.contentCache urls of
|
case ContentCache.notFoundReason model.contentCache urls of
|
||||||
@ -49,7 +48,7 @@ mainView config model =
|
|||||||
Ok pageData ->
|
Ok pageData ->
|
||||||
(config.view
|
(config.view
|
||||||
{ path = ContentCache.pathForUrl urls |> Path.join
|
{ path = ContentCache.pathForUrl urls |> Path.join
|
||||||
, frontmatter = config.urlToRoute model.url
|
, route = config.urlToRoute model.url
|
||||||
}
|
}
|
||||||
Nothing
|
Nothing
|
||||||
pageData.sharedData
|
pageData.sharedData
|
||||||
@ -65,27 +64,15 @@ mainView config model =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
urlToPath : Url -> Url -> Path
|
|
||||||
urlToPath url baseUrl =
|
|
||||||
url.path
|
|
||||||
|> String.dropLeft (String.length baseUrl.path)
|
|
||||||
|> String.chopForwardSlashes
|
|
||||||
|> String.split "/"
|
|
||||||
|> List.filter ((/=) "")
|
|
||||||
|> Path.join
|
|
||||||
|
|
||||||
|
|
||||||
urlsToPagePath :
|
urlsToPagePath :
|
||||||
{ currentUrl : Url
|
{ currentUrl : Url, basePath : List String }
|
||||||
, baseUrl : Url
|
|
||||||
}
|
|
||||||
-> Path
|
-> Path
|
||||||
urlsToPagePath urls =
|
urlsToPagePath urls =
|
||||||
urls.currentUrl.path
|
urls.currentUrl.path
|
||||||
|> String.dropLeft (String.length urls.baseUrl.path)
|
|
||||||
|> String.chopForwardSlashes
|
|> String.chopForwardSlashes
|
||||||
|> String.split "/"
|
|> String.split "/"
|
||||||
|> List.filter ((/=) "")
|
|> List.filter ((/=) "")
|
||||||
|
|> List.drop (List.length urls.basePath)
|
||||||
|> Path.join
|
|> Path.join
|
||||||
|
|
||||||
|
|
||||||
@ -137,33 +124,37 @@ init config flags url key =
|
|||||||
ContentCache.init
|
ContentCache.init
|
||||||
(Maybe.map
|
(Maybe.map
|
||||||
(\cj ->
|
(\cj ->
|
||||||
-- TODO parse the page path to a list here
|
( currentPath
|
||||||
( urls
|
|
||||||
, cj
|
, cj
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
contentJson
|
contentJson
|
||||||
)
|
)
|
||||||
|
|
||||||
|
currentPath : List String
|
||||||
|
currentPath =
|
||||||
|
flags
|
||||||
|
|> Decode.decodeValue
|
||||||
|
(Decode.at [ "contentJson", "path" ]
|
||||||
|
(Decode.string
|
||||||
|
|> Decode.map Path.fromString
|
||||||
|
|> Decode.map Path.toSegments
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> Result.mapError Decode.errorToString
|
||||||
|
|> Result.toMaybe
|
||||||
|
|> Maybe.withDefault []
|
||||||
|
|
||||||
contentJson : Maybe ContentJson
|
contentJson : Maybe ContentJson
|
||||||
contentJson =
|
contentJson =
|
||||||
flags
|
flags
|
||||||
|> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder)
|
|> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder)
|
||||||
|> Result.toMaybe
|
|> Result.toMaybe
|
||||||
|
|
||||||
baseUrl : Url
|
urls : { currentUrl : Url, basePath : List String }
|
||||||
baseUrl =
|
|
||||||
flags
|
|
||||||
|> Decode.decodeValue (Decode.field "baseUrl" Decode.string)
|
|
||||||
|> Result.toMaybe
|
|
||||||
|> Maybe.andThen Url.fromString
|
|
||||||
|> Maybe.withDefault url
|
|
||||||
|
|
||||||
urls : { currentUrl : Url, baseUrl : Url }
|
|
||||||
urls =
|
urls =
|
||||||
-- @@@
|
{ currentUrl = url
|
||||||
{ currentUrl = url -- |> normalizeUrl baseUrl
|
, basePath = config.basePath
|
||||||
, baseUrl = baseUrl
|
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
case contentJson |> Maybe.map .staticData of
|
case contentJson |> Maybe.map .staticData of
|
||||||
@ -231,10 +222,10 @@ init config flags url key =
|
|||||||
|> List.filterMap identity
|
|> List.filterMap identity
|
||||||
|> Cmd.batch
|
|> Cmd.batch
|
||||||
|
|
||||||
|
initialModel : Model userModel pageData sharedData
|
||||||
initialModel =
|
initialModel =
|
||||||
{ key = key
|
{ key = key
|
||||||
, url = url
|
, url = url
|
||||||
, baseUrl = baseUrl
|
|
||||||
, contentCache = contentCache
|
, contentCache = contentCache
|
||||||
, pageData =
|
, pageData =
|
||||||
Ok
|
Ok
|
||||||
@ -243,6 +234,7 @@ init config flags url key =
|
|||||||
, userModel = userModel
|
, userModel = userModel
|
||||||
}
|
}
|
||||||
, ariaNavigationAnnouncement = ""
|
, ariaNavigationAnnouncement = ""
|
||||||
|
, userFlags = flags
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
( { initialModel
|
( { initialModel
|
||||||
@ -254,10 +246,10 @@ init config flags url key =
|
|||||||
Err error ->
|
Err error ->
|
||||||
( { key = key
|
( { key = key
|
||||||
, url = url
|
, url = url
|
||||||
, baseUrl = baseUrl
|
|
||||||
, contentCache = contentCache
|
, contentCache = contentCache
|
||||||
, pageData = BuildError.errorToString error |> Err
|
, pageData = BuildError.errorToString error |> Err
|
||||||
, ariaNavigationAnnouncement = "Error"
|
, ariaNavigationAnnouncement = "Error"
|
||||||
|
, userFlags = flags
|
||||||
}
|
}
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
@ -265,10 +257,10 @@ init config flags url key =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
( { key = key
|
( { key = key
|
||||||
, url = url
|
, url = url
|
||||||
, baseUrl = baseUrl
|
|
||||||
, contentCache = contentCache
|
, contentCache = contentCache
|
||||||
, pageData = Err "TODO"
|
, pageData = Err "TODO"
|
||||||
, ariaNavigationAnnouncement = "Error"
|
, ariaNavigationAnnouncement = "Error"
|
||||||
|
, userFlags = flags
|
||||||
}
|
}
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
@ -288,16 +280,15 @@ type Msg userMsg
|
|||||||
type alias Model userModel pageData sharedData =
|
type alias Model userModel pageData sharedData =
|
||||||
{ key : Browser.Navigation.Key
|
{ key : Browser.Navigation.Key
|
||||||
, url : Url
|
, url : Url
|
||||||
, baseUrl : Url
|
|
||||||
, contentCache : ContentCache
|
, contentCache : ContentCache
|
||||||
, ariaNavigationAnnouncement : String
|
, ariaNavigationAnnouncement : String
|
||||||
, pageData :
|
, pageData :
|
||||||
Result
|
Result String
|
||||||
String
|
|
||||||
{ userModel : userModel
|
{ userModel : userModel
|
||||||
, pageData : pageData
|
, pageData : pageData
|
||||||
, sharedData : sharedData
|
, sharedData : sharedData
|
||||||
}
|
}
|
||||||
|
, userFlags : Decode.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -335,10 +326,10 @@ update config appMsg model =
|
|||||||
navigatingToSamePage =
|
navigatingToSamePage =
|
||||||
(url.path == model.url.path) && (url /= model.url)
|
(url.path == model.url.path) && (url /= model.url)
|
||||||
|
|
||||||
urls : { currentUrl : Url, baseUrl : Url }
|
urls : { currentUrl : Url, basePath : List String }
|
||||||
urls =
|
urls =
|
||||||
{ currentUrl = url
|
{ currentUrl = url
|
||||||
, baseUrl = model.baseUrl
|
, basePath = config.basePath
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
if navigatingToSamePage then
|
if navigatingToSamePage then
|
||||||
@ -368,7 +359,7 @@ update config appMsg model =
|
|||||||
{ protocol = model.url.protocol
|
{ protocol = model.url.protocol
|
||||||
, host = model.url.host
|
, host = model.url.host
|
||||||
, port_ = model.url.port_
|
, port_ = model.url.port_
|
||||||
, path = urlToPath url model.baseUrl
|
, path = urlPathToPath config urls.currentUrl
|
||||||
, query = url.query
|
, query = url.query
|
||||||
, fragment = url.fragment
|
, fragment = url.fragment
|
||||||
, metadata = config.urlToRoute url
|
, metadata = config.urlToRoute url
|
||||||
@ -449,7 +440,12 @@ update config appMsg model =
|
|||||||
StaticHttpRequest.resolve ApplicationType.Browser
|
StaticHttpRequest.resolve ApplicationType.Browser
|
||||||
(config.data (config.urlToRoute url))
|
(config.data (config.urlToRoute url))
|
||||||
contentJson.staticData
|
contentJson.staticData
|
||||||
|> Result.mapError (\_ -> "Http error")
|
|> Result.mapError
|
||||||
|
(\error ->
|
||||||
|
error
|
||||||
|
|> StaticHttpRequest.toBuildError ""
|
||||||
|
|> BuildError.errorToString
|
||||||
|
)
|
||||||
|
|
||||||
( userModel, userCmd ) =
|
( userModel, userCmd ) =
|
||||||
config.update
|
config.update
|
||||||
@ -460,7 +456,7 @@ update config appMsg model =
|
|||||||
{ protocol = model.url.protocol
|
{ protocol = model.url.protocol
|
||||||
, host = model.url.host
|
, host = model.url.host
|
||||||
, port_ = model.url.port_
|
, port_ = model.url.port_
|
||||||
, path = urlToPath url model.baseUrl
|
, path = url |> urlPathToPath config
|
||||||
, query = url.query
|
, query = url.query
|
||||||
, fragment = url.fragment
|
, fragment = url.fragment
|
||||||
, metadata = config.urlToRoute url
|
, metadata = config.urlToRoute url
|
||||||
@ -484,18 +480,36 @@ update config appMsg model =
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
Err _ ->
|
Err error ->
|
||||||
-- TODO handle error
|
{-
|
||||||
( { model | url = url }, Cmd.none )
|
When there is an error loading the content.json, we are either
|
||||||
|
1) in the dev server, and should show the relevant DataSource error for the page
|
||||||
|
we're navigating to. This could be done more cleanly, but it's simplest to just
|
||||||
|
do a fresh page load and use the code path for presenting an error for a fresh page.
|
||||||
|
2) In a production app. That means we had a successful build, so there were no DataSource failures,
|
||||||
|
so the app must be stale (unless it's in some unexpected state from a bug). In the future,
|
||||||
|
it probably makes sense to include some sort of hash of the app version we are fetching, match
|
||||||
|
it with the current version that's running, and perform this logic when we see there is a mismatch.
|
||||||
|
But for now, if there is any error we do a full page load (not a single-page navigation), which
|
||||||
|
gives us a fresh version of the app to make sure things are in sync.
|
||||||
|
|
||||||
|
-}
|
||||||
|
( model
|
||||||
|
, url
|
||||||
|
|> Url.toString
|
||||||
|
|> Browser.Navigation.load
|
||||||
|
)
|
||||||
|
|
||||||
PageScrollComplete ->
|
PageScrollComplete ->
|
||||||
( model, Cmd.none )
|
( model, Cmd.none )
|
||||||
|
|
||||||
HotReloadComplete contentJson ->
|
HotReloadComplete contentJson ->
|
||||||
let
|
let
|
||||||
urls : { currentUrl : Url, baseUrl : Url }
|
urls : { currentUrl : Url, basePath : List String }
|
||||||
urls =
|
urls =
|
||||||
{ currentUrl = model.url, baseUrl = model.baseUrl }
|
{ currentUrl = model.url
|
||||||
|
, basePath = config.basePath
|
||||||
|
}
|
||||||
|
|
||||||
pageDataResult : Result BuildError pageData
|
pageDataResult : Result BuildError pageData
|
||||||
pageDataResult =
|
pageDataResult =
|
||||||
@ -536,7 +550,7 @@ update config appMsg model =
|
|||||||
{ protocol = model.url.protocol
|
{ protocol = model.url.protocol
|
||||||
, host = model.url.host
|
, host = model.url.host
|
||||||
, port_ = model.url.port_
|
, port_ = model.url.port_
|
||||||
, path = urlToPath model.url model.baseUrl
|
, path = model.url |> urlPathToPath config
|
||||||
, query = model.url.query
|
, query = model.url.query
|
||||||
, fragment = model.url.fragment
|
, fragment = model.url.fragment
|
||||||
, metadata = config.urlToRoute model.url
|
, metadata = config.urlToRoute model.url
|
||||||
@ -554,7 +568,15 @@ update config appMsg model =
|
|||||||
case updateResult of
|
case updateResult of
|
||||||
Just ( userModel, userCmd ) ->
|
Just ( userModel, userCmd ) ->
|
||||||
( { model
|
( { model
|
||||||
| contentCache = ContentCache.init (Just ( urls, contentJson ))
|
| contentCache =
|
||||||
|
ContentCache.init
|
||||||
|
(Just
|
||||||
|
( urls.currentUrl
|
||||||
|
|> config.urlToRoute
|
||||||
|
|> config.routeToPath
|
||||||
|
, contentJson
|
||||||
|
)
|
||||||
|
)
|
||||||
, pageData =
|
, pageData =
|
||||||
Ok
|
Ok
|
||||||
{ pageData = pageData
|
{ pageData = pageData
|
||||||
@ -568,9 +590,49 @@ update config appMsg model =
|
|||||||
)
|
)
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
|
let
|
||||||
|
pagePath : Path
|
||||||
|
pagePath =
|
||||||
|
urlsToPagePath urls
|
||||||
|
|
||||||
|
userFlags : Pages.Flags.Flags
|
||||||
|
userFlags =
|
||||||
|
model.userFlags
|
||||||
|
|> Decode.decodeValue
|
||||||
|
(Decode.field "userFlags" Decode.value)
|
||||||
|
|> Result.withDefault Json.Encode.null
|
||||||
|
|> Pages.Flags.BrowserFlags
|
||||||
|
|
||||||
|
( userModel, userCmd ) =
|
||||||
|
Just
|
||||||
|
{ path =
|
||||||
|
{ path = pagePath
|
||||||
|
, query = model.url.query
|
||||||
|
, fragment = model.url.fragment
|
||||||
|
}
|
||||||
|
, metadata = config.urlToRoute model.url
|
||||||
|
, pageUrl =
|
||||||
|
Just
|
||||||
|
{ protocol = model.url.protocol
|
||||||
|
, host = model.url.host
|
||||||
|
, port_ = model.url.port_
|
||||||
|
, path = pagePath
|
||||||
|
, query = model.url.query |> Maybe.map QueryParams.fromString
|
||||||
|
, fragment = model.url.fragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> config.init userFlags sharedData pageData (Just model.key)
|
||||||
|
in
|
||||||
( { model
|
( { model
|
||||||
| contentCache =
|
| contentCache =
|
||||||
ContentCache.init (Just ( urls, contentJson ))
|
ContentCache.init
|
||||||
|
(Just
|
||||||
|
( urls.currentUrl
|
||||||
|
|> config.urlToRoute
|
||||||
|
|> config.routeToPath
|
||||||
|
, contentJson
|
||||||
|
)
|
||||||
|
)
|
||||||
, pageData =
|
, pageData =
|
||||||
model.pageData
|
model.pageData
|
||||||
|> Result.map
|
|> Result.map
|
||||||
@ -580,14 +642,27 @@ update config appMsg model =
|
|||||||
, userModel = previousPageData.userModel
|
, userModel = previousPageData.userModel
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|> Result.withDefault
|
||||||
|
{ pageData = pageData
|
||||||
|
, sharedData = sharedData
|
||||||
|
, userModel = userModel
|
||||||
}
|
}
|
||||||
, Cmd.none
|
|> Ok
|
||||||
|
}
|
||||||
|
, userCmd |> Cmd.map UserMsg
|
||||||
)
|
)
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
( { model
|
( { model
|
||||||
| contentCache =
|
| contentCache =
|
||||||
ContentCache.init (Just ( urls, contentJson ))
|
ContentCache.init
|
||||||
|
(Just
|
||||||
|
( urls.currentUrl
|
||||||
|
|> config.urlToRoute
|
||||||
|
|> config.routeToPath
|
||||||
|
, contentJson
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
@ -609,18 +684,16 @@ application config =
|
|||||||
, subscriptions =
|
, subscriptions =
|
||||||
\model ->
|
\model ->
|
||||||
let
|
let
|
||||||
urls : { currentUrl : Url, baseUrl : Url }
|
urls : { currentUrl : Url }
|
||||||
urls =
|
urls =
|
||||||
{ currentUrl = model.url, baseUrl = model.baseUrl }
|
{ currentUrl = model.url }
|
||||||
|
|
||||||
pagePath : Path
|
|
||||||
pagePath =
|
|
||||||
urlsToPagePath urls
|
|
||||||
in
|
in
|
||||||
case model.pageData of
|
case model.pageData of
|
||||||
Ok pageData ->
|
Ok pageData ->
|
||||||
Sub.batch
|
Sub.batch
|
||||||
[ config.subscriptions (model.url |> config.urlToRoute) pagePath pageData.userModel
|
[ config.subscriptions (model.url |> config.urlToRoute)
|
||||||
|
(urls.currentUrl |> config.urlToRoute |> config.routeToPath |> Path.join)
|
||||||
|
pageData.userModel
|
||||||
|> Sub.map UserMsg
|
|> Sub.map UserMsg
|
||||||
, config.fromJsPort
|
, config.fromJsPort
|
||||||
|> Sub.map
|
|> Sub.map
|
||||||
@ -650,3 +723,8 @@ application config =
|
|||||||
, onUrlChange = UrlChanged
|
, onUrlChange = UrlChanged
|
||||||
, onUrlRequest = LinkClicked
|
, onUrlRequest = LinkClicked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
urlPathToPath : ProgramConfig userMsg userModel route siteData pageData sharedData -> Url -> Path
|
||||||
|
urlPathToPath config urls =
|
||||||
|
urls.path |> Path.fromString
|
||||||
|
@ -28,11 +28,11 @@ import Pages.Flags
|
|||||||
import Pages.Http
|
import Pages.Http
|
||||||
import Pages.Internal.ApplicationType as ApplicationType
|
import Pages.Internal.ApplicationType as ApplicationType
|
||||||
import Pages.Internal.Platform.Effect as Effect exposing (Effect)
|
import Pages.Internal.Platform.Effect as Effect exposing (Effect)
|
||||||
import Pages.Internal.Platform.Mode as Mode exposing (Mode)
|
|
||||||
import Pages.Internal.Platform.StaticResponses as StaticResponses exposing (StaticResponses)
|
import Pages.Internal.Platform.StaticResponses as StaticResponses exposing (StaticResponses)
|
||||||
import Pages.Internal.Platform.ToJsPayload as ToJsPayload exposing (ToJsSuccessPayload)
|
import Pages.Internal.Platform.ToJsPayload as ToJsPayload
|
||||||
import Pages.Internal.StaticHttpBody as StaticHttpBody
|
import Pages.Internal.StaticHttpBody as StaticHttpBody
|
||||||
import Pages.ProgramConfig exposing (ProgramConfig)
|
import Pages.ProgramConfig exposing (ProgramConfig)
|
||||||
|
import Pages.StaticHttp.Request
|
||||||
import Pages.StaticHttpRequest as StaticHttpRequest
|
import Pages.StaticHttpRequest as StaticHttpRequest
|
||||||
import Path exposing (Path)
|
import Path exposing (Path)
|
||||||
import RenderRequest exposing (RenderRequest)
|
import RenderRequest exposing (RenderRequest)
|
||||||
@ -51,7 +51,6 @@ type alias Model route =
|
|||||||
, secrets : SecretsDict
|
, secrets : SecretsDict
|
||||||
, errors : List BuildError
|
, errors : List BuildError
|
||||||
, allRawResponses : Dict String (Maybe String)
|
, allRawResponses : Dict String (Maybe String)
|
||||||
, mode : Mode
|
|
||||||
, pendingRequests : List { masked : RequestDetails, unmasked : RequestDetails }
|
, pendingRequests : List { masked : RequestDetails, unmasked : RequestDetails }
|
||||||
, unprocessedPages : List ( Path, route )
|
, unprocessedPages : List ( Path, route )
|
||||||
, staticRoutes : Maybe (List ( Path, route ))
|
, staticRoutes : Maybe (List ( Path, route ))
|
||||||
@ -60,11 +59,13 @@ type alias Model route =
|
|||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= GotStaticHttpResponse { request : { masked : RequestDetails, unmasked : RequestDetails }, response : Result Pages.Http.Error String }
|
= GotDataBatch
|
||||||
| GotPortResponse ( String, Decode.Value )
|
(List
|
||||||
| GotStaticFile ( String, Decode.Value )
|
{ request : { masked : RequestDetails, unmasked : RequestDetails }
|
||||||
|
, response : String
|
||||||
|
}
|
||||||
|
)
|
||||||
| GotBuildError BuildError
|
| GotBuildError BuildError
|
||||||
| GotGlob ( String, Decode.Value )
|
|
||||||
| Continue
|
| Continue
|
||||||
|
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ cliApplication config =
|
|||||||
renderRequest : RenderRequest (Maybe route)
|
renderRequest : RenderRequest (Maybe route)
|
||||||
renderRequest =
|
renderRequest =
|
||||||
Decode.decodeValue (RenderRequest.decoder config) flags
|
Decode.decodeValue (RenderRequest.decoder config) flags
|
||||||
|> Result.withDefault RenderRequest.FullBuild
|
|> Result.withDefault RenderRequest.default
|
||||||
in
|
in
|
||||||
init renderRequest contentCache config flags
|
init renderRequest contentCache config flags
|
||||||
|> Tuple.mapSecond (perform renderRequest config config.toJsPort)
|
|> Tuple.mapSecond (perform renderRequest config config.toJsPort)
|
||||||
@ -112,36 +113,36 @@ cliApplication config =
|
|||||||
case tag of
|
case tag of
|
||||||
"BuildError" ->
|
"BuildError" ->
|
||||||
Decode.field "data"
|
Decode.field "data"
|
||||||
(Decode.field "filePath" Decode.string
|
(Decode.map2
|
||||||
|> Decode.map
|
(\message title ->
|
||||||
(\filePath ->
|
{ title = title
|
||||||
{ title = "File not found"
|
, message = message
|
||||||
, message =
|
|
||||||
[ Terminal.text "A DataSource.File read failed because I couldn't find this file: "
|
|
||||||
, Terminal.yellow <| Terminal.text filePath
|
|
||||||
]
|
|
||||||
, fatal = True
|
, fatal = True
|
||||||
, path = "" -- TODO wire in current path here
|
, path = "" -- TODO wire in current path here
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
(Decode.field "message" Decode.string |> Decode.map Terminal.fromAnsiString)
|
||||||
|
(Decode.field "title" Decode.string)
|
||||||
)
|
)
|
||||||
|> Decode.map GotBuildError
|
|> Decode.map GotBuildError
|
||||||
|
|
||||||
"GotFile" ->
|
"GotBatch" ->
|
||||||
gotStaticFileDecoder
|
|
||||||
|> Decode.map GotStaticFile
|
|
||||||
|
|
||||||
"GotPort" ->
|
|
||||||
gotPortDecoder
|
|
||||||
|> Decode.map GotPortResponse
|
|
||||||
|
|
||||||
"GotGlob" ->
|
|
||||||
Decode.field "data"
|
Decode.field "data"
|
||||||
(Decode.map2 Tuple.pair
|
(Decode.list
|
||||||
(Decode.field "pattern" Decode.string)
|
(Decode.map2
|
||||||
(Decode.field "result" Decode.value)
|
(\requests response ->
|
||||||
|
{ request =
|
||||||
|
{ masked = requests.masked
|
||||||
|
, unmasked = requests.unmasked
|
||||||
|
}
|
||||||
|
, response = response
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|> Decode.map GotGlob
|
(Decode.field "request" requestDecoder)
|
||||||
|
(Decode.field "response" Decode.string)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|> Decode.map GotDataBatch
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Decode.fail "Unhandled msg"
|
Decode.fail "Unhandled msg"
|
||||||
@ -154,6 +155,16 @@ cliApplication config =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
requestDecoder : Decode.Decoder { masked : Pages.StaticHttp.Request.Request, unmasked : Pages.StaticHttp.Request.Request }
|
||||||
|
requestDecoder =
|
||||||
|
(Codec.object (\masked unmasked -> { masked = masked, unmasked = unmasked })
|
||||||
|
|> Codec.field "masked" .masked Pages.StaticHttp.Request.codec
|
||||||
|
|> Codec.field "unmasked" .unmasked Pages.StaticHttp.Request.codec
|
||||||
|
|> Codec.buildObject
|
||||||
|
)
|
||||||
|
|> Codec.decoder
|
||||||
|
|
||||||
|
|
||||||
gotStaticFileDecoder : Decode.Decoder ( String, Decode.Value )
|
gotStaticFileDecoder : Decode.Decoder ( String, Decode.Value )
|
||||||
gotStaticFileDecoder =
|
gotStaticFileDecoder =
|
||||||
Decode.field "data"
|
Decode.field "data"
|
||||||
@ -189,12 +200,6 @@ perform renderRequest config toJsPort effect =
|
|||||||
Effect.NoEffect ->
|
Effect.NoEffect ->
|
||||||
Cmd.none
|
Cmd.none
|
||||||
|
|
||||||
Effect.SendJsData value ->
|
|
||||||
value
|
|
||||||
|> Codec.encoder ToJsPayload.toJsCodec
|
|
||||||
|> toJsPort
|
|
||||||
|> Cmd.map never
|
|
||||||
|
|
||||||
Effect.Batch list ->
|
Effect.Batch list ->
|
||||||
list
|
list
|
||||||
|> List.map (perform renderRequest config toJsPort)
|
|> List.map (perform renderRequest config toJsPort)
|
||||||
@ -202,17 +207,53 @@ perform renderRequest config toJsPort effect =
|
|||||||
|
|
||||||
Effect.FetchHttp ({ unmasked, masked } as requests) ->
|
Effect.FetchHttp ({ unmasked, masked } as requests) ->
|
||||||
if unmasked.url == "$$elm-pages$$headers" then
|
if unmasked.url == "$$elm-pages$$headers" then
|
||||||
Cmd.batch
|
case
|
||||||
[ Task.succeed
|
|
||||||
{ request = requests
|
|
||||||
, response =
|
|
||||||
renderRequest
|
renderRequest
|
||||||
|> RenderRequest.maybeRequestPayload
|
|> RenderRequest.maybeRequestPayload
|
||||||
|> Maybe.map (Json.Encode.encode 0)
|
|> Maybe.map (Json.Encode.encode 0)
|
||||||
|> Result.fromMaybe (Pages.Http.BadUrl "$$elm-pages$$headers is only available on server-side request (not on build).")
|
|> Result.fromMaybe (Pages.Http.BadUrl "$$elm-pages$$headers is only available on server-side request (not on build).")
|
||||||
|
of
|
||||||
|
Ok okResponse ->
|
||||||
|
Task.succeed
|
||||||
|
[ { request = requests
|
||||||
|
, response = okResponse
|
||||||
}
|
}
|
||||||
|> Task.perform GotStaticHttpResponse
|
|
||||||
]
|
]
|
||||||
|
|> Task.perform GotDataBatch
|
||||||
|
|
||||||
|
Err error ->
|
||||||
|
{ title = "Static HTTP Error"
|
||||||
|
, message =
|
||||||
|
[ Terminal.text "I got an error making an HTTP request to this URL: "
|
||||||
|
|
||||||
|
-- TODO include HTTP method, headers, and body
|
||||||
|
, Terminal.yellow requests.masked.url
|
||||||
|
, Terminal.text <| Json.Encode.encode 2 <| StaticHttpBody.encode requests.masked.body
|
||||||
|
, Terminal.text "\n\n"
|
||||||
|
, case error of
|
||||||
|
Pages.Http.BadStatus metadata body ->
|
||||||
|
Terminal.text <|
|
||||||
|
String.join "\n"
|
||||||
|
[ "Bad status: " ++ String.fromInt metadata.statusCode
|
||||||
|
, "Status message: " ++ metadata.statusText
|
||||||
|
, "Body: " ++ body
|
||||||
|
]
|
||||||
|
|
||||||
|
Pages.Http.BadUrl _ ->
|
||||||
|
-- TODO include HTTP method, headers, and body
|
||||||
|
Terminal.text <| "Invalid url: " ++ requests.masked.url
|
||||||
|
|
||||||
|
Pages.Http.Timeout ->
|
||||||
|
Terminal.text "Timeout"
|
||||||
|
|
||||||
|
Pages.Http.NetworkError ->
|
||||||
|
Terminal.text "Network error"
|
||||||
|
]
|
||||||
|
, fatal = True
|
||||||
|
, path = "" -- TODO wire in current path here
|
||||||
|
}
|
||||||
|
|> Task.succeed
|
||||||
|
|> Task.perform GotBuildError
|
||||||
|
|
||||||
else if unmasked.url |> String.startsWith "file://" then
|
else if unmasked.url |> String.startsWith "file://" then
|
||||||
let
|
let
|
||||||
@ -236,53 +277,12 @@ perform renderRequest config toJsPort effect =
|
|||||||
|> toJsPort
|
|> toJsPort
|
||||||
|> Cmd.map never
|
|> Cmd.map never
|
||||||
|
|
||||||
else if unmasked.url |> String.startsWith "port://" then
|
else
|
||||||
let
|
ToJsPayload.DoHttp { masked = masked, unmasked = unmasked }
|
||||||
portName : String
|
|
||||||
portName =
|
|
||||||
String.dropLeft 7 unmasked.url
|
|
||||||
in
|
|
||||||
ToJsPayload.Port portName
|
|
||||||
|> Codec.encoder (ToJsPayload.successCodecNew2 canonicalSiteUrl "")
|
|> Codec.encoder (ToJsPayload.successCodecNew2 canonicalSiteUrl "")
|
||||||
|> toJsPort
|
|> toJsPort
|
||||||
|> Cmd.map never
|
|> Cmd.map never
|
||||||
|
|
||||||
else
|
|
||||||
Cmd.batch
|
|
||||||
[ Http.request
|
|
||||||
{ method = unmasked.method
|
|
||||||
, url = unmasked.url
|
|
||||||
, headers = unmasked.headers |> List.map (\( key, value ) -> Http.header key value)
|
|
||||||
, body =
|
|
||||||
case unmasked.body of
|
|
||||||
StaticHttpBody.EmptyBody ->
|
|
||||||
Http.emptyBody
|
|
||||||
|
|
||||||
StaticHttpBody.StringBody contentType string ->
|
|
||||||
Http.stringBody contentType string
|
|
||||||
|
|
||||||
StaticHttpBody.JsonBody value ->
|
|
||||||
Http.jsonBody value
|
|
||||||
, expect =
|
|
||||||
Pages.Http.expectString
|
|
||||||
(\response ->
|
|
||||||
GotStaticHttpResponse
|
|
||||||
{ request = requests
|
|
||||||
, response = response
|
|
||||||
}
|
|
||||||
)
|
|
||||||
, timeout = Nothing
|
|
||||||
, tracker = Nothing
|
|
||||||
}
|
|
||||||
, toJsPort
|
|
||||||
(Json.Encode.object
|
|
||||||
[ ( "command", Json.Encode.string "log" )
|
|
||||||
, ( "value", Json.Encode.string ("Fetching " ++ masked.url) )
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|> Cmd.map never
|
|
||||||
]
|
|
||||||
|
|
||||||
Effect.SendSinglePage done info ->
|
Effect.SendSinglePage done info ->
|
||||||
let
|
let
|
||||||
currentPagePath : String
|
currentPagePath : String
|
||||||
@ -326,19 +326,16 @@ perform renderRequest config toJsPort effect =
|
|||||||
flagsDecoder :
|
flagsDecoder :
|
||||||
Decode.Decoder
|
Decode.Decoder
|
||||||
{ secrets : SecretsDict
|
{ secrets : SecretsDict
|
||||||
, mode : Mode
|
|
||||||
, staticHttpCache : Dict String (Maybe String)
|
, staticHttpCache : Dict String (Maybe String)
|
||||||
}
|
}
|
||||||
flagsDecoder =
|
flagsDecoder =
|
||||||
Decode.map3
|
Decode.map2
|
||||||
(\secrets mode staticHttpCache ->
|
(\secrets staticHttpCache ->
|
||||||
{ secrets = secrets
|
{ secrets = secrets
|
||||||
, mode = mode
|
|
||||||
, staticHttpCache = staticHttpCache
|
, staticHttpCache = staticHttpCache
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
(Decode.field "secrets" SecretsDict.decoder)
|
(Decode.field "secrets" SecretsDict.decoder)
|
||||||
(Decode.field "mode" Mode.modeDecoder)
|
|
||||||
(Decode.field "staticHttpCache"
|
(Decode.field "staticHttpCache"
|
||||||
(Decode.dict
|
(Decode.dict
|
||||||
(Decode.string
|
(Decode.string
|
||||||
@ -356,8 +353,8 @@ init :
|
|||||||
-> ( Model route, Effect )
|
-> ( Model route, Effect )
|
||||||
init renderRequest contentCache config flags =
|
init renderRequest contentCache config flags =
|
||||||
case Decode.decodeValue flagsDecoder flags of
|
case Decode.decodeValue flagsDecoder flags of
|
||||||
Ok { secrets, mode, staticHttpCache } ->
|
Ok { secrets, staticHttpCache } ->
|
||||||
initLegacy renderRequest { secrets = secrets, mode = mode, staticHttpCache = staticHttpCache } contentCache config flags
|
initLegacy renderRequest { secrets = secrets, staticHttpCache = staticHttpCache } contentCache config flags
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
updateAndSendPortIfDone
|
updateAndSendPortIfDone
|
||||||
@ -373,7 +370,6 @@ init renderRequest contentCache config flags =
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
, allRawResponses = Dict.empty
|
, allRawResponses = Dict.empty
|
||||||
, mode = Mode.Dev
|
|
||||||
, pendingRequests = []
|
, pendingRequests = []
|
||||||
, unprocessedPages = []
|
, unprocessedPages = []
|
||||||
, staticRoutes = Just []
|
, staticRoutes = Just []
|
||||||
@ -383,12 +379,12 @@ init renderRequest contentCache config flags =
|
|||||||
|
|
||||||
initLegacy :
|
initLegacy :
|
||||||
RenderRequest route
|
RenderRequest route
|
||||||
-> { a | secrets : SecretsDict, mode : Mode, staticHttpCache : Dict String (Maybe String) }
|
-> { a | secrets : SecretsDict, staticHttpCache : Dict String (Maybe String) }
|
||||||
-> ContentCache
|
-> ContentCache
|
||||||
-> ProgramConfig userMsg userModel route siteData pageData sharedData
|
-> ProgramConfig userMsg userModel route siteData pageData sharedData
|
||||||
-> Decode.Value
|
-> Decode.Value
|
||||||
-> ( Model route, Effect )
|
-> ( Model route, Effect )
|
||||||
initLegacy renderRequest { secrets, mode, staticHttpCache } contentCache config flags =
|
initLegacy renderRequest { secrets, staticHttpCache } contentCache config flags =
|
||||||
let
|
let
|
||||||
staticResponses : StaticResponses
|
staticResponses : StaticResponses
|
||||||
staticResponses =
|
staticResponses =
|
||||||
@ -412,9 +408,6 @@ initLegacy renderRequest { secrets, mode, staticHttpCache } contentCache config
|
|||||||
StaticResponses.renderApiRequest
|
StaticResponses.renderApiRequest
|
||||||
(DataSource.succeed [])
|
(DataSource.succeed [])
|
||||||
|
|
||||||
RenderRequest.FullBuild ->
|
|
||||||
StaticResponses.init config
|
|
||||||
|
|
||||||
unprocessedPages : List ( Path, route )
|
unprocessedPages : List ( Path, route )
|
||||||
unprocessedPages =
|
unprocessedPages =
|
||||||
case renderRequest of
|
case renderRequest of
|
||||||
@ -429,9 +422,6 @@ initLegacy renderRequest { secrets, mode, staticHttpCache } contentCache config
|
|||||||
RenderRequest.NotFound path ->
|
RenderRequest.NotFound path ->
|
||||||
[]
|
[]
|
||||||
|
|
||||||
RenderRequest.FullBuild ->
|
|
||||||
[]
|
|
||||||
|
|
||||||
unprocessedPagesState : Maybe (List ( Path, route ))
|
unprocessedPagesState : Maybe (List ( Path, route ))
|
||||||
unprocessedPagesState =
|
unprocessedPagesState =
|
||||||
case renderRequest of
|
case renderRequest of
|
||||||
@ -446,16 +436,12 @@ initLegacy renderRequest { secrets, mode, staticHttpCache } contentCache config
|
|||||||
RenderRequest.NotFound path ->
|
RenderRequest.NotFound path ->
|
||||||
Just []
|
Just []
|
||||||
|
|
||||||
RenderRequest.FullBuild ->
|
|
||||||
Nothing
|
|
||||||
|
|
||||||
initialModel : Model route
|
initialModel : Model route
|
||||||
initialModel =
|
initialModel =
|
||||||
{ staticResponses = staticResponses
|
{ staticResponses = staticResponses
|
||||||
, secrets = secrets
|
, secrets = secrets
|
||||||
, errors = []
|
, errors = []
|
||||||
, allRawResponses = staticHttpCache
|
, allRawResponses = staticHttpCache
|
||||||
, mode = mode
|
|
||||||
, pendingRequests = []
|
, pendingRequests = []
|
||||||
, unprocessedPages = unprocessedPages
|
, unprocessedPages = unprocessedPages
|
||||||
, staticRoutes = unprocessedPagesState
|
, staticRoutes = unprocessedPagesState
|
||||||
@ -489,103 +475,26 @@ update :
|
|||||||
-> ( Model route, Effect )
|
-> ( Model route, Effect )
|
||||||
update contentCache config msg model =
|
update contentCache config msg model =
|
||||||
case msg of
|
case msg of
|
||||||
GotStaticHttpResponse { request, response } ->
|
GotDataBatch batch ->
|
||||||
let
|
let
|
||||||
updatedModel : Model route
|
|
||||||
updatedModel =
|
|
||||||
(case response of
|
|
||||||
Ok _ ->
|
|
||||||
{ model
|
|
||||||
| pendingRequests =
|
|
||||||
model.pendingRequests
|
|
||||||
|> List.filter (\pending -> pending /= request)
|
|
||||||
}
|
|
||||||
|
|
||||||
Err error ->
|
|
||||||
{ model
|
|
||||||
| errors =
|
|
||||||
List.append
|
|
||||||
model.errors
|
|
||||||
[ { title = "Static HTTP Error"
|
|
||||||
, message =
|
|
||||||
[ Terminal.text "I got an error making an HTTP request to this URL: "
|
|
||||||
|
|
||||||
-- TODO include HTTP method, headers, and body
|
|
||||||
, Terminal.yellow <| Terminal.text request.masked.url
|
|
||||||
, Terminal.text <| Json.Encode.encode 2 <| StaticHttpBody.encode request.masked.body
|
|
||||||
, Terminal.text "\n\n"
|
|
||||||
, case error of
|
|
||||||
Pages.Http.BadStatus metadata body ->
|
|
||||||
Terminal.text <|
|
|
||||||
String.join "\n"
|
|
||||||
[ "Bad status: " ++ String.fromInt metadata.statusCode
|
|
||||||
, "Status message: " ++ metadata.statusText
|
|
||||||
, "Body: " ++ body
|
|
||||||
]
|
|
||||||
|
|
||||||
Pages.Http.BadUrl _ ->
|
|
||||||
-- TODO include HTTP method, headers, and body
|
|
||||||
Terminal.text <| "Invalid url: " ++ request.masked.url
|
|
||||||
|
|
||||||
Pages.Http.Timeout ->
|
|
||||||
Terminal.text "Timeout"
|
|
||||||
|
|
||||||
Pages.Http.NetworkError ->
|
|
||||||
Terminal.text "Network error"
|
|
||||||
]
|
|
||||||
, fatal = True
|
|
||||||
, path = "" -- TODO wire in current path here
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|> StaticResponses.update
|
|
||||||
-- TODO for hash pass in RequestDetails here
|
|
||||||
{ request = request
|
|
||||||
, response = Result.mapError (\_ -> ()) response
|
|
||||||
}
|
|
||||||
in
|
|
||||||
StaticResponses.nextStep config
|
|
||||||
updatedModel
|
|
||||||
Nothing
|
|
||||||
|> nextStepToEffect contentCache config updatedModel
|
|
||||||
|
|
||||||
GotStaticFile ( filePath, fileContent ) ->
|
|
||||||
let
|
|
||||||
--_ =
|
|
||||||
-- Debug.log "GotStaticFile"
|
|
||||||
-- { filePath = filePath
|
|
||||||
-- , pendingRequests = model.pendingRequests
|
|
||||||
-- }
|
|
||||||
updatedModel : Model route
|
|
||||||
updatedModel =
|
updatedModel =
|
||||||
|
(case batch of
|
||||||
|
[ single ] ->
|
||||||
{ model
|
{ model
|
||||||
| pendingRequests =
|
| pendingRequests =
|
||||||
model.pendingRequests
|
model.pendingRequests
|
||||||
|> List.filter
|
|> List.filter
|
||||||
(\pending ->
|
(\pending ->
|
||||||
pending.unmasked.url
|
pending /= single.request
|
||||||
== ("file://" ++ filePath)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|> StaticResponses.update
|
|
||||||
-- TODO for hash pass in RequestDetails here
|
_ ->
|
||||||
{ request =
|
{ model
|
||||||
{ masked =
|
| pendingRequests = [] -- TODO is it safe to clear it entirely?
|
||||||
{ url = "file://" ++ filePath
|
|
||||||
, method = "GET"
|
|
||||||
, headers = []
|
|
||||||
, body = StaticHttpBody.EmptyBody
|
|
||||||
}
|
|
||||||
, unmasked =
|
|
||||||
{ url = "file://" ++ filePath
|
|
||||||
, method = "GET"
|
|
||||||
, headers = []
|
|
||||||
, body = StaticHttpBody.EmptyBody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, response = Ok (Json.Encode.encode 0 fileContent)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|> StaticResponses.batchUpdate batch
|
||||||
in
|
in
|
||||||
StaticResponses.nextStep config
|
StaticResponses.nextStep config
|
||||||
updatedModel
|
updatedModel
|
||||||
@ -603,40 +512,6 @@ update contentCache config msg model =
|
|||||||
Nothing
|
Nothing
|
||||||
|> nextStepToEffect contentCache config updatedModel
|
|> nextStepToEffect contentCache config updatedModel
|
||||||
|
|
||||||
GotGlob ( globPattern, globResult ) ->
|
|
||||||
let
|
|
||||||
updatedModel : Model route
|
|
||||||
updatedModel =
|
|
||||||
{ model
|
|
||||||
| pendingRequests =
|
|
||||||
model.pendingRequests
|
|
||||||
|> List.filter
|
|
||||||
(\pending -> pending.unmasked.url == ("glob://" ++ globPattern))
|
|
||||||
}
|
|
||||||
|> StaticResponses.update
|
|
||||||
-- TODO for hash pass in RequestDetails here
|
|
||||||
{ request =
|
|
||||||
{ masked =
|
|
||||||
{ url = "glob://" ++ globPattern
|
|
||||||
, method = "GET"
|
|
||||||
, headers = []
|
|
||||||
, body = StaticHttpBody.EmptyBody
|
|
||||||
}
|
|
||||||
, unmasked =
|
|
||||||
{ url = "glob://" ++ globPattern
|
|
||||||
, method = "GET"
|
|
||||||
, headers = []
|
|
||||||
, body = StaticHttpBody.EmptyBody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, response = Ok (Json.Encode.encode 0 globResult)
|
|
||||||
}
|
|
||||||
in
|
|
||||||
StaticResponses.nextStep config
|
|
||||||
updatedModel
|
|
||||||
Nothing
|
|
||||||
|> nextStepToEffect contentCache config updatedModel
|
|
||||||
|
|
||||||
GotBuildError buildError ->
|
GotBuildError buildError ->
|
||||||
let
|
let
|
||||||
updatedModel : Model route
|
updatedModel : Model route
|
||||||
@ -651,40 +526,6 @@ update contentCache config msg model =
|
|||||||
Nothing
|
Nothing
|
||||||
|> nextStepToEffect contentCache config updatedModel
|
|> nextStepToEffect contentCache config updatedModel
|
||||||
|
|
||||||
GotPortResponse ( portName, portResponse ) ->
|
|
||||||
let
|
|
||||||
updatedModel : Model route
|
|
||||||
updatedModel =
|
|
||||||
{ model
|
|
||||||
| pendingRequests =
|
|
||||||
model.pendingRequests
|
|
||||||
|> List.filter
|
|
||||||
(\pending -> pending.unmasked.url == ("port://" ++ portName))
|
|
||||||
}
|
|
||||||
|> StaticResponses.update
|
|
||||||
-- TODO for hash pass in RequestDetails here
|
|
||||||
{ request =
|
|
||||||
{ masked =
|
|
||||||
{ url = "port://" ++ portName
|
|
||||||
, method = "GET"
|
|
||||||
, headers = []
|
|
||||||
, body = StaticHttpBody.EmptyBody
|
|
||||||
}
|
|
||||||
, unmasked =
|
|
||||||
{ url = "port://" ++ portName
|
|
||||||
, method = "GET"
|
|
||||||
, headers = []
|
|
||||||
, body = StaticHttpBody.EmptyBody
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, response = Ok (Json.Encode.encode 0 portResponse)
|
|
||||||
}
|
|
||||||
in
|
|
||||||
StaticResponses.nextStep config
|
|
||||||
updatedModel
|
|
||||||
Nothing
|
|
||||||
|> nextStepToEffect contentCache config updatedModel
|
|
||||||
|
|
||||||
|
|
||||||
nextStepToEffect :
|
nextStepToEffect :
|
||||||
ContentCache
|
ContentCache
|
||||||
@ -767,39 +608,8 @@ nextStepToEffect contentCache config model ( updatedStaticResponsesModel, nextSt
|
|||||||
)
|
)
|
||||||
|
|
||||||
StaticResponses.Finish toJsPayload ->
|
StaticResponses.Finish toJsPayload ->
|
||||||
case model.mode of
|
|
||||||
Mode.ElmToHtmlBeta ->
|
|
||||||
let
|
|
||||||
sendManifestIfNeeded : Effect
|
|
||||||
sendManifestIfNeeded =
|
|
||||||
if
|
|
||||||
List.length model.unprocessedPages
|
|
||||||
== (model.staticRoutes
|
|
||||||
|> Maybe.map List.length
|
|
||||||
|> Maybe.withDefault -1
|
|
||||||
)
|
|
||||||
&& model.maybeRequestJson
|
|
||||||
== RenderRequest.FullBuild
|
|
||||||
then
|
|
||||||
case toJsPayload of
|
case toJsPayload of
|
||||||
ToJsPayload.Success value ->
|
StaticResponses.ApiResponse ->
|
||||||
Effect.SendSinglePage True
|
|
||||||
(ToJsPayload.InitialData
|
|
||||||
{ filesToGenerate = value.filesToGenerate
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
ToJsPayload.Errors _ ->
|
|
||||||
Effect.SendJsData toJsPayload
|
|
||||||
|
|
||||||
ToJsPayload.ApiResponse ->
|
|
||||||
Effect.NoEffect
|
|
||||||
|
|
||||||
else
|
|
||||||
Effect.NoEffect
|
|
||||||
in
|
|
||||||
case toJsPayload of
|
|
||||||
ToJsPayload.ApiResponse ->
|
|
||||||
let
|
let
|
||||||
apiResponse : Effect
|
apiResponse : Effect
|
||||||
apiResponse =
|
apiResponse =
|
||||||
@ -837,7 +647,7 @@ nextStepToEffect contentCache config model ( updatedStaticResponsesModel, nextSt
|
|||||||
Err error ->
|
Err error ->
|
||||||
[ error ]
|
[ error ]
|
||||||
|> ToJsPayload.Errors
|
|> ToJsPayload.Errors
|
||||||
|> Effect.SendJsData
|
|> Effect.SendSinglePage True
|
||||||
)
|
)
|
||||||
|
|
||||||
RenderRequest.Page payload ->
|
RenderRequest.Page payload ->
|
||||||
@ -894,7 +704,7 @@ nextStepToEffect contentCache config model ( updatedStaticResponsesModel, nextSt
|
|||||||
, query = Nothing
|
, query = Nothing
|
||||||
, fragment = Nothing
|
, fragment = Nothing
|
||||||
}
|
}
|
||||||
, metadata = currentPage.frontmatter
|
, metadata = currentPage.route
|
||||||
, pageUrl = Nothing
|
, pageUrl = Nothing
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -917,9 +727,9 @@ nextStepToEffect contentCache config model ( updatedStaticResponsesModel, nextSt
|
|||||||
-- |> Maybe.withDefault Dict.empty
|
-- |> Maybe.withDefault Dict.empty
|
||||||
Dict.empty
|
Dict.empty
|
||||||
|
|
||||||
currentPage : { path : Path, frontmatter : route }
|
currentPage : { path : Path, route : route }
|
||||||
currentPage =
|
currentPage =
|
||||||
{ path = payload.path, frontmatter = config.urlToRoute currentUrl }
|
{ path = payload.path, route = config.urlToRoute currentUrl }
|
||||||
|
|
||||||
pageDataResult : Result BuildError pageData
|
pageDataResult : Result BuildError pageData
|
||||||
pageDataResult =
|
pageDataResult =
|
||||||
@ -961,61 +771,46 @@ nextStepToEffect contentCache config model ( updatedStaticResponsesModel, nextSt
|
|||||||
|> Effect.SendSinglePage False
|
|> Effect.SendSinglePage False
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
[ error ] |> ToJsPayload.Errors |> Effect.SendJsData
|
[ error ] |> ToJsPayload.Errors |> Effect.SendSinglePage True
|
||||||
|
|
||||||
Ok (Just notFoundReason) ->
|
Ok (Just notFoundReason) ->
|
||||||
render404Page config model payload.path notFoundReason
|
render404Page config model payload.path notFoundReason
|
||||||
|
|
||||||
Err error ->
|
Err error ->
|
||||||
[] |> ToJsPayload.Errors |> Effect.SendJsData
|
[ error ] |> ToJsPayload.Errors |> Effect.SendSinglePage True
|
||||||
|
|
||||||
RenderRequest.NotFound path ->
|
RenderRequest.NotFound path ->
|
||||||
render404Page config model path NotFoundReason.NoMatchingRoute
|
render404Page config model path NotFoundReason.NoMatchingRoute
|
||||||
|
|
||||||
RenderRequest.FullBuild ->
|
|
||||||
[] |> ToJsPayload.Errors |> Effect.SendJsData
|
|
||||||
in
|
in
|
||||||
( { model | staticRoutes = Just [] }
|
( { model | staticRoutes = Just [] }
|
||||||
, apiResponse
|
, apiResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
_ ->
|
StaticResponses.Page contentJson ->
|
||||||
model.unprocessedPages
|
case model.unprocessedPages |> List.head of
|
||||||
|> List.take 1
|
Just pageAndMetadata ->
|
||||||
|> List.filterMap
|
|
||||||
(\pageAndMetadata ->
|
|
||||||
case toJsPayload of
|
|
||||||
ToJsPayload.Success value ->
|
|
||||||
sendSinglePageProgress value config model pageAndMetadata
|
|
||||||
|> Just
|
|
||||||
|
|
||||||
ToJsPayload.Errors errors ->
|
|
||||||
errors |> ToJsPayload.Errors |> Effect.SendJsData |> Just
|
|
||||||
|
|
||||||
ToJsPayload.ApiResponse ->
|
|
||||||
Nothing
|
|
||||||
)
|
|
||||||
|> (\cmds ->
|
|
||||||
( model
|
( model
|
||||||
|> popProcessedRequest
|
, sendSinglePageProgress contentJson config model pageAndMetadata
|
||||||
, Effect.Batch
|
|
||||||
(sendManifestIfNeeded
|
|
||||||
:: cmds
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_ ->
|
Nothing ->
|
||||||
( model, Effect.SendJsData toJsPayload )
|
( model
|
||||||
|
, [] |> ToJsPayload.Errors |> Effect.SendSinglePage True
|
||||||
|
)
|
||||||
|
|
||||||
|
StaticResponses.Errors errors ->
|
||||||
|
( model
|
||||||
|
, errors |> ToJsPayload.Errors |> Effect.SendSinglePage True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
sendSinglePageProgress :
|
sendSinglePageProgress :
|
||||||
ToJsSuccessPayload
|
Dict String String
|
||||||
-> ProgramConfig userMsg userModel route siteData pageData sharedData
|
-> ProgramConfig userMsg userModel route siteData pageData sharedData
|
||||||
-> Model route
|
-> Model route
|
||||||
-> ( Path, route )
|
-> ( Path, route )
|
||||||
-> Effect
|
-> Effect
|
||||||
sendSinglePageProgress toJsPayload config model =
|
sendSinglePageProgress contentJson config model =
|
||||||
\( page, route ) ->
|
\( page, route ) ->
|
||||||
case model.maybeRequestJson of
|
case model.maybeRequestJson of
|
||||||
RenderRequest.SinglePage includeHtml _ _ ->
|
RenderRequest.SinglePage includeHtml _ _ ->
|
||||||
@ -1059,7 +854,7 @@ sendSinglePageProgress toJsPayload config model =
|
|||||||
, query = Nothing
|
, query = Nothing
|
||||||
, fragment = Nothing
|
, fragment = Nothing
|
||||||
}
|
}
|
||||||
, metadata = currentPage.frontmatter
|
, metadata = currentPage.route
|
||||||
, pageUrl = Nothing
|
, pageUrl = Nothing
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1085,35 +880,29 @@ sendSinglePageProgress toJsPayload config model =
|
|||||||
, fragment = Nothing
|
, fragment = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
staticData : Dict String String
|
currentPage : { path : Path, route : route }
|
||||||
staticData =
|
|
||||||
toJsPayload.pages
|
|
||||||
|> Dict.get (Path.toRelative page)
|
|
||||||
|> Maybe.withDefault Dict.empty
|
|
||||||
|
|
||||||
currentPage : { path : Path, frontmatter : route }
|
|
||||||
currentPage =
|
currentPage =
|
||||||
{ path = page, frontmatter = config.urlToRoute currentUrl }
|
{ path = page, route = config.urlToRoute currentUrl }
|
||||||
|
|
||||||
pageDataResult : Result BuildError pageData
|
pageDataResult : Result BuildError pageData
|
||||||
pageDataResult =
|
pageDataResult =
|
||||||
StaticHttpRequest.resolve ApplicationType.Browser
|
StaticHttpRequest.resolve ApplicationType.Browser
|
||||||
(config.data (config.urlToRoute currentUrl))
|
(config.data (config.urlToRoute currentUrl))
|
||||||
(staticData |> Dict.map (\_ v -> Just v))
|
(contentJson |> Dict.map (\_ v -> Just v))
|
||||||
|> Result.mapError (StaticHttpRequest.toBuildError currentUrl.path)
|
|> Result.mapError (StaticHttpRequest.toBuildError currentUrl.path)
|
||||||
|
|
||||||
sharedDataResult : Result BuildError sharedData
|
sharedDataResult : Result BuildError sharedData
|
||||||
sharedDataResult =
|
sharedDataResult =
|
||||||
StaticHttpRequest.resolve ApplicationType.Browser
|
StaticHttpRequest.resolve ApplicationType.Browser
|
||||||
config.sharedData
|
config.sharedData
|
||||||
(staticData |> Dict.map (\_ v -> Just v))
|
(contentJson |> Dict.map (\_ v -> Just v))
|
||||||
|> Result.mapError (StaticHttpRequest.toBuildError currentUrl.path)
|
|> Result.mapError (StaticHttpRequest.toBuildError currentUrl.path)
|
||||||
|
|
||||||
siteDataResult : Result BuildError siteData
|
siteDataResult : Result BuildError siteData
|
||||||
siteDataResult =
|
siteDataResult =
|
||||||
StaticHttpRequest.resolve ApplicationType.Cli
|
StaticHttpRequest.resolve ApplicationType.Cli
|
||||||
(config.site allRoutes |> .data)
|
(config.site allRoutes |> .data)
|
||||||
(staticData |> Dict.map (\_ v -> Just v))
|
(contentJson |> Dict.map (\_ v -> Just v))
|
||||||
|> Result.mapError (StaticHttpRequest.toBuildError "Site.elm")
|
|> Result.mapError (StaticHttpRequest.toBuildError "Site.elm")
|
||||||
in
|
in
|
||||||
case Result.map3 (\a b c -> ( a, b, c )) pageFoundResult renderedResult siteDataResult of
|
case Result.map3 (\a b c -> ( a, b, c )) pageFoundResult renderedResult siteDataResult of
|
||||||
@ -1121,10 +910,7 @@ sendSinglePageProgress toJsPayload config model =
|
|||||||
case maybeNotFoundReason of
|
case maybeNotFoundReason of
|
||||||
Nothing ->
|
Nothing ->
|
||||||
{ route = page |> Path.toRelative
|
{ route = page |> Path.toRelative
|
||||||
, contentJson =
|
, contentJson = contentJson
|
||||||
toJsPayload.pages
|
|
||||||
|> Dict.get (Path.toRelative page)
|
|
||||||
|> Maybe.withDefault Dict.empty
|
|
||||||
, html = rendered.view
|
, html = rendered.view
|
||||||
, errors = []
|
, errors = []
|
||||||
, head = rendered.head ++ (config.site allRoutes |> .head) siteData
|
, head = rendered.head ++ (config.site allRoutes |> .head) siteData
|
||||||
@ -1132,7 +918,8 @@ sendSinglePageProgress toJsPayload config model =
|
|||||||
, staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v)
|
, staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v)
|
||||||
, is404 = False
|
, is404 = False
|
||||||
}
|
}
|
||||||
|> sendProgress
|
|> ToJsPayload.PageProgress
|
||||||
|
|> Effect.SendSinglePage True
|
||||||
|
|
||||||
Just notFoundReason ->
|
Just notFoundReason ->
|
||||||
render404Page config model page notFoundReason
|
render404Page config model page notFoundReason
|
||||||
@ -1140,115 +927,7 @@ sendSinglePageProgress toJsPayload config model =
|
|||||||
Err error ->
|
Err error ->
|
||||||
[ error ]
|
[ error ]
|
||||||
|> ToJsPayload.Errors
|
|> ToJsPayload.Errors
|
||||||
|> Effect.SendJsData
|
|> Effect.SendSinglePage True
|
||||||
|
|
||||||
RenderRequest.FullBuild ->
|
|
||||||
let
|
|
||||||
staticData : Dict String String
|
|
||||||
staticData =
|
|
||||||
toJsPayload.pages
|
|
||||||
|> Dict.get (Path.toRelative page)
|
|
||||||
|> Maybe.withDefault Dict.empty
|
|
||||||
|
|
||||||
currentPage : { path : Path, frontmatter : route }
|
|
||||||
currentPage =
|
|
||||||
{ path = page, frontmatter = config.urlToRoute currentUrl }
|
|
||||||
|
|
||||||
pageDataResult : Result BuildError pageData
|
|
||||||
pageDataResult =
|
|
||||||
StaticHttpRequest.resolve ApplicationType.Browser
|
|
||||||
(config.data (config.urlToRoute currentUrl))
|
|
||||||
(staticData |> Dict.map (\_ v -> Just v))
|
|
||||||
|> Result.mapError (StaticHttpRequest.toBuildError currentUrl.path)
|
|
||||||
|
|
||||||
sharedDataResult : Result BuildError sharedData
|
|
||||||
sharedDataResult =
|
|
||||||
StaticHttpRequest.resolve ApplicationType.Browser
|
|
||||||
config.sharedData
|
|
||||||
(staticData |> Dict.map (\_ v -> Just v))
|
|
||||||
|> Result.mapError (StaticHttpRequest.toBuildError currentUrl.path)
|
|
||||||
|
|
||||||
allRoutes : List route
|
|
||||||
allRoutes =
|
|
||||||
-- TODO
|
|
||||||
[]
|
|
||||||
|
|
||||||
currentUrl : { protocol : Url.Protocol, host : String, port_ : Maybe Int, path : String, query : Maybe String, fragment : Maybe String }
|
|
||||||
currentUrl =
|
|
||||||
{ protocol = Url.Https
|
|
||||||
, host = config.site allRoutes |> .canonicalUrl
|
|
||||||
, port_ = Nothing
|
|
||||||
, path = page |> Path.toRelative
|
|
||||||
, query = Nothing
|
|
||||||
, fragment = Nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
siteDataResult : Result BuildError siteData
|
|
||||||
siteDataResult =
|
|
||||||
StaticHttpRequest.resolve ApplicationType.Cli
|
|
||||||
(config.site allRoutes |> .data)
|
|
||||||
(staticData |> Dict.map (\_ v -> Just v))
|
|
||||||
|> Result.mapError (StaticHttpRequest.toBuildError "Site.elm")
|
|
||||||
in
|
|
||||||
case Result.map3 (\a b c -> ( a, b, c )) sharedDataResult pageDataResult siteDataResult of
|
|
||||||
Ok ( sharedData, pageData, siteData ) ->
|
|
||||||
let
|
|
||||||
pageModel : userModel
|
|
||||||
pageModel =
|
|
||||||
config.init
|
|
||||||
Pages.Flags.PreRenderFlags
|
|
||||||
sharedData
|
|
||||||
pageData
|
|
||||||
Nothing
|
|
||||||
(Just
|
|
||||||
{ path =
|
|
||||||
{ path = currentPage.path
|
|
||||||
, query = Nothing
|
|
||||||
, fragment = Nothing
|
|
||||||
}
|
|
||||||
, metadata = currentPage.frontmatter
|
|
||||||
, pageUrl = Nothing
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|> Tuple.first
|
|
||||||
|
|
||||||
viewValue : { title : String, body : Html userMsg }
|
|
||||||
viewValue =
|
|
||||||
(config.view currentPage Nothing sharedData pageData |> .view) pageModel
|
|
||||||
|
|
||||||
headTags : List Head.Tag
|
|
||||||
headTags =
|
|
||||||
(config.view currentPage Nothing sharedData pageData |> .head)
|
|
||||||
++ (siteData |> (config.site allRoutes |> .head))
|
|
||||||
in
|
|
||||||
{ route = page |> Path.toRelative
|
|
||||||
, contentJson =
|
|
||||||
toJsPayload.pages
|
|
||||||
|> Dict.get (Path.toRelative page)
|
|
||||||
|> Maybe.withDefault Dict.empty
|
|
||||||
, html = viewValue.body |> HtmlPrinter.htmlToString
|
|
||||||
, errors = []
|
|
||||||
, head = headTags
|
|
||||||
, title = viewValue.title
|
|
||||||
, staticHttpCache = model.allRawResponses |> Dict.Extra.filterMap (\_ v -> v)
|
|
||||||
, is404 = False
|
|
||||||
}
|
|
||||||
|> sendProgress
|
|
||||||
|
|
||||||
Err error ->
|
|
||||||
[ error ]
|
|
||||||
|> ToJsPayload.Errors
|
|
||||||
|> Effect.SendJsData
|
|
||||||
|
|
||||||
|
|
||||||
popProcessedRequest : Model route -> Model route
|
|
||||||
popProcessedRequest model =
|
|
||||||
{ model | unprocessedPages = List.drop 1 model.unprocessedPages }
|
|
||||||
|
|
||||||
|
|
||||||
sendProgress : ToJsPayload.ToJsSuccessPayloadNew -> Effect
|
|
||||||
sendProgress singlePage =
|
|
||||||
singlePage |> ToJsPayload.PageProgress |> Effect.SendSinglePage False
|
|
||||||
|
|
||||||
|
|
||||||
render404Page :
|
render404Page :
|
||||||
@ -1277,6 +956,7 @@ render404Page config model path notFoundReason =
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
, ( "path", Path.toAbsolute path )
|
||||||
]
|
]
|
||||||
|
|
||||||
-- TODO include the needed info for content.json?
|
-- TODO include the needed info for content.json?
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
module Pages.Internal.Platform.Effect exposing (Effect(..))
|
module Pages.Internal.Platform.Effect exposing (Effect(..))
|
||||||
|
|
||||||
import DataSource.Http exposing (RequestDetails)
|
import DataSource.Http exposing (RequestDetails)
|
||||||
import Pages.Internal.Platform.ToJsPayload exposing (ToJsPayload, ToJsSuccessPayloadNewCombined)
|
import Pages.Internal.Platform.ToJsPayload exposing (ToJsSuccessPayloadNewCombined)
|
||||||
|
|
||||||
|
|
||||||
type Effect
|
type Effect
|
||||||
= NoEffect
|
= NoEffect
|
||||||
| SendJsData ToJsPayload
|
|
||||||
| FetchHttp { masked : RequestDetails, unmasked : RequestDetails }
|
| FetchHttp { masked : RequestDetails, unmasked : RequestDetails }
|
||||||
| ReadFile String
|
| ReadFile String
|
||||||
| GetGlob String
|
| GetGlob String
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
module Pages.Internal.Platform.Mode exposing (Mode(..), modeDecoder)
|
|
||||||
|
|
||||||
import Json.Decode as Decode exposing (Decoder)
|
|
||||||
|
|
||||||
|
|
||||||
type Mode
|
|
||||||
= Prod
|
|
||||||
| Dev
|
|
||||||
| ElmToHtmlBeta
|
|
||||||
|
|
||||||
|
|
||||||
modeDecoder : Decoder Mode
|
|
||||||
modeDecoder =
|
|
||||||
Decode.string
|
|
||||||
|> Decode.andThen
|
|
||||||
(\mode ->
|
|
||||||
if mode == "prod" then
|
|
||||||
Decode.succeed Prod
|
|
||||||
|
|
||||||
else if mode == "elm-to-html-beta" then
|
|
||||||
Decode.succeed ElmToHtmlBeta
|
|
||||||
|
|
||||||
else
|
|
||||||
Decode.succeed Dev
|
|
||||||
)
|
|
@ -1,4 +1,4 @@
|
|||||||
module Pages.Internal.Platform.StaticResponses exposing (NextStep(..), StaticResponses, error, init, nextStep, renderApiRequest, renderSingleRoute, update)
|
module Pages.Internal.Platform.StaticResponses exposing (FinishKind(..), NextStep(..), StaticResponses, batchUpdate, error, nextStep, renderApiRequest, renderSingleRoute)
|
||||||
|
|
||||||
import ApiRoute
|
import ApiRoute
|
||||||
import BuildError exposing (BuildError)
|
import BuildError exposing (BuildError)
|
||||||
@ -11,8 +11,6 @@ import HtmlPrinter exposing (htmlToString)
|
|||||||
import Internal.ApiRoute exposing (Done(..))
|
import Internal.ApiRoute exposing (Done(..))
|
||||||
import NotFoundReason exposing (NotFoundReason)
|
import NotFoundReason exposing (NotFoundReason)
|
||||||
import Pages.Internal.ApplicationType as ApplicationType
|
import Pages.Internal.ApplicationType as ApplicationType
|
||||||
import Pages.Internal.Platform.Mode exposing (Mode)
|
|
||||||
import Pages.Internal.Platform.ToJsPayload as ToJsPayload exposing (ToJsPayload)
|
|
||||||
import Pages.SiteConfig exposing (SiteConfig)
|
import Pages.SiteConfig exposing (SiteConfig)
|
||||||
import Pages.StaticHttp.Request as HashRequest
|
import Pages.StaticHttp.Request as HashRequest
|
||||||
import Pages.StaticHttpRequest as StaticHttpRequest
|
import Pages.StaticHttpRequest as StaticHttpRequest
|
||||||
@ -25,8 +23,7 @@ import TerminalText as Terminal
|
|||||||
|
|
||||||
|
|
||||||
type StaticResponses
|
type StaticResponses
|
||||||
= GettingInitialData StaticHttpResult
|
= ApiRequest StaticHttpResult
|
||||||
| ApiRequest StaticHttpResult
|
|
||||||
| StaticResponses (Dict String StaticHttpResult)
|
| StaticResponses (Dict String StaticHttpResult)
|
||||||
| CheckIfHandled (DataSource (Maybe NotFoundReason)) StaticHttpResult (Dict String StaticHttpResult)
|
| CheckIfHandled (DataSource (Maybe NotFoundReason)) StaticHttpResult (Dict String StaticHttpResult)
|
||||||
|
|
||||||
@ -40,32 +37,6 @@ error =
|
|||||||
StaticResponses Dict.empty
|
StaticResponses Dict.empty
|
||||||
|
|
||||||
|
|
||||||
init :
|
|
||||||
{ config
|
|
||||||
| getStaticRoutes : DataSource (List route)
|
|
||||||
, site : SiteConfig route siteData
|
|
||||||
, data : route -> DataSource pageData
|
|
||||||
, sharedData : DataSource sharedData
|
|
||||||
, apiRoutes :
|
|
||||||
(Html Never -> String) -> List (ApiRoute.Done ApiRoute.Response)
|
|
||||||
}
|
|
||||||
-> StaticResponses
|
|
||||||
init config =
|
|
||||||
NotFetched
|
|
||||||
(DataSource.map3 (\_ _ _ -> ())
|
|
||||||
(config.getStaticRoutes
|
|
||||||
|> DataSource.andThen
|
|
||||||
(\resolvedRoutes ->
|
|
||||||
config.site resolvedRoutes |> .data
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(buildTimeFilesRequest config)
|
|
||||||
config.sharedData
|
|
||||||
)
|
|
||||||
Dict.empty
|
|
||||||
|> GettingInitialData
|
|
||||||
|
|
||||||
|
|
||||||
buildTimeFilesRequest :
|
buildTimeFilesRequest :
|
||||||
{ config
|
{ config
|
||||||
| apiRoutes :
|
| apiRoutes :
|
||||||
@ -136,10 +107,11 @@ renderApiRequest request =
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
update :
|
batchUpdate :
|
||||||
|
List
|
||||||
{ request :
|
{ request :
|
||||||
{ masked : RequestDetails, unmasked : RequestDetails }
|
{ masked : RequestDetails, unmasked : RequestDetails }
|
||||||
, response : Result () String
|
, response : String
|
||||||
}
|
}
|
||||||
->
|
->
|
||||||
{ model
|
{ model
|
||||||
@ -151,23 +123,33 @@ update :
|
|||||||
| staticResponses : StaticResponses
|
| staticResponses : StaticResponses
|
||||||
, allRawResponses : Dict String (Maybe String)
|
, allRawResponses : Dict String (Maybe String)
|
||||||
}
|
}
|
||||||
update newEntry model =
|
batchUpdate newEntries model =
|
||||||
let
|
let
|
||||||
|
newResponses =
|
||||||
|
newEntries
|
||||||
|
|> List.map
|
||||||
|
(\newEntry ->
|
||||||
|
( HashRequest.hash newEntry.request.masked, newEntry.response )
|
||||||
|
)
|
||||||
|
|> Dict.fromList
|
||||||
|
|
||||||
updatedAllResponses : Dict String (Maybe String)
|
updatedAllResponses : Dict String (Maybe String)
|
||||||
updatedAllResponses =
|
updatedAllResponses =
|
||||||
-- @@@@@@@@@ TODO handle errors here, change Dict to have `Result` instead of `Maybe`
|
Dict.merge
|
||||||
Dict.insert
|
(\key a -> Dict.insert key (Just a))
|
||||||
(HashRequest.hash newEntry.request.masked)
|
(\key a _ -> Dict.insert key (Just a))
|
||||||
(Just <| Result.withDefault "TODO" newEntry.response)
|
(\key b -> Dict.insert key b)
|
||||||
|
newResponses
|
||||||
model.allRawResponses
|
model.allRawResponses
|
||||||
|
Dict.empty
|
||||||
in
|
in
|
||||||
{ model
|
{ model
|
||||||
| allRawResponses = updatedAllResponses
|
| allRawResponses = updatedAllResponses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
encode : RequestsAndPending -> Mode -> Dict String StaticHttpResult -> Result (List BuildError) (Dict String (Dict String String))
|
encode : RequestsAndPending -> Dict String StaticHttpResult -> Result (List BuildError) (Dict String (Dict String String))
|
||||||
encode requestsAndPending _ staticResponses =
|
encode requestsAndPending staticResponses =
|
||||||
staticResponses
|
staticResponses
|
||||||
|> Dict.filter
|
|> Dict.filter
|
||||||
(\key _ ->
|
(\key _ ->
|
||||||
@ -191,7 +173,13 @@ cliDictKey =
|
|||||||
|
|
||||||
type NextStep route
|
type NextStep route
|
||||||
= Continue (Dict String (Maybe String)) (List { masked : RequestDetails, unmasked : RequestDetails }) (Maybe (List route))
|
= Continue (Dict String (Maybe String)) (List { masked : RequestDetails, unmasked : RequestDetails }) (Maybe (List route))
|
||||||
| Finish ToJsPayload
|
| Finish (FinishKind route)
|
||||||
|
|
||||||
|
|
||||||
|
type FinishKind route
|
||||||
|
= ApiResponse
|
||||||
|
| Errors (List BuildError)
|
||||||
|
| Page (Dict String String)
|
||||||
|
|
||||||
|
|
||||||
nextStep :
|
nextStep :
|
||||||
@ -209,11 +197,10 @@ nextStep :
|
|||||||
, secrets : SecretsDict
|
, secrets : SecretsDict
|
||||||
, errors : List BuildError
|
, errors : List BuildError
|
||||||
, allRawResponses : Dict String (Maybe String)
|
, allRawResponses : Dict String (Maybe String)
|
||||||
, mode : Mode
|
|
||||||
}
|
}
|
||||||
-> Maybe (List route)
|
-> Maybe (List route)
|
||||||
-> ( StaticResponses, NextStep route )
|
-> ( StaticResponses, NextStep route )
|
||||||
nextStep config ({ mode, secrets, allRawResponses, errors } as model) maybeRoutes =
|
nextStep config ({ secrets, allRawResponses, errors } as model) maybeRoutes =
|
||||||
let
|
let
|
||||||
staticResponses : Dict String StaticHttpResult
|
staticResponses : Dict String StaticHttpResult
|
||||||
staticResponses =
|
staticResponses =
|
||||||
@ -221,9 +208,6 @@ nextStep config ({ mode, secrets, allRawResponses, errors } as model) maybeRoute
|
|||||||
StaticResponses s ->
|
StaticResponses s ->
|
||||||
s
|
s
|
||||||
|
|
||||||
GettingInitialData initialData ->
|
|
||||||
Dict.singleton cliDictKey initialData
|
|
||||||
|
|
||||||
ApiRequest staticHttpResult ->
|
ApiRequest staticHttpResult ->
|
||||||
Dict.singleton cliDictKey staticHttpResult
|
Dict.singleton cliDictKey staticHttpResult
|
||||||
|
|
||||||
@ -240,19 +224,6 @@ nextStep config ({ mode, secrets, allRawResponses, errors } as model) maybeRoute
|
|||||||
(buildTimeFilesRequest config)
|
(buildTimeFilesRequest config)
|
||||||
(allRawResponses |> Dict.Extra.filterMap (\_ value -> Just value))
|
(allRawResponses |> Dict.Extra.filterMap (\_ value -> Just value))
|
||||||
|
|
||||||
generatedOkayFiles : List { path : List String, content : String }
|
|
||||||
generatedOkayFiles =
|
|
||||||
generatedFiles
|
|
||||||
|> List.filterMap
|
|
||||||
(\result ->
|
|
||||||
case result of
|
|
||||||
Ok ok ->
|
|
||||||
Just ok
|
|
||||||
|
|
||||||
Err _ ->
|
|
||||||
Nothing
|
|
||||||
)
|
|
||||||
|
|
||||||
generatedFileErrors : List BuildError
|
generatedFileErrors : List BuildError
|
||||||
generatedFileErrors =
|
generatedFileErrors =
|
||||||
generatedFiles
|
generatedFiles
|
||||||
@ -429,73 +400,10 @@ nextStep config ({ mode, secrets, allRawResponses, errors } as model) maybeRoute
|
|||||||
( model.staticResponses, Continue newAllRawResponses newThing maybeRoutes )
|
( model.staticResponses, Continue newAllRawResponses newThing maybeRoutes )
|
||||||
|
|
||||||
Err error_ ->
|
Err error_ ->
|
||||||
( model.staticResponses, Finish (ToJsPayload.Errors <| (error_ ++ failedRequests ++ errors)) )
|
( model.staticResponses, Finish (Errors <| (error_ ++ failedRequests ++ errors)) )
|
||||||
|
|
||||||
else
|
else
|
||||||
case model.staticResponses of
|
case model.staticResponses of
|
||||||
GettingInitialData (NotFetched _ _) ->
|
|
||||||
let
|
|
||||||
resolvedRoutes : Result StaticHttpRequest.Error (List route)
|
|
||||||
resolvedRoutes =
|
|
||||||
StaticHttpRequest.resolve ApplicationType.Cli
|
|
||||||
(DataSource.map3
|
|
||||||
(\routes _ _ ->
|
|
||||||
routes
|
|
||||||
)
|
|
||||||
config.getStaticRoutes
|
|
||||||
(buildTimeFilesRequest config)
|
|
||||||
config.sharedData
|
|
||||||
)
|
|
||||||
(allRawResponses |> Dict.Extra.filterMap (\_ value -> Just value))
|
|
||||||
in
|
|
||||||
case resolvedRoutes of
|
|
||||||
Ok staticRoutes ->
|
|
||||||
let
|
|
||||||
newState : StaticResponses
|
|
||||||
newState =
|
|
||||||
staticRoutes
|
|
||||||
|> List.map
|
|
||||||
(\route ->
|
|
||||||
let
|
|
||||||
entry : StaticHttpResult
|
|
||||||
entry =
|
|
||||||
NotFetched
|
|
||||||
(DataSource.map2 (\_ _ -> ())
|
|
||||||
config.sharedData
|
|
||||||
(config.data route)
|
|
||||||
)
|
|
||||||
Dict.empty
|
|
||||||
in
|
|
||||||
( config.routeToPath route |> String.join "/"
|
|
||||||
, entry
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|> Dict.fromList
|
|
||||||
|> StaticResponses
|
|
||||||
|
|
||||||
newThing : List { masked : RequestDetails, unmasked : RequestDetails }
|
|
||||||
newThing =
|
|
||||||
[]
|
|
||||||
in
|
|
||||||
( newState
|
|
||||||
, Continue allRawResponses newThing (Just staticRoutes)
|
|
||||||
)
|
|
||||||
|
|
||||||
Err error_ ->
|
|
||||||
( model.staticResponses
|
|
||||||
, Finish
|
|
||||||
(ToJsPayload.Errors <|
|
|
||||||
([ StaticHttpRequest.toBuildError
|
|
||||||
-- TODO give more fine-grained error reference
|
|
||||||
"get static routes"
|
|
||||||
error_
|
|
||||||
]
|
|
||||||
++ failedRequests
|
|
||||||
++ errors
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
StaticResponses _ ->
|
StaticResponses _ ->
|
||||||
--let
|
--let
|
||||||
-- siteStaticData =
|
-- siteStaticData =
|
||||||
@ -517,29 +425,27 @@ nextStep config ({ mode, secrets, allRawResponses, errors } as model) maybeRoute
|
|||||||
--
|
--
|
||||||
-- Ok okSiteStaticData ->
|
-- Ok okSiteStaticData ->
|
||||||
( model.staticResponses
|
( model.staticResponses
|
||||||
, case encode allRawResponses mode staticResponses of
|
, case encode allRawResponses staticResponses of
|
||||||
Ok encodedResponses ->
|
Ok encodedResponses ->
|
||||||
ToJsPayload.toJsPayload
|
|
||||||
encodedResponses
|
|
||||||
generatedOkayFiles
|
|
||||||
allRawResponses
|
|
||||||
allErrors
|
|
||||||
-- TODO send all global head tags on initial call
|
-- TODO send all global head tags on initial call
|
||||||
|
if List.length allErrors > 0 then
|
||||||
|
allErrors
|
||||||
|
|> Errors
|
||||||
|
|> Finish
|
||||||
|
|
||||||
|
else
|
||||||
|
Page (encodedResponses |> Dict.values |> List.head |> Maybe.withDefault Dict.empty)
|
||||||
|> Finish
|
|> Finish
|
||||||
|
|
||||||
Err buildErrors ->
|
Err buildErrors ->
|
||||||
ToJsPayload.toJsPayload
|
|
||||||
Dict.empty
|
|
||||||
generatedOkayFiles
|
|
||||||
allRawResponses
|
|
||||||
(allErrors ++ buildErrors)
|
(allErrors ++ buildErrors)
|
||||||
-- TODO send all global head tags on initial call
|
|> Errors
|
||||||
|> Finish
|
|> Finish
|
||||||
)
|
)
|
||||||
|
|
||||||
ApiRequest _ ->
|
ApiRequest _ ->
|
||||||
( model.staticResponses
|
( model.staticResponses
|
||||||
, ToJsPayload.ApiResponse
|
, ApiResponse
|
||||||
|> Finish
|
|> Finish
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -557,14 +463,14 @@ nextStep config ({ mode, secrets, allRawResponses, errors } as model) maybeRoute
|
|||||||
|
|
||||||
Ok (Just _) ->
|
Ok (Just _) ->
|
||||||
( StaticResponses Dict.empty
|
( StaticResponses Dict.empty
|
||||||
, Finish ToJsPayload.ApiResponse
|
, Finish ApiResponse
|
||||||
-- TODO should there be a new type for 404response? Or something else?
|
-- TODO should there be a new type for 404response? Or something else?
|
||||||
)
|
)
|
||||||
|
|
||||||
Err error_ ->
|
Err error_ ->
|
||||||
( model.staticResponses
|
( model.staticResponses
|
||||||
, Finish
|
, Finish
|
||||||
(ToJsPayload.Errors <|
|
(Errors <|
|
||||||
([ StaticHttpRequest.toBuildError
|
([ StaticHttpRequest.toBuildError
|
||||||
-- TODO give more fine-grained error reference
|
-- TODO give more fine-grained error reference
|
||||||
"get static routes"
|
"get static routes"
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
module Pages.Internal.Platform.ToJsPayload exposing
|
module Pages.Internal.Platform.ToJsPayload exposing
|
||||||
( FileToGenerate
|
( ToJsSuccessPayloadNew
|
||||||
, InitialDataRecord
|
|
||||||
, ToJsPayload(..)
|
|
||||||
, ToJsSuccessPayload
|
|
||||||
, ToJsSuccessPayloadNew
|
|
||||||
, ToJsSuccessPayloadNewCombined(..)
|
, ToJsSuccessPayloadNewCombined(..)
|
||||||
, successCodecNew2
|
, successCodecNew2
|
||||||
, toJsCodec
|
|
||||||
, toJsPayload
|
|
||||||
)
|
)
|
||||||
|
|
||||||
import BuildError exposing (BuildError)
|
import BuildError exposing (BuildError)
|
||||||
@ -16,20 +10,7 @@ import Dict exposing (Dict)
|
|||||||
import Head
|
import Head
|
||||||
import Json.Decode as Decode
|
import Json.Decode as Decode
|
||||||
import Json.Encode
|
import Json.Encode
|
||||||
|
import Pages.StaticHttp.Request
|
||||||
|
|
||||||
type ToJsPayload
|
|
||||||
= Errors (List BuildError)
|
|
||||||
| Success ToJsSuccessPayload
|
|
||||||
| ApiResponse
|
|
||||||
|
|
||||||
|
|
||||||
type alias ToJsSuccessPayload =
|
|
||||||
{ pages : Dict String (Dict String String)
|
|
||||||
, filesToGenerate : List FileToGenerate
|
|
||||||
, staticHttpCache : Dict String String
|
|
||||||
, errors : List BuildError
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias ToJsSuccessPayloadNew =
|
type alias ToJsSuccessPayloadNew =
|
||||||
@ -44,60 +25,6 @@ type alias ToJsSuccessPayloadNew =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type alias FileToGenerate =
|
|
||||||
{ path : List String
|
|
||||||
, content : String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
toJsPayload :
|
|
||||||
Dict String (Dict String String)
|
|
||||||
-> List FileToGenerate
|
|
||||||
-> Dict String (Maybe String)
|
|
||||||
-> List BuildError
|
|
||||||
-> ToJsPayload
|
|
||||||
toJsPayload encodedStatic generated allRawResponses allErrors =
|
|
||||||
if allErrors |> List.filter .fatal |> List.isEmpty then
|
|
||||||
Success
|
|
||||||
(ToJsSuccessPayload
|
|
||||||
encodedStatic
|
|
||||||
generated
|
|
||||||
(allRawResponses
|
|
||||||
|> Dict.toList
|
|
||||||
|> List.filterMap
|
|
||||||
(\( key, maybeValue ) ->
|
|
||||||
maybeValue
|
|
||||||
|> Maybe.map (\value -> ( key, value ))
|
|
||||||
)
|
|
||||||
|> Dict.fromList
|
|
||||||
)
|
|
||||||
allErrors
|
|
||||||
)
|
|
||||||
|
|
||||||
else
|
|
||||||
Errors <| allErrors
|
|
||||||
|
|
||||||
|
|
||||||
toJsCodec : Codec ToJsPayload
|
|
||||||
toJsCodec =
|
|
||||||
Codec.custom
|
|
||||||
(\errorsTag success vApiResponse value ->
|
|
||||||
case value of
|
|
||||||
Errors errorList ->
|
|
||||||
errorsTag errorList
|
|
||||||
|
|
||||||
Success { pages, filesToGenerate, errors, staticHttpCache } ->
|
|
||||||
success (ToJsSuccessPayload pages filesToGenerate staticHttpCache errors)
|
|
||||||
|
|
||||||
ApiResponse ->
|
|
||||||
vApiResponse
|
|
||||||
)
|
|
||||||
|> Codec.variant1 "Errors" Errors errorCodec
|
|
||||||
|> Codec.variant1 "Success" Success successCodec
|
|
||||||
|> Codec.variant0 "ApiResponse" ApiResponse
|
|
||||||
|> Codec.buildCustom
|
|
||||||
|
|
||||||
|
|
||||||
errorCodec : Codec (List BuildError)
|
errorCodec : Codec (List BuildError)
|
||||||
errorCodec =
|
errorCodec =
|
||||||
Codec.object (\errorString _ -> errorString)
|
Codec.object (\errorString _ -> errorString)
|
||||||
@ -117,39 +44,6 @@ errorCodec =
|
|||||||
|> Codec.buildObject
|
|> Codec.buildObject
|
||||||
|
|
||||||
|
|
||||||
successCodec : Codec ToJsSuccessPayload
|
|
||||||
successCodec =
|
|
||||||
Codec.object ToJsSuccessPayload
|
|
||||||
|> Codec.field "pages"
|
|
||||||
.pages
|
|
||||||
(Codec.dict (Codec.dict Codec.string))
|
|
||||||
|> Codec.field "filesToGenerate"
|
|
||||||
.filesToGenerate
|
|
||||||
(Codec.build
|
|
||||||
(\list ->
|
|
||||||
list
|
|
||||||
|> Json.Encode.list
|
|
||||||
(\item ->
|
|
||||||
Json.Encode.object
|
|
||||||
[ ( "path", item.path |> String.join "/" |> Json.Encode.string )
|
|
||||||
, ( "content", item.content |> Json.Encode.string )
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(Decode.list
|
|
||||||
(Decode.map2 (\path content -> { path = path, content = content })
|
|
||||||
(Decode.string |> Decode.map (String.split "/") |> Decode.field "path")
|
|
||||||
(Decode.string |> Decode.field "content")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|> Codec.field "staticHttpCache"
|
|
||||||
.staticHttpCache
|
|
||||||
(Codec.dict Codec.string)
|
|
||||||
|> Codec.field "errors" .errors errorCodec
|
|
||||||
|> Codec.buildObject
|
|
||||||
|
|
||||||
|
|
||||||
successCodecNew : String -> String -> Codec ToJsSuccessPayloadNew
|
successCodecNew : String -> String -> Codec ToJsSuccessPayloadNew
|
||||||
successCodecNew canonicalSiteUrl currentPagePath =
|
successCodecNew canonicalSiteUrl currentPagePath =
|
||||||
Codec.object ToJsSuccessPayloadNew
|
Codec.object ToJsSuccessPayloadNew
|
||||||
@ -180,45 +74,56 @@ headCodec canonicalSiteUrl currentPagePath =
|
|||||||
|
|
||||||
type ToJsSuccessPayloadNewCombined
|
type ToJsSuccessPayloadNewCombined
|
||||||
= PageProgress ToJsSuccessPayloadNew
|
= PageProgress ToJsSuccessPayloadNew
|
||||||
| InitialData InitialDataRecord
|
|
||||||
| SendApiResponse { body : String, staticHttpCache : Dict String String, statusCode : Int }
|
| SendApiResponse { body : String, staticHttpCache : Dict String String, statusCode : Int }
|
||||||
| ReadFile String
|
| ReadFile String
|
||||||
| Glob String
|
| Glob String
|
||||||
|
| DoHttp { masked : Pages.StaticHttp.Request.Request, unmasked : Pages.StaticHttp.Request.Request }
|
||||||
| Port String
|
| Port String
|
||||||
|
| Errors (List BuildError)
|
||||||
|
| ApiResponse
|
||||||
type alias InitialDataRecord =
|
|
||||||
{ filesToGenerate : List FileToGenerate
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
successCodecNew2 : String -> String -> Codec ToJsSuccessPayloadNewCombined
|
successCodecNew2 : String -> String -> Codec ToJsSuccessPayloadNewCombined
|
||||||
successCodecNew2 canonicalSiteUrl currentPagePath =
|
successCodecNew2 canonicalSiteUrl currentPagePath =
|
||||||
Codec.custom
|
Codec.custom
|
||||||
(\success initialData vReadFile vGlob vSendApiResponse vPort value ->
|
(\errorsTag vApiResponse success vReadFile vGlob vDoHttp vSendApiResponse vPort value ->
|
||||||
case value of
|
case value of
|
||||||
|
ApiResponse ->
|
||||||
|
vApiResponse
|
||||||
|
|
||||||
|
Errors errorList ->
|
||||||
|
errorsTag errorList
|
||||||
|
|
||||||
PageProgress payload ->
|
PageProgress payload ->
|
||||||
success payload
|
success payload
|
||||||
|
|
||||||
InitialData payload ->
|
|
||||||
initialData payload
|
|
||||||
|
|
||||||
ReadFile filePath ->
|
ReadFile filePath ->
|
||||||
vReadFile filePath
|
vReadFile filePath
|
||||||
|
|
||||||
Glob globPattern ->
|
Glob globPattern ->
|
||||||
vGlob globPattern
|
vGlob globPattern
|
||||||
|
|
||||||
|
DoHttp requestUrl ->
|
||||||
|
vDoHttp requestUrl
|
||||||
|
|
||||||
SendApiResponse record ->
|
SendApiResponse record ->
|
||||||
vSendApiResponse record
|
vSendApiResponse record
|
||||||
|
|
||||||
Port string ->
|
Port string ->
|
||||||
vPort string
|
vPort string
|
||||||
)
|
)
|
||||||
|
|> Codec.variant1 "Errors" Errors errorCodec
|
||||||
|
|> Codec.variant0 "ApiResponse" ApiResponse
|
||||||
|> Codec.variant1 "PageProgress" PageProgress (successCodecNew canonicalSiteUrl currentPagePath)
|
|> Codec.variant1 "PageProgress" PageProgress (successCodecNew canonicalSiteUrl currentPagePath)
|
||||||
|> Codec.variant1 "InitialData" InitialData initialDataCodec
|
|
||||||
|> Codec.variant1 "ReadFile" ReadFile Codec.string
|
|> Codec.variant1 "ReadFile" ReadFile Codec.string
|
||||||
|> Codec.variant1 "Glob" Glob Codec.string
|
|> Codec.variant1 "Glob" Glob Codec.string
|
||||||
|
|> Codec.variant1 "DoHttp"
|
||||||
|
DoHttp
|
||||||
|
(Codec.object (\masked unmasked -> { masked = masked, unmasked = unmasked })
|
||||||
|
|> Codec.field "masked" .masked Pages.StaticHttp.Request.codec
|
||||||
|
|> Codec.field "unmasked" .unmasked Pages.StaticHttp.Request.codec
|
||||||
|
|> Codec.buildObject
|
||||||
|
)
|
||||||
|> Codec.variant1 "ApiResponse"
|
|> Codec.variant1 "ApiResponse"
|
||||||
SendApiResponse
|
SendApiResponse
|
||||||
(Codec.object (\body staticHttpCache statusCode -> { body = body, staticHttpCache = staticHttpCache, statusCode = statusCode })
|
(Codec.object (\body staticHttpCache statusCode -> { body = body, staticHttpCache = staticHttpCache, statusCode = statusCode })
|
||||||
@ -231,33 +136,3 @@ successCodecNew2 canonicalSiteUrl currentPagePath =
|
|||||||
)
|
)
|
||||||
|> Codec.variant1 "Port" Port Codec.string
|
|> Codec.variant1 "Port" Port Codec.string
|
||||||
|> Codec.buildCustom
|
|> Codec.buildCustom
|
||||||
|
|
||||||
|
|
||||||
filesToGenerateCodec : Codec (List { path : List String, content : String })
|
|
||||||
filesToGenerateCodec =
|
|
||||||
Codec.build
|
|
||||||
(\list ->
|
|
||||||
list
|
|
||||||
|> Json.Encode.list
|
|
||||||
(\item ->
|
|
||||||
Json.Encode.object
|
|
||||||
[ ( "path", item.path |> String.join "/" |> Json.Encode.string )
|
|
||||||
, ( "content", item.content |> Json.Encode.string )
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
(Decode.list
|
|
||||||
(Decode.map2 (\path content -> { path = path, content = content })
|
|
||||||
(Decode.string |> Decode.map (String.split "/") |> Decode.field "path")
|
|
||||||
(Decode.string |> Decode.field "content")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
initialDataCodec : Codec InitialDataRecord
|
|
||||||
initialDataCodec =
|
|
||||||
Codec.object InitialDataRecord
|
|
||||||
|> Codec.field "filesToGenerate"
|
|
||||||
.filesToGenerate
|
|
||||||
filesToGenerateCodec
|
|
||||||
|> Codec.buildObject
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
module Pages.Internal.StaticHttpBody exposing (Body(..), encode)
|
module Pages.Internal.StaticHttpBody exposing (Body(..), codec, encode)
|
||||||
|
|
||||||
|
import Codec exposing (Codec)
|
||||||
import Json.Encode as Encode
|
import Json.Encode as Encode
|
||||||
|
|
||||||
|
|
||||||
@ -31,3 +32,23 @@ encodeWithType typeName otherFields =
|
|||||||
Encode.object <|
|
Encode.object <|
|
||||||
( "type", Encode.string typeName )
|
( "type", Encode.string typeName )
|
||||||
:: otherFields
|
:: otherFields
|
||||||
|
|
||||||
|
|
||||||
|
codec : Codec Body
|
||||||
|
codec =
|
||||||
|
Codec.custom
|
||||||
|
(\vEmpty vString vJson value ->
|
||||||
|
case value of
|
||||||
|
EmptyBody ->
|
||||||
|
vEmpty
|
||||||
|
|
||||||
|
StringBody a b ->
|
||||||
|
vString a b
|
||||||
|
|
||||||
|
JsonBody body ->
|
||||||
|
vJson body
|
||||||
|
)
|
||||||
|
|> Codec.variant0 "EmptyBody" EmptyBody
|
||||||
|
|> Codec.variant2 "StringBody" StringBody Codec.string Codec.string
|
||||||
|
|> Codec.variant1 "JsonBody" JsonBody Codec.value
|
||||||
|
|> Codec.buildCustom
|
||||||
|
@ -39,7 +39,7 @@ type alias ProgramConfig userMsg userModel route siteData pageData sharedData =
|
|||||||
, data : route -> DataSource.DataSource pageData
|
, data : route -> DataSource.DataSource pageData
|
||||||
, view :
|
, view :
|
||||||
{ path : Path
|
{ path : Path
|
||||||
, frontmatter : route
|
, route : route
|
||||||
}
|
}
|
||||||
-> Maybe PageUrl
|
-> Maybe PageUrl
|
||||||
-> sharedData
|
-> sharedData
|
||||||
@ -69,4 +69,5 @@ type alias ProgramConfig userMsg userModel route siteData pageData sharedData =
|
|||||||
(Html Never -> String)
|
(Html Never -> String)
|
||||||
-> List (ApiRoute.Done ApiRoute.Response)
|
-> List (ApiRoute.Done ApiRoute.Response)
|
||||||
, pathPatterns : List RoutePattern
|
, pathPatterns : List RoutePattern
|
||||||
|
, basePath : List String
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
module Pages.StaticHttp.Request exposing (Request, hash)
|
module Pages.StaticHttp.Request exposing (Request, codec, hash)
|
||||||
|
|
||||||
|
import Codec exposing (Codec)
|
||||||
import Json.Encode as Encode
|
import Json.Encode as Encode
|
||||||
|
import Murmur3
|
||||||
import Pages.Internal.StaticHttpBody as StaticHttpBody exposing (Body)
|
import Pages.Internal.StaticHttpBody as StaticHttpBody exposing (Body)
|
||||||
|
|
||||||
|
|
||||||
@ -21,8 +23,20 @@ hash requestDetails =
|
|||||||
, ( "body", StaticHttpBody.encode requestDetails.body )
|
, ( "body", StaticHttpBody.encode requestDetails.body )
|
||||||
]
|
]
|
||||||
|> Encode.encode 0
|
|> Encode.encode 0
|
||||||
|
|> Murmur3.hashString 0
|
||||||
|
|> String.fromInt
|
||||||
|
|
||||||
|
|
||||||
hashHeader : ( String, String ) -> Encode.Value
|
hashHeader : ( String, String ) -> Encode.Value
|
||||||
hashHeader ( name, value ) =
|
hashHeader ( name, value ) =
|
||||||
Encode.string <| name ++ ": " ++ value
|
Encode.string <| name ++ ": " ++ value
|
||||||
|
|
||||||
|
|
||||||
|
codec : Codec Request
|
||||||
|
codec =
|
||||||
|
Codec.object Request
|
||||||
|
|> Codec.field "url" .url Codec.string
|
||||||
|
|> Codec.field "method" .method Codec.string
|
||||||
|
|> Codec.field "headers" .headers (Codec.list (Codec.tuple Codec.string Codec.string))
|
||||||
|
|> Codec.field "body" .body StaticHttpBody.codec
|
||||||
|
|> Codec.buildObject
|
||||||
|
@ -16,11 +16,7 @@ import TerminalText as Terminal
|
|||||||
|
|
||||||
|
|
||||||
type RawRequest value
|
type RawRequest value
|
||||||
= Request
|
= Request (Dict String WhatToDo) ( List (Secrets.Value Pages.StaticHttp.Request.Request), KeepOrDiscard -> ApplicationType -> RequestsAndPending -> RawRequest value )
|
||||||
(Dict String WhatToDo)
|
|
||||||
( List (Secrets.Value Pages.StaticHttp.Request.Request)
|
|
||||||
, KeepOrDiscard -> ApplicationType -> RequestsAndPending -> RawRequest value
|
|
||||||
)
|
|
||||||
| RequestError Error
|
| RequestError Error
|
||||||
| Done (Dict String WhatToDo) value
|
| Done (Dict String WhatToDo) value
|
||||||
|
|
||||||
@ -70,14 +66,14 @@ merge key whatToDo1 whatToDo2 =
|
|||||||
, message =
|
, message =
|
||||||
[ Terminal.text "I encountered DataSource.distill with two matching keys that had differing encoded values.\n\n"
|
[ Terminal.text "I encountered DataSource.distill with two matching keys that had differing encoded values.\n\n"
|
||||||
, Terminal.text "Look for "
|
, Terminal.text "Look for "
|
||||||
, Terminal.red <| Terminal.text "DataSource.distill"
|
, Terminal.red <| "DataSource.distill"
|
||||||
, Terminal.text " with the key "
|
, Terminal.text " with the key "
|
||||||
, Terminal.red <| Terminal.text ("\"" ++ key ++ "\"")
|
, Terminal.red <| ("\"" ++ key ++ "\"")
|
||||||
, Terminal.text "\n\n"
|
, Terminal.text "\n\n"
|
||||||
, Terminal.yellow <| Terminal.text "The first encoded value was:\n"
|
, Terminal.yellow <| "The first encoded value was:\n"
|
||||||
, Terminal.text <| Json.Encode.encode 2 distilled1
|
, Terminal.text <| Json.Encode.encode 2 distilled1
|
||||||
, Terminal.text "\n\n-------------------------------\n\n"
|
, Terminal.text "\n\n-------------------------------\n\n"
|
||||||
, Terminal.yellow <| Terminal.text "The second encoded value was:\n"
|
, Terminal.yellow <| "The second encoded value was:\n"
|
||||||
, Terminal.text <| Json.Encode.encode 2 distilled2
|
, Terminal.text <| Json.Encode.encode 2 distilled2
|
||||||
]
|
]
|
||||||
, path = "" -- TODO wire in path here?
|
, path = "" -- TODO wire in path here?
|
||||||
@ -263,10 +259,6 @@ resolveUrlsHelp appType request rawResponses soFar =
|
|||||||
RequestError error ->
|
RequestError error ->
|
||||||
case error of
|
case error of
|
||||||
MissingHttpResponse _ next ->
|
MissingHttpResponse _ next ->
|
||||||
let
|
|
||||||
thing =
|
|
||||||
next |> List.map Secrets.maskedLookup
|
|
||||||
in
|
|
||||||
(soFar ++ next)
|
(soFar ++ next)
|
||||||
|> List.Extra.uniqueBy (Secrets.maskedLookup >> Pages.StaticHttp.Request.hash)
|
|> List.Extra.uniqueBy (Secrets.maskedLookup >> Pages.StaticHttp.Request.hash)
|
||||||
|
|
||||||
@ -311,7 +303,7 @@ cacheRequestResolutionHelp foundUrls appType request rawResponses =
|
|||||||
case request of
|
case request of
|
||||||
RequestError error ->
|
RequestError error ->
|
||||||
case error of
|
case error of
|
||||||
MissingHttpResponse key _ ->
|
MissingHttpResponse _ _ ->
|
||||||
-- TODO do I need to pass through continuation URLs here? -- Incomplete (urlList ++ foundUrls)
|
-- TODO do I need to pass through continuation URLs here? -- Incomplete (urlList ++ foundUrls)
|
||||||
Incomplete foundUrls
|
Incomplete foundUrls
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ fromString path =
|
|||||||
{-| -}
|
{-| -}
|
||||||
toSegments : Path -> List String
|
toSegments : Path -> List String
|
||||||
toSegments (Path path) =
|
toSegments (Path path) =
|
||||||
path |> String.split "/"
|
path |> String.split "/" |> List.filter ((/=) "")
|
||||||
|
|
||||||
|
|
||||||
{-| Turn a Path to an absolute URL (with no trailing slash).
|
{-| Turn a Path to an absolute URL (with no trailing slash).
|
||||||
|
@ -3,6 +3,7 @@ module RenderRequest exposing
|
|||||||
, RenderRequest(..)
|
, RenderRequest(..)
|
||||||
, RequestPayload(..)
|
, RequestPayload(..)
|
||||||
, decoder
|
, decoder
|
||||||
|
, default
|
||||||
, maybeRequestPayload
|
, maybeRequestPayload
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ import ApiRoute
|
|||||||
import HtmlPrinter
|
import HtmlPrinter
|
||||||
import Internal.ApiRoute
|
import Internal.ApiRoute
|
||||||
import Json.Decode as Decode
|
import Json.Decode as Decode
|
||||||
|
import Json.Encode as Encode
|
||||||
import Pages.ProgramConfig exposing (ProgramConfig)
|
import Pages.ProgramConfig exposing (ProgramConfig)
|
||||||
import Path exposing (Path)
|
import Path exposing (Path)
|
||||||
import Regex
|
import Regex
|
||||||
@ -24,15 +26,19 @@ type RequestPayload route
|
|||||||
|
|
||||||
type RenderRequest route
|
type RenderRequest route
|
||||||
= SinglePage IncludeHtml (RequestPayload route) Decode.Value
|
= SinglePage IncludeHtml (RequestPayload route) Decode.Value
|
||||||
| FullBuild
|
|
||||||
|
|
||||||
|
default : RenderRequest route
|
||||||
|
default =
|
||||||
|
SinglePage
|
||||||
|
HtmlAndJson
|
||||||
|
(NotFound (Path.fromString "/error"))
|
||||||
|
Encode.null
|
||||||
|
|
||||||
|
|
||||||
maybeRequestPayload : RenderRequest route -> Maybe Decode.Value
|
maybeRequestPayload : RenderRequest route -> Maybe Decode.Value
|
||||||
maybeRequestPayload renderRequest =
|
maybeRequestPayload renderRequest =
|
||||||
case renderRequest of
|
case renderRequest of
|
||||||
FullBuild ->
|
|
||||||
Nothing
|
|
||||||
|
|
||||||
SinglePage _ _ rawJson ->
|
SinglePage _ _ rawJson ->
|
||||||
Just rawJson
|
Just rawJson
|
||||||
|
|
||||||
@ -46,7 +52,7 @@ decoder :
|
|||||||
ProgramConfig userMsg userModel (Maybe route) siteData pageData sharedData
|
ProgramConfig userMsg userModel (Maybe route) siteData pageData sharedData
|
||||||
-> Decode.Decoder (RenderRequest (Maybe route))
|
-> Decode.Decoder (RenderRequest (Maybe route))
|
||||||
decoder config =
|
decoder config =
|
||||||
optionalField "request"
|
Decode.field "request"
|
||||||
(Decode.map3
|
(Decode.map3
|
||||||
(\includeHtml requestThing payload ->
|
(\includeHtml requestThing payload ->
|
||||||
SinglePage includeHtml requestThing payload
|
SinglePage includeHtml requestThing payload
|
||||||
@ -73,15 +79,6 @@ decoder config =
|
|||||||
(requestPayloadDecoder config)
|
(requestPayloadDecoder config)
|
||||||
(Decode.field "payload" Decode.value)
|
(Decode.field "payload" Decode.value)
|
||||||
)
|
)
|
||||||
|> Decode.map
|
|
||||||
(\maybeRequest ->
|
|
||||||
case maybeRequest of
|
|
||||||
Just request ->
|
|
||||||
request
|
|
||||||
|
|
||||||
Nothing ->
|
|
||||||
FullBuild
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ fromOptionalSplat : Maybe String -> List String
|
|||||||
fromOptionalSplat maybeMatch =
|
fromOptionalSplat maybeMatch =
|
||||||
maybeMatch
|
maybeMatch
|
||||||
|> Maybe.map (\match -> match |> String.split "/")
|
|> Maybe.map (\match -> match |> String.split "/")
|
||||||
|
|> Maybe.map (List.filter (\item -> item /= ""))
|
||||||
|> Maybe.withDefault []
|
|> Maybe.withDefault []
|
||||||
|
|
||||||
|
|
||||||
|
@ -50,11 +50,11 @@ buildError secretName secretsDict =
|
|||||||
[ Terminal.text "I expected to find this Secret in your environment variables but didn't find a match:\n\nSecrets.get \""
|
[ Terminal.text "I expected to find this Secret in your environment variables but didn't find a match:\n\nSecrets.get \""
|
||||||
, Terminal.text secretName
|
, Terminal.text secretName
|
||||||
, Terminal.text "\"\n "
|
, Terminal.text "\"\n "
|
||||||
, Terminal.red <| Terminal.text (underlineText (secretName |> String.length))
|
, Terminal.red <| underlineText (secretName |> String.length)
|
||||||
, Terminal.text "\n\nSo maybe "
|
, Terminal.text "\n\nSo maybe "
|
||||||
, Terminal.yellow <| Terminal.text secretName
|
, Terminal.yellow <| secretName
|
||||||
, Terminal.text " should be "
|
, Terminal.text " should be "
|
||||||
, Terminal.green <| Terminal.text (sortMatches secretName availableEnvironmentVariables |> List.head |> Maybe.withDefault "")
|
, Terminal.green <| (sortMatches secretName availableEnvironmentVariables |> List.head |> Maybe.withDefault "")
|
||||||
]
|
]
|
||||||
, path = "" -- TODO wire in path here?
|
, path = "" -- TODO wire in path here?
|
||||||
, fatal = True
|
, fatal = True
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
module TerminalText exposing
|
module TerminalText exposing
|
||||||
( Color(..)
|
( Text(..)
|
||||||
, Text(..)
|
|
||||||
, ansi
|
, ansi
|
||||||
, ansiPrefix
|
, ansiPrefix
|
||||||
, blue
|
, blue
|
||||||
, colorToString
|
, colorToString
|
||||||
, cyan
|
, cyan
|
||||||
, encoder
|
, encoder
|
||||||
, getString
|
, fromAnsiString
|
||||||
, green
|
, green
|
||||||
, red
|
, red
|
||||||
, resetColors
|
, resetColors
|
||||||
@ -17,50 +16,42 @@ module TerminalText exposing
|
|||||||
, yellow
|
, yellow
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import Ansi
|
||||||
import Json.Encode as Encode
|
import Json.Encode as Encode
|
||||||
|
|
||||||
|
|
||||||
type Text
|
type Text
|
||||||
= RawText String
|
= Style AnsiStyle String
|
||||||
| Style Color Text
|
|
||||||
|
|
||||||
|
|
||||||
type Color
|
|
||||||
= Red
|
|
||||||
| Blue
|
|
||||||
| Green
|
|
||||||
| Yellow
|
|
||||||
| Cyan
|
|
||||||
|
|
||||||
|
|
||||||
text : String -> Text
|
text : String -> Text
|
||||||
text value =
|
text value =
|
||||||
RawText value
|
Style blankStyle value
|
||||||
|
|
||||||
|
|
||||||
cyan : Text -> Text
|
cyan : String -> Text
|
||||||
cyan inner =
|
cyan inner =
|
||||||
Style Cyan inner
|
Style { blankStyle | color = Just Ansi.Cyan } inner
|
||||||
|
|
||||||
|
|
||||||
green : Text -> Text
|
green : String -> Text
|
||||||
green inner =
|
green inner =
|
||||||
Style Green inner
|
Style { blankStyle | color = Just Ansi.Green } inner
|
||||||
|
|
||||||
|
|
||||||
yellow : Text -> Text
|
yellow : String -> Text
|
||||||
yellow inner =
|
yellow inner =
|
||||||
Style Yellow inner
|
Style { blankStyle | color = Just Ansi.Yellow } inner
|
||||||
|
|
||||||
|
|
||||||
red : Text -> Text
|
red : String -> Text
|
||||||
red inner =
|
red inner =
|
||||||
Style Red inner
|
Style { blankStyle | color = Just Ansi.Red } inner
|
||||||
|
|
||||||
|
|
||||||
blue : Text -> Text
|
blue : String -> Text
|
||||||
blue inner =
|
blue inner =
|
||||||
Style Blue inner
|
Style { blankStyle | color = Just Ansi.Blue } inner
|
||||||
|
|
||||||
|
|
||||||
resetColors : String
|
resetColors : String
|
||||||
@ -78,25 +69,29 @@ ansiPrefix =
|
|||||||
"\u{001B}"
|
"\u{001B}"
|
||||||
|
|
||||||
|
|
||||||
colorToString : Color -> String
|
colorToString : Ansi.Color -> String
|
||||||
colorToString color =
|
colorToString color =
|
||||||
ansi <|
|
ansi <|
|
||||||
case color of
|
case color of
|
||||||
Red ->
|
Ansi.Red ->
|
||||||
"[31m"
|
"[31m"
|
||||||
|
|
||||||
Blue ->
|
Ansi.Blue ->
|
||||||
"[34m"
|
"[34m"
|
||||||
|
|
||||||
Green ->
|
Ansi.Green ->
|
||||||
"[32m"
|
"[32m"
|
||||||
|
|
||||||
Yellow ->
|
Ansi.Yellow ->
|
||||||
"[33m"
|
"[33m"
|
||||||
|
|
||||||
Cyan ->
|
Ansi.Cyan ->
|
||||||
"[36m"
|
"[36m"
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
-- TODO
|
||||||
|
""
|
||||||
|
|
||||||
|
|
||||||
toString : List Text -> String
|
toString : List Text -> String
|
||||||
toString list =
|
toString list =
|
||||||
@ -106,57 +101,142 @@ toString list =
|
|||||||
|
|
||||||
|
|
||||||
toString_ : Text -> String
|
toString_ : Text -> String
|
||||||
toString_ textValue =
|
toString_ (Style ansiStyle innerText) =
|
||||||
-- elm-review: known-unoptimized-recursion
|
|
||||||
case textValue of
|
|
||||||
RawText content ->
|
|
||||||
content
|
|
||||||
|
|
||||||
Style color innerText ->
|
|
||||||
String.concat
|
String.concat
|
||||||
[ colorToString color
|
[ ansiStyle.color |> Maybe.withDefault Ansi.White |> colorToString
|
||||||
, toString_ innerText
|
, innerText
|
||||||
, resetColors
|
, resetColors
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
fromAnsiString : String -> List Text
|
||||||
|
fromAnsiString ansiString =
|
||||||
|
Ansi.parseInto ( blankStyle, [] ) parseInto ansiString
|
||||||
|
|> Tuple.second
|
||||||
|
|> List.reverse
|
||||||
|
|
||||||
|
|
||||||
|
type alias AnsiStyle =
|
||||||
|
{ bold : Bool
|
||||||
|
, underline : Bool
|
||||||
|
, color : Maybe Ansi.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
blankStyle : AnsiStyle
|
||||||
|
blankStyle =
|
||||||
|
{ bold = False
|
||||||
|
, underline = False
|
||||||
|
, color = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parseInto : Ansi.Action -> ( AnsiStyle, List Text ) -> ( AnsiStyle, List Text )
|
||||||
|
parseInto action ( pendingStyle, soFar ) =
|
||||||
|
case action of
|
||||||
|
Ansi.Print string ->
|
||||||
|
( blankStyle, Style pendingStyle string :: soFar )
|
||||||
|
|
||||||
|
Ansi.Remainder _ ->
|
||||||
|
( pendingStyle, soFar )
|
||||||
|
|
||||||
|
Ansi.SetForeground maybeColor ->
|
||||||
|
case maybeColor of
|
||||||
|
Just newColor ->
|
||||||
|
( { pendingStyle
|
||||||
|
| color = Just newColor
|
||||||
|
}
|
||||||
|
, soFar
|
||||||
|
)
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
( blankStyle, soFar )
|
||||||
|
|
||||||
|
Ansi.SetBold bool ->
|
||||||
|
( { pendingStyle | bold = bool }, soFar )
|
||||||
|
|
||||||
|
Ansi.SetFaint _ ->
|
||||||
|
( pendingStyle, soFar )
|
||||||
|
|
||||||
|
Ansi.SetItalic _ ->
|
||||||
|
( pendingStyle, soFar )
|
||||||
|
|
||||||
|
Ansi.SetUnderline bool ->
|
||||||
|
( { pendingStyle | underline = bool }, soFar )
|
||||||
|
|
||||||
|
Ansi.SetBackground _ ->
|
||||||
|
( pendingStyle, soFar )
|
||||||
|
|
||||||
|
Ansi.Linebreak ->
|
||||||
|
case soFar of
|
||||||
|
next :: rest ->
|
||||||
|
( pendingStyle, Style blankStyle "\n" :: next :: rest )
|
||||||
|
|
||||||
|
[] ->
|
||||||
|
( pendingStyle, soFar )
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
( pendingStyle, soFar )
|
||||||
|
|
||||||
|
|
||||||
encoder : Text -> Encode.Value
|
encoder : Text -> Encode.Value
|
||||||
encoder node =
|
encoder (Style ansiStyle string) =
|
||||||
Encode.object
|
Encode.object
|
||||||
[ ( "bold", Encode.bool False )
|
[ ( "bold", Encode.bool ansiStyle.bold )
|
||||||
, ( "underline", Encode.bool False )
|
, ( "underline", Encode.bool ansiStyle.underline )
|
||||||
, ( "color"
|
, ( "color"
|
||||||
, Encode.string <|
|
, Encode.string <|
|
||||||
case node of
|
case ansiStyle.color |> Maybe.withDefault Ansi.White of
|
||||||
RawText _ ->
|
Ansi.Red ->
|
||||||
"WHITE"
|
|
||||||
|
|
||||||
Style color _ ->
|
|
||||||
case color of
|
|
||||||
Red ->
|
|
||||||
"red"
|
"red"
|
||||||
|
|
||||||
Blue ->
|
Ansi.Blue ->
|
||||||
"blue"
|
"blue"
|
||||||
|
|
||||||
Green ->
|
Ansi.Green ->
|
||||||
"green"
|
"green"
|
||||||
|
|
||||||
Yellow ->
|
Ansi.Yellow ->
|
||||||
"yellow"
|
"yellow"
|
||||||
|
|
||||||
Cyan ->
|
Ansi.Cyan ->
|
||||||
"cyan"
|
"cyan"
|
||||||
|
|
||||||
|
Ansi.Black ->
|
||||||
|
"black"
|
||||||
|
|
||||||
|
Ansi.Magenta ->
|
||||||
|
"magenta"
|
||||||
|
|
||||||
|
Ansi.White ->
|
||||||
|
"white"
|
||||||
|
|
||||||
|
Ansi.BrightBlack ->
|
||||||
|
"BLACK"
|
||||||
|
|
||||||
|
Ansi.BrightRed ->
|
||||||
|
"RED"
|
||||||
|
|
||||||
|
Ansi.BrightGreen ->
|
||||||
|
"GREEN"
|
||||||
|
|
||||||
|
Ansi.BrightYellow ->
|
||||||
|
"YELLOW"
|
||||||
|
|
||||||
|
Ansi.BrightBlue ->
|
||||||
|
"BLUE"
|
||||||
|
|
||||||
|
Ansi.BrightMagenta ->
|
||||||
|
"MAGENTA"
|
||||||
|
|
||||||
|
Ansi.BrightCyan ->
|
||||||
|
"CYAN"
|
||||||
|
|
||||||
|
Ansi.BrightWhite ->
|
||||||
|
"WHITE"
|
||||||
|
|
||||||
|
Ansi.Custom _ _ _ ->
|
||||||
|
""
|
||||||
)
|
)
|
||||||
, ( "string", Encode.string (getString node) )
|
, ( "string", Encode.string string )
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
getString : Text -> String
|
|
||||||
getString node =
|
|
||||||
case node of
|
|
||||||
RawText string ->
|
|
||||||
string
|
|
||||||
|
|
||||||
Style _ innerNode ->
|
|
||||||
getString innerNode
|
|
||||||
|
@ -11,13 +11,12 @@ import Expect
|
|||||||
import Html
|
import Html
|
||||||
import Json.Decode as JD
|
import Json.Decode as JD
|
||||||
import Json.Encode as Encode
|
import Json.Encode as Encode
|
||||||
import List.Extra
|
|
||||||
import NotFoundReason
|
import NotFoundReason
|
||||||
import OptimizedDecoder as Decode exposing (Decoder)
|
import OptimizedDecoder as Decode exposing (Decoder)
|
||||||
import Pages.ContentCache as ContentCache exposing (ContentCache)
|
import Pages.ContentCache as ContentCache exposing (ContentCache)
|
||||||
import Pages.Internal.Platform.Cli exposing (..)
|
import Pages.Internal.Platform.Cli exposing (..)
|
||||||
import Pages.Internal.Platform.Effect as Effect exposing (Effect)
|
import Pages.Internal.Platform.Effect as Effect exposing (Effect)
|
||||||
import Pages.Internal.Platform.ToJsPayload as ToJsPayload exposing (ToJsPayload)
|
import Pages.Internal.Platform.ToJsPayload as ToJsPayload
|
||||||
import Pages.Internal.StaticHttpBody as StaticHttpBody
|
import Pages.Internal.StaticHttpBody as StaticHttpBody
|
||||||
import Pages.Manifest as Manifest
|
import Pages.Manifest as Manifest
|
||||||
import Pages.ProgramConfig exposing (ProgramConfig)
|
import Pages.ProgramConfig exposing (ProgramConfig)
|
||||||
@ -28,12 +27,11 @@ import ProgramTest exposing (ProgramTest)
|
|||||||
import Regex
|
import Regex
|
||||||
import RenderRequest
|
import RenderRequest
|
||||||
import Secrets
|
import Secrets
|
||||||
import Serialize
|
|
||||||
import SimulatedEffect.Cmd
|
import SimulatedEffect.Cmd
|
||||||
import SimulatedEffect.Http as Http
|
import SimulatedEffect.Http as Http
|
||||||
import SimulatedEffect.Ports
|
import SimulatedEffect.Ports
|
||||||
import SimulatedEffect.Task
|
import SimulatedEffect.Task
|
||||||
import Test exposing (Test, describe, only, test)
|
import Test exposing (Test, describe, test)
|
||||||
import Test.Http
|
import Test.Http
|
||||||
|
|
||||||
|
|
||||||
@ -52,13 +50,10 @@ all =
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"""{ "stargazer_count": 86 }"""
|
"""{ "stargazer_count": 86 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":86}"""
|
, """{"stargazer_count":86}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "StaticHttp request for initial are resolved" <|
|
, test "StaticHttp request for initial are resolved" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -76,13 +71,10 @@ all =
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"""{ "stargazer_count": 86 }"""
|
"""{ "stargazer_count": 86 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( "post-1"
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":86}"""
|
, """{"stargazer_count":86}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, describe "single page renders"
|
, describe "single page renders"
|
||||||
[ test "single pages that are pre-rendered" <|
|
[ test "single pages that are pre-rendered" <|
|
||||||
\() ->
|
\() ->
|
||||||
@ -151,13 +143,10 @@ all =
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"""{ "stargazer_count": 86, "language": "Elm" }"""
|
"""{ "stargazer_count": 86, "language": "Elm" }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( "post-1"
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":86,"language":"Elm"}"""
|
, """{"stargazer_count":86,"language":"Elm"}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "andThen" <|
|
, test "andThen" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -178,16 +167,13 @@ all =
|
|||||||
"NEXT-REQUEST"
|
"NEXT-REQUEST"
|
||||||
"""null"""
|
"""null"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( "elm-pages"
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """null"""
|
, """null"""
|
||||||
)
|
)
|
||||||
, ( get "NEXT-REQUEST"
|
, ( get "NEXT-REQUEST"
|
||||||
, """null"""
|
, """null"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "andThen chain avoids repeat requests" <|
|
, test "andThen chain avoids repeat requests" <|
|
||||||
\() ->
|
\() ->
|
||||||
let
|
let
|
||||||
@ -272,8 +258,7 @@ all =
|
|||||||
"url10"
|
"url10"
|
||||||
"""{"image": "image10.jpg"}"""
|
"""{"image": "image10.jpg"}"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( "elm-pages"
|
[ ( get "https://pokeapi.co/api/v2/pokemon/"
|
||||||
, [ ( get "https://pokeapi.co/api/v2/pokemon/"
|
|
||||||
, """[{"url":"url1"},{"url":"url2"},{"url":"url3"},{"url":"url4"},{"url":"url5"},{"url":"url6"},{"url":"url7"},{"url":"url8"},{"url":"url9"},{"url":"url10"}]"""
|
, """[{"url":"url1"},{"url":"url2"},{"url":"url3"},{"url":"url4"},{"url":"url5"},{"url":"url6"},{"url":"url7"},{"url":"url8"},{"url":"url9"},{"url":"url10"}]"""
|
||||||
)
|
)
|
||||||
, ( get "url1"
|
, ( get "url1"
|
||||||
@ -307,40 +292,39 @@ all =
|
|||||||
, """{"image":"image10.jpg"}"""
|
, """{"image":"image10.jpg"}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
--, test "port is sent out once all requests are finished" <|
|
||||||
, test "port is sent out once all requests are finished" <|
|
-- \() ->
|
||||||
\() ->
|
-- start
|
||||||
start
|
-- [ ( [ "elm-pages" ]
|
||||||
[ ( [ "elm-pages" ]
|
-- , DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages") starDecoder
|
||||||
, DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages") starDecoder
|
-- )
|
||||||
)
|
-- , ( [ "elm-pages-starter" ]
|
||||||
, ( [ "elm-pages-starter" ]
|
-- , DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages-starter") starDecoder
|
||||||
, DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages-starter") starDecoder
|
-- )
|
||||||
)
|
-- ]
|
||||||
]
|
-- |> ProgramTest.simulateHttpOk
|
||||||
|> ProgramTest.simulateHttpOk
|
-- "GET"
|
||||||
"GET"
|
-- "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
-- """{ "stargazer_count": 86 }"""
|
||||||
"""{ "stargazer_count": 86 }"""
|
-- |> ProgramTest.simulateHttpOk
|
||||||
|> ProgramTest.simulateHttpOk
|
-- "GET"
|
||||||
"GET"
|
-- "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||||
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
-- """{ "stargazer_count": 22 }"""
|
||||||
"""{ "stargazer_count": 22 }"""
|
-- |> expectSuccess
|
||||||
|> expectSuccess
|
-- [ ( "elm-pages"
|
||||||
[ ( "elm-pages"
|
-- , [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
-- , """{"stargazer_count":86}"""
|
||||||
, """{"stargazer_count":86}"""
|
-- )
|
||||||
)
|
-- ]
|
||||||
]
|
-- )
|
||||||
)
|
-- , ( "elm-pages-starter"
|
||||||
, ( "elm-pages-starter"
|
-- , [ ( get "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
-- , """{"stargazer_count":22}"""
|
||||||
, """{"stargazer_count":22}"""
|
-- )
|
||||||
)
|
-- ]
|
||||||
]
|
-- )
|
||||||
)
|
-- ]
|
||||||
]
|
|
||||||
, test "reduced JSON is sent out" <|
|
, test "reduced JSON is sent out" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -353,13 +337,10 @@ all =
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":86}"""
|
, """{"stargazer_count":86}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "you can use elm/json decoders with StaticHttp.unoptimizedRequest" <|
|
, test "you can use elm/json decoders with StaticHttp.unoptimizedRequest" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -382,13 +363,10 @@ all =
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{ "stargazer_count": 86, "unused_field": 123 }"""
|
, """{ "stargazer_count": 86, "unused_field": 123 }"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "plain string" <|
|
, test "plain string" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -409,13 +387,10 @@ all =
|
|||||||
"https://example.com/file.txt"
|
"https://example.com/file.txt"
|
||||||
"This is a raw text file."
|
"This is a raw text file."
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://example.com/file.txt"
|
||||||
, [ ( get "https://example.com/file.txt"
|
|
||||||
, "This is a raw text file."
|
, "This is a raw text file."
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "Err in String to Result function turns into decode error" <|
|
, test "Err in String to Result function turns into decode error" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -445,7 +420,7 @@ all =
|
|||||||
"This is a raw text file."
|
"This is a raw text file."
|
||||||
|> ProgramTest.expectOutgoingPortValues
|
|> ProgramTest.expectOutgoingPortValues
|
||||||
"toJsPort"
|
"toJsPort"
|
||||||
(Codec.decoder ToJsPayload.toJsCodec)
|
(Codec.decoder (ToJsPayload.successCodecNew2 "" ""))
|
||||||
(expectErrorsPort
|
(expectErrorsPort
|
||||||
"""-- STATIC HTTP DECODING ERROR ----------------------------------------------------- elm-pages
|
"""-- STATIC HTTP DECODING ERROR ----------------------------------------------------- elm-pages
|
||||||
|
|
||||||
@ -473,8 +448,7 @@ String was not uppercased"""
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( { method = "POST"
|
||||||
, [ ( { method = "POST"
|
|
||||||
, url = "https://api.github.com/repos/dillonkearns/elm-pages"
|
, url = "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, headers = []
|
, headers = []
|
||||||
, body = DataSource.emptyBody
|
, body = DataSource.emptyBody
|
||||||
@ -482,8 +456,6 @@ String was not uppercased"""
|
|||||||
, """{"stargazer_count":86}"""
|
, """{"stargazer_count":86}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "json is reduced from andThen chains" <|
|
, test "json is reduced from andThen chains" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -504,16 +476,13 @@ String was not uppercased"""
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||||
"""{ "stargazer_count": 50, "unused_field": 456 }"""
|
"""{ "stargazer_count": 50, "unused_field": 456 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":100}"""
|
, """{"stargazer_count":100}"""
|
||||||
)
|
)
|
||||||
, ( get "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
, ( get "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||||
, """{"stargazer_count":50}"""
|
, """{"stargazer_count":50}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "reduced json is preserved by StaticHttp.map2" <|
|
, test "reduced json is preserved by StaticHttp.map2" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -532,16 +501,13 @@ String was not uppercased"""
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||||
"""{ "stargazer_count": 50, "unused_field": 456 }"""
|
"""{ "stargazer_count": 50, "unused_field": 456 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":100}"""
|
, """{"stargazer_count":100}"""
|
||||||
)
|
)
|
||||||
, ( get "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
, ( get "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||||
, """{"stargazer_count":50}"""
|
, """{"stargazer_count":50}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "the port sends out even if there are no http requests" <|
|
, test "the port sends out even if there are no http requests" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -549,7 +515,7 @@ String was not uppercased"""
|
|||||||
, DataSource.succeed ()
|
, DataSource.succeed ()
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|> expectSuccess [ ( "", [] ) ]
|
|> expectSuccess []
|
||||||
, test "the port sends out when there are duplicate http requests for the same page" <|
|
, test "the port sends out when there are duplicate http requests for the same page" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -564,13 +530,10 @@ String was not uppercased"""
|
|||||||
"http://example.com"
|
"http://example.com"
|
||||||
"""null"""
|
"""null"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "http://example.com"
|
||||||
, [ ( get "http://example.com"
|
|
||||||
, """null"""
|
, """null"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "an error is sent out for decoder failures" <|
|
, test "an error is sent out for decoder failures" <|
|
||||||
\() ->
|
\() ->
|
||||||
start
|
start
|
||||||
@ -584,7 +547,7 @@ String was not uppercased"""
|
|||||||
"""{ "stargazer_count": 86 }"""
|
"""{ "stargazer_count": 86 }"""
|
||||||
|> ProgramTest.expectOutgoingPortValues
|
|> ProgramTest.expectOutgoingPortValues
|
||||||
"toJsPort"
|
"toJsPort"
|
||||||
(Codec.decoder ToJsPayload.toJsCodec)
|
(Codec.decoder (ToJsPayload.successCodecNew2 "" ""))
|
||||||
(expectErrorsPort
|
(expectErrorsPort
|
||||||
"""-- STATIC HTTP DECODING ERROR ----------------------------------------------------- elm-pages
|
"""-- STATIC HTTP DECODING ERROR ----------------------------------------------------- elm-pages
|
||||||
|
|
||||||
@ -627,7 +590,7 @@ I encountered some errors while decoding this JSON:
|
|||||||
""" "continuation-url" """
|
""" "continuation-url" """
|
||||||
|> ProgramTest.expectOutgoingPortValues
|
|> ProgramTest.expectOutgoingPortValues
|
||||||
"toJsPort"
|
"toJsPort"
|
||||||
(Codec.decoder ToJsPayload.toJsCodec)
|
(Codec.decoder (ToJsPayload.successCodecNew2 "" ""))
|
||||||
(expectErrorsPort
|
(expectErrorsPort
|
||||||
"""-- MISSING SECRET ----------------------------------------------------- elm-pages
|
"""-- MISSING SECRET ----------------------------------------------------- elm-pages
|
||||||
|
|
||||||
@ -656,21 +619,8 @@ So maybe MISSING should be API_KEY"""
|
|||||||
)
|
)
|
||||||
|> ProgramTest.expectOutgoingPortValues
|
|> ProgramTest.expectOutgoingPortValues
|
||||||
"toJsPort"
|
"toJsPort"
|
||||||
(Codec.decoder ToJsPayload.toJsCodec)
|
(Codec.decoder (ToJsPayload.successCodecNew2 "" ""))
|
||||||
(expectErrorsPort """-- STATIC HTTP ERROR ----------------------------------------------------- elm-pages
|
(expectErrorsPort """-- STATIC HTTP ERROR ----------------------------------------------------- elm-pages
|
||||||
|
|
||||||
I got an error making an HTTP request to this URL: https://api.github.com/repos/dillonkearns/elm-pages
|
|
||||||
|
|
||||||
Bad status: 404
|
|
||||||
Status message: TODO: if you need this, please report to https://github.com/avh4/elm-program-test/issues
|
|
||||||
Body:
|
|
||||||
|
|
||||||
-- STATIC HTTP DECODING ERROR ----------------------------------------------------- elm-pages
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Payload sent back invalid JSON
|
|
||||||
TODO
|
|
||||||
""")
|
""")
|
||||||
, test "uses real secrets to perform request and masked secrets to store and lookup response" <|
|
, test "uses real secrets to perform request and masked secrets to store and lookup response" <|
|
||||||
\() ->
|
\() ->
|
||||||
@ -707,8 +657,7 @@ TODO
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( { method = "GET"
|
||||||
, [ ( { method = "GET"
|
|
||||||
, url = "https://api.github.com/repos/dillonkearns/elm-pages?apiKey=<API_KEY>"
|
, url = "https://api.github.com/repos/dillonkearns/elm-pages?apiKey=<API_KEY>"
|
||||||
, headers =
|
, headers =
|
||||||
[ ( "Authorization", "Bearer <BEARER>" )
|
[ ( "Authorization", "Bearer <BEARER>" )
|
||||||
@ -718,8 +667,6 @@ TODO
|
|||||||
, """{}"""
|
, """{}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, describe "staticHttpCache"
|
, describe "staticHttpCache"
|
||||||
[ test "it doesn't perform http requests that are provided in the http cache flag" <|
|
[ test "it doesn't perform http requests that are provided in the http cache flag" <|
|
||||||
\() ->
|
\() ->
|
||||||
@ -737,13 +684,10 @@ TODO
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":86}"""
|
, """{"stargazer_count":86}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "it ignores unused cache" <|
|
, test "it ignores unused cache" <|
|
||||||
\() ->
|
\() ->
|
||||||
startWithHttpCache
|
startWithHttpCache
|
||||||
@ -764,13 +708,10 @@ TODO
|
|||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
"""{ "stargazer_count": 86 }"""
|
"""{ "stargazer_count": 86 }"""
|
||||||
|> expectSuccess
|
|> expectSuccess
|
||||||
[ ( ""
|
[ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
|
||||||
, """{"stargazer_count":86}"""
|
, """{"stargazer_count":86}"""
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
|
||||||
]
|
|
||||||
, test "validate DataSource is not stored for any pages" <|
|
, test "validate DataSource is not stored for any pages" <|
|
||||||
\() ->
|
\() ->
|
||||||
startWithRoutes [ "hello" ]
|
startWithRoutes [ "hello" ]
|
||||||
@ -941,7 +882,25 @@ TODO
|
|||||||
[ {- ToJsPayload.Glob _, ToJsPayload.ReadFile _ -} ToJsPayload.PageProgress portData ] ->
|
[ {- ToJsPayload.Glob _, ToJsPayload.ReadFile _ -} ToJsPayload.PageProgress portData ] ->
|
||||||
portData.contentJson
|
portData.contentJson
|
||||||
|> Expect.equalDicts
|
|> Expect.equalDicts
|
||||||
(Dict.fromList [ ( "{\"method\":\"GET\",\"url\":\"file://content/glossary/hello.md\",\"headers\":[],\"body\":{\"type\":\"empty\"}}", "{\"withoutFrontmatter\":\"BODY\"}" ), ( "{\"method\":\"GET\",\"url\":\"glob://content/glossary/*.md\",\"headers\":[],\"body\":{\"type\":\"empty\"}}", "[\"content/glossary/hello.md\"]" ) ])
|
(Dict.fromList
|
||||||
|
[ ( Request.hash
|
||||||
|
{ method = "GET"
|
||||||
|
, url = "file://content/glossary/hello.md"
|
||||||
|
, headers = []
|
||||||
|
, body = DataSource.Http.emptyBody
|
||||||
|
}
|
||||||
|
, "{\"withoutFrontmatter\":\"BODY\"}"
|
||||||
|
)
|
||||||
|
, ( Request.hash
|
||||||
|
{ method = "GET"
|
||||||
|
, url = "glob://content/glossary/*.md"
|
||||||
|
, headers = []
|
||||||
|
, body = DataSource.Http.emptyBody
|
||||||
|
}
|
||||||
|
, "[\"content/glossary/hello.md\"]"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Expect.fail <|
|
Expect.fail <|
|
||||||
@ -1009,7 +968,7 @@ TODO
|
|||||||
"""{ "stargazer_count": 123 }"""
|
"""{ "stargazer_count": 123 }"""
|
||||||
|> ProgramTest.expectOutgoingPortValues
|
|> ProgramTest.expectOutgoingPortValues
|
||||||
"toJsPort"
|
"toJsPort"
|
||||||
(Codec.decoder ToJsPayload.toJsCodec)
|
(Codec.decoder (ToJsPayload.successCodecNew2 "" ""))
|
||||||
(expectErrorsPort """-- NON-UNIQUE DISTILL KEYS ----------------------------------------------------- elm-pages
|
(expectErrorsPort """-- NON-UNIQUE DISTILL KEYS ----------------------------------------------------- elm-pages
|
||||||
I encountered DataSource.distill with two matching keys that had differing encoded values.
|
I encountered DataSource.distill with two matching keys that had differing encoded values.
|
||||||
|
|
||||||
@ -1022,91 +981,92 @@ The second encoded value was:
|
|||||||
|
|
||||||
123""")
|
123""")
|
||||||
]
|
]
|
||||||
, describe "generateFiles"
|
|
||||||
[ test "initial requests are sent out" <|
|
--, describe "generateFiles"
|
||||||
\() ->
|
-- [ test "initial requests are sent out" <|
|
||||||
startLowLevel
|
-- \() ->
|
||||||
[ ApiRoute.succeed
|
-- startLowLevel
|
||||||
(DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
|
-- [ ApiRoute.succeed
|
||||||
(starDecoder
|
-- (DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
|
||||||
|> Decode.map
|
-- (starDecoder
|
||||||
(\starCount ->
|
-- |> Decode.map
|
||||||
{ body = "Star count: " ++ String.fromInt starCount
|
-- (\starCount ->
|
||||||
}
|
-- { body = "Star count: " ++ String.fromInt starCount
|
||||||
)
|
-- }
|
||||||
)
|
-- )
|
||||||
)
|
-- )
|
||||||
|> ApiRoute.literal "test.txt"
|
-- )
|
||||||
|> ApiRoute.single
|
-- |> ApiRoute.literal "test.txt"
|
||||||
]
|
-- |> ApiRoute.single
|
||||||
[]
|
-- ]
|
||||||
[]
|
-- []
|
||||||
|> ProgramTest.simulateHttpOk
|
-- []
|
||||||
"GET"
|
-- |> ProgramTest.simulateHttpOk
|
||||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
-- "GET"
|
||||||
"""{ "stargazer_count": 86 }"""
|
-- "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
|> expectSuccessNew
|
-- """{ "stargazer_count": 86 }"""
|
||||||
[]
|
-- |> expectSuccessNew
|
||||||
[ \success ->
|
-- []
|
||||||
success.filesToGenerate
|
-- [ \success ->
|
||||||
|> Expect.equal
|
-- success.filesToGenerate
|
||||||
[ { path = [ "test.txt" ]
|
-- |> Expect.equal
|
||||||
, content = "Star count: 86"
|
-- [ { path = [ "test.txt" ]
|
||||||
}
|
-- , content = "Star count: 86"
|
||||||
]
|
-- }
|
||||||
]
|
-- ]
|
||||||
, test "it sends success port when no HTTP requests are needed because they're all cached" <|
|
-- ]
|
||||||
\() ->
|
-- , test "it sends success port when no HTTP requests are needed because they're all cached" <|
|
||||||
startLowLevel
|
-- \() ->
|
||||||
[ ApiRoute.succeed
|
-- startLowLevel
|
||||||
(DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages-starter")
|
-- [ ApiRoute.succeed
|
||||||
(starDecoder
|
-- (DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages-starter")
|
||||||
|> Decode.map
|
-- (starDecoder
|
||||||
(\starCount ->
|
-- |> Decode.map
|
||||||
{ body = "Star count: " ++ String.fromInt starCount
|
-- (\starCount ->
|
||||||
}
|
-- { body = "Star count: " ++ String.fromInt starCount
|
||||||
)
|
-- }
|
||||||
)
|
-- )
|
||||||
)
|
-- )
|
||||||
|> ApiRoute.literal "test.txt"
|
-- )
|
||||||
|> ApiRoute.single
|
-- |> ApiRoute.literal "test.txt"
|
||||||
]
|
-- |> ApiRoute.single
|
||||||
[ ( { url = "https://api.github.com/repos/dillonkearns/elm-pages"
|
-- ]
|
||||||
, method = "GET"
|
-- [ ( { url = "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
, headers = []
|
-- , method = "GET"
|
||||||
, body = StaticHttpBody.EmptyBody
|
-- , headers = []
|
||||||
}
|
-- , body = StaticHttpBody.EmptyBody
|
||||||
, """{"stargazer_count":86}"""
|
-- }
|
||||||
)
|
-- , """{"stargazer_count":86}"""
|
||||||
, ( { url = "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
-- )
|
||||||
, method = "GET"
|
-- , ( { url = "https://api.github.com/repos/dillonkearns/elm-pages-starter"
|
||||||
, headers = []
|
-- , method = "GET"
|
||||||
, body = StaticHttpBody.EmptyBody
|
-- , headers = []
|
||||||
}
|
-- , body = StaticHttpBody.EmptyBody
|
||||||
, """{"stargazer_count":23}"""
|
-- }
|
||||||
)
|
-- , """{"stargazer_count":23}"""
|
||||||
]
|
-- )
|
||||||
[ ( []
|
-- ]
|
||||||
, DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages") starDecoder
|
-- [ ( []
|
||||||
)
|
-- , DataSource.Http.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages") starDecoder
|
||||||
]
|
-- )
|
||||||
|> expectSuccessNew
|
-- ]
|
||||||
[ ( ""
|
-- |> expectSuccessNew
|
||||||
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
-- [ ( ""
|
||||||
, """{"stargazer_count":86}"""
|
-- , [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
|
||||||
)
|
-- , """{"stargazer_count":86}"""
|
||||||
]
|
-- )
|
||||||
)
|
-- ]
|
||||||
]
|
-- )
|
||||||
[ \success ->
|
-- ]
|
||||||
success.filesToGenerate
|
-- [ \success ->
|
||||||
|> Expect.equal
|
-- success.filesToGenerate
|
||||||
[ { path = [ "test.txt" ]
|
-- |> Expect.equal
|
||||||
, content = "Star count: 23"
|
-- [ { path = [ "test.txt" ]
|
||||||
}
|
-- , content = "Star count: 23"
|
||||||
]
|
-- }
|
||||||
]
|
-- ]
|
||||||
]
|
-- ]
|
||||||
|
-- ]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -1138,6 +1098,15 @@ startLowLevel apiRoutes staticHttpCache pages =
|
|||||||
contentCache =
|
contentCache =
|
||||||
ContentCache.init Nothing
|
ContentCache.init Nothing
|
||||||
|
|
||||||
|
pageToLoad : List String
|
||||||
|
pageToLoad =
|
||||||
|
case pages |> List.head |> Maybe.map Tuple.first of
|
||||||
|
Just justPageToLoad ->
|
||||||
|
justPageToLoad
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
Debug.todo "Error - no pages"
|
||||||
|
|
||||||
config : ProgramConfig Msg () Route () () ()
|
config : ProgramConfig Msg () Route () () ()
|
||||||
config =
|
config =
|
||||||
{ toJsPort = toJsPort
|
{ toJsPort = toJsPort
|
||||||
@ -1154,6 +1123,7 @@ startLowLevel apiRoutes staticHttpCache pages =
|
|||||||
, handleRoute = \_ -> DataSource.succeed Nothing
|
, handleRoute = \_ -> DataSource.succeed Nothing
|
||||||
, urlToRoute = .path >> Route
|
, urlToRoute = .path >> Route
|
||||||
, update = \_ _ _ _ _ -> ( (), Cmd.none )
|
, update = \_ _ _ _ _ -> ( (), Cmd.none )
|
||||||
|
, basePath = []
|
||||||
, data =
|
, data =
|
||||||
\(Route pageRoute) ->
|
\(Route pageRoute) ->
|
||||||
let
|
let
|
||||||
@ -1239,7 +1209,19 @@ startLowLevel apiRoutes staticHttpCache pages =
|
|||||||
-> ( model, Effect pathKey )
|
-> ( model, Effect pathKey )
|
||||||
-}
|
-}
|
||||||
ProgramTest.createDocument
|
ProgramTest.createDocument
|
||||||
{ init = init RenderRequest.FullBuild contentCache config
|
{ init =
|
||||||
|
init
|
||||||
|
(RenderRequest.SinglePage
|
||||||
|
RenderRequest.OnlyJson
|
||||||
|
(RenderRequest.Page
|
||||||
|
{ path = Path.fromString (pageToLoad |> String.join "/")
|
||||||
|
, frontmatter = Route (pageToLoad |> String.join "/")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
(Encode.object [])
|
||||||
|
)
|
||||||
|
contentCache
|
||||||
|
config
|
||||||
, update = update contentCache config
|
, update = update contentCache config
|
||||||
, view = \_ -> { title = "", body = [] }
|
, view = \_ -> { title = "", body = [] }
|
||||||
}
|
}
|
||||||
@ -1284,6 +1266,7 @@ startWithRoutes pageToLoad staticRoutes staticHttpCache pages =
|
|||||||
|> DataSource.succeed
|
|> DataSource.succeed
|
||||||
, urlToRoute = .path >> Route
|
, urlToRoute = .path >> Route
|
||||||
, update = \_ _ _ _ _ -> ( (), Cmd.none )
|
, update = \_ _ _ _ _ -> ( (), Cmd.none )
|
||||||
|
, basePath = []
|
||||||
, data =
|
, data =
|
||||||
\(Route pageRoute) ->
|
\(Route pageRoute) ->
|
||||||
let
|
let
|
||||||
@ -1347,7 +1330,6 @@ startWithRoutes pageToLoad staticRoutes staticHttpCache pages =
|
|||||||
|> Dict.fromList
|
|> Dict.fromList
|
||||||
|> Encode.dict identity Encode.string
|
|> Encode.dict identity Encode.string
|
||||||
)
|
)
|
||||||
, ( "mode", Encode.string "elm-to-html-beta" )
|
|
||||||
, ( "staticHttpCache", encodedStaticHttpCache )
|
, ( "staticHttpCache", encodedStaticHttpCache )
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1410,20 +1392,13 @@ simulateEffects effect =
|
|||||||
Effect.NoEffect ->
|
Effect.NoEffect ->
|
||||||
SimulatedEffect.Cmd.none
|
SimulatedEffect.Cmd.none
|
||||||
|
|
||||||
Effect.SendJsData value ->
|
|
||||||
SimulatedEffect.Ports.send "toJsPort" (value |> Codec.encoder ToJsPayload.toJsCodec)
|
|
||||||
|
|
||||||
-- toJsPort value |> Cmd.map never
|
-- toJsPort value |> Cmd.map never
|
||||||
Effect.Batch list ->
|
Effect.Batch list ->
|
||||||
list
|
list
|
||||||
|> List.map simulateEffects
|
|> List.map simulateEffects
|
||||||
|> SimulatedEffect.Cmd.batch
|
|> SimulatedEffect.Cmd.batch
|
||||||
|
|
||||||
Effect.FetchHttp ({ unmasked } as requests) ->
|
Effect.FetchHttp { unmasked } ->
|
||||||
let
|
|
||||||
_ =
|
|
||||||
Debug.log "Fetching " unmasked.url
|
|
||||||
in
|
|
||||||
if unmasked.url |> String.startsWith "file://" then
|
if unmasked.url |> String.startsWith "file://" then
|
||||||
let
|
let
|
||||||
filePath : String
|
filePath : String
|
||||||
@ -1455,10 +1430,6 @@ simulateEffects effect =
|
|||||||
|> SimulatedEffect.Cmd.map never
|
|> SimulatedEffect.Cmd.map never
|
||||||
|
|
||||||
else
|
else
|
||||||
let
|
|
||||||
_ =
|
|
||||||
Debug.log "Fetching" unmasked.url
|
|
||||||
in
|
|
||||||
Http.request
|
Http.request
|
||||||
{ method = unmasked.method
|
{ method = unmasked.method
|
||||||
, url = unmasked.url
|
, url = unmasked.url
|
||||||
@ -1476,9 +1447,23 @@ simulateEffects effect =
|
|||||||
, expect =
|
, expect =
|
||||||
PagesHttp.expectString
|
PagesHttp.expectString
|
||||||
(\response ->
|
(\response ->
|
||||||
GotStaticHttpResponse
|
case response of
|
||||||
{ request = requests
|
Ok okResponse ->
|
||||||
, response = response
|
GotDataBatch
|
||||||
|
[ { request =
|
||||||
|
{ unmasked = unmasked
|
||||||
|
, masked = unmasked -- TODO use masked
|
||||||
|
}
|
||||||
|
, response = okResponse
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
GotBuildError
|
||||||
|
{ title = "Static HTTP Error"
|
||||||
|
, message = []
|
||||||
|
, fatal = True
|
||||||
|
, path = ""
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
, timeout = Nothing
|
, timeout = Nothing
|
||||||
@ -1508,7 +1493,7 @@ simulateEffects effect =
|
|||||||
SimulatedEffect.Cmd.none
|
SimulatedEffect.Cmd.none
|
||||||
|
|
||||||
|
|
||||||
expectErrorsPort : String -> List ToJsPayload -> Expect.Expectation
|
expectErrorsPort : String -> List ToJsPayload.ToJsSuccessPayloadNewCombined -> Expect.Expectation
|
||||||
expectErrorsPort expectedPlainString actualPorts =
|
expectErrorsPort expectedPlainString actualPorts =
|
||||||
case actualPorts of
|
case actualPorts of
|
||||||
[ ToJsPayload.Errors actualRichTerminalString ] ->
|
[ ToJsPayload.Errors actualRichTerminalString ] ->
|
||||||
@ -1528,10 +1513,12 @@ normalizeErrorExpectEqual : String -> String -> Expect.Expectation
|
|||||||
normalizeErrorExpectEqual expectedPlainString actualRichTerminalString =
|
normalizeErrorExpectEqual expectedPlainString actualRichTerminalString =
|
||||||
actualRichTerminalString
|
actualRichTerminalString
|
||||||
|> Regex.replace
|
|> Regex.replace
|
||||||
(Regex.fromString "\u{001B}\\[[0-9;]+m"
|
-- strip out all possible ANSI sequences
|
||||||
|
(Regex.fromString "(\\x9B|\\x1B\\[)[0-?]*[ -/]*[@-~]"
|
||||||
|> Maybe.withDefault Regex.never
|
|> Maybe.withDefault Regex.never
|
||||||
)
|
)
|
||||||
(\_ -> "")
|
(\_ -> "")
|
||||||
|
|> String.replace "\u{001B}" ""
|
||||||
|> normalizeNewlines
|
|> normalizeNewlines
|
||||||
|> Expect.equal
|
|> Expect.equal
|
||||||
(expectedPlainString |> normalizeNewlines)
|
(expectedPlainString |> normalizeNewlines)
|
||||||
@ -1546,6 +1533,10 @@ normalizeNewlines string =
|
|||||||
|> Regex.replace
|
|> Regex.replace
|
||||||
(Regex.fromString "( )+" |> Maybe.withDefault Regex.never)
|
(Regex.fromString "( )+" |> Maybe.withDefault Regex.never)
|
||||||
(\_ -> " ")
|
(\_ -> " ")
|
||||||
|
|> String.replace "\u{000D}" ""
|
||||||
|
|> Regex.replace
|
||||||
|
(Regex.fromString "\\s" |> Maybe.withDefault Regex.never)
|
||||||
|
(\_ -> "")
|
||||||
|
|
||||||
|
|
||||||
toJsPort : a -> Cmd msg
|
toJsPort : a -> Cmd msg
|
||||||
@ -1573,40 +1564,37 @@ starDecoder =
|
|||||||
Decode.field "stargazer_count" Decode.int
|
Decode.field "stargazer_count" Decode.int
|
||||||
|
|
||||||
|
|
||||||
expectSuccess : List ( String, List ( Request.Request, String ) ) -> ProgramTest model msg effect -> Expect.Expectation
|
expectSuccess : List ( Request.Request, String ) -> ProgramTest model msg effect -> Expect.Expectation
|
||||||
expectSuccess expectedRequests previous =
|
expectSuccess expectedRequests previous =
|
||||||
expectSuccessNew expectedRequests [] previous
|
expectSuccessNew expectedRequests [] previous
|
||||||
|
|
||||||
|
|
||||||
expectSuccessNew : List ( String, List ( Request.Request, String ) ) -> List (ToJsPayload.ToJsSuccessPayload -> Expect.Expectation) -> ProgramTest model msg effect -> Expect.Expectation
|
expectSuccessNew : List ( Request.Request, String ) -> List (ToJsPayload.ToJsSuccessPayloadNew -> Expect.Expectation) -> ProgramTest model msg effect -> Expect.Expectation
|
||||||
expectSuccessNew expectedRequests expectations previous =
|
expectSuccessNew expectedRequest expectations previous =
|
||||||
previous
|
previous
|
||||||
|> ProgramTest.expectOutgoingPortValues
|
|> ProgramTest.expectOutgoingPortValues
|
||||||
"toJsPort"
|
"toJsPort"
|
||||||
(Codec.decoder ToJsPayload.toJsCodec)
|
(Codec.decoder (ToJsPayload.successCodecNew2 "" ""))
|
||||||
(\value ->
|
(\value ->
|
||||||
case value of
|
case value of
|
||||||
(ToJsPayload.Success portPayload) :: _ ->
|
(ToJsPayload.PageProgress portPayload) :: _ ->
|
||||||
portPayload
|
let
|
||||||
|> Expect.all
|
singleExpectation : ToJsPayload.ToJsSuccessPayloadNew -> Expect.Expectation
|
||||||
((\subject ->
|
singleExpectation =
|
||||||
subject.pages
|
\subject ->
|
||||||
|> Expect.equalDicts
|
subject.contentJson
|
||||||
(expectedRequests
|
|> Expect.equal
|
||||||
|> List.map
|
(expectedRequest
|
||||||
(\( url, requests ) ->
|
|
||||||
( url
|
|
||||||
, requests
|
|
||||||
|> List.map
|
|> List.map
|
||||||
(\( request, response ) ->
|
(\( request, response ) ->
|
||||||
( Request.hash request, response )
|
( Request.hash request, response )
|
||||||
)
|
)
|
||||||
|> Dict.fromList
|
|> Dict.fromList
|
||||||
)
|
)
|
||||||
)
|
in
|
||||||
|> Dict.fromList
|
portPayload
|
||||||
)
|
|> Expect.all
|
||||||
)
|
(singleExpectation
|
||||||
:: expectations
|
:: expectations
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1631,7 +1619,27 @@ simulateSubscriptions _ =
|
|||||||
(JD.field "pattern" JD.string)
|
(JD.field "pattern" JD.string)
|
||||||
(JD.field "result" JD.value)
|
(JD.field "result" JD.value)
|
||||||
)
|
)
|
||||||
|> JD.map GotGlob
|
|> JD.map
|
||||||
|
(\( globPattern, response ) ->
|
||||||
|
GotDataBatch
|
||||||
|
[ { request =
|
||||||
|
{ masked =
|
||||||
|
{ url = "glob://" ++ globPattern
|
||||||
|
, method = "GET"
|
||||||
|
, headers = []
|
||||||
|
, body = StaticHttpBody.EmptyBody
|
||||||
|
}
|
||||||
|
, unmasked =
|
||||||
|
{ url = "glob://" ++ globPattern
|
||||||
|
, method = "GET"
|
||||||
|
, headers = []
|
||||||
|
, body = StaticHttpBody.EmptyBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, response = Encode.encode 0 response
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
"GotFile" ->
|
"GotFile" ->
|
||||||
JD.field "data"
|
JD.field "data"
|
||||||
@ -1639,7 +1647,27 @@ simulateSubscriptions _ =
|
|||||||
(JD.field "filePath" JD.string)
|
(JD.field "filePath" JD.string)
|
||||||
JD.value
|
JD.value
|
||||||
)
|
)
|
||||||
|> JD.map GotStaticFile
|
|> JD.map
|
||||||
|
(\( filePath, response ) ->
|
||||||
|
GotDataBatch
|
||||||
|
[ { request =
|
||||||
|
{ masked =
|
||||||
|
{ url = "file://" ++ filePath
|
||||||
|
, method = "GET"
|
||||||
|
, headers = []
|
||||||
|
, body = StaticHttpBody.EmptyBody
|
||||||
|
}
|
||||||
|
, unmasked =
|
||||||
|
{ url = "file://" ++ filePath
|
||||||
|
, method = "GET"
|
||||||
|
, headers = []
|
||||||
|
, body = StaticHttpBody.EmptyBody
|
||||||
|
}
|
||||||
|
}
|
||||||
|
, response = Encode.encode 0 response
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
JD.fail "Unexpected subscription tag."
|
JD.fail "Unexpected subscription tag."
|
||||||
|
Loading…
Reference in New Issue
Block a user