Merge branch 'master' into phantom-builder

This commit is contained in:
Dillon Kearns 2020-04-07 16:59:02 -07:00
commit a6dcde5dde
42 changed files with 865 additions and 761 deletions

View File

@ -22,6 +22,24 @@
"contributions": [
"code"
]
},
{
"login": "Y0hy0h",
"name": "Johannes Maas",
"avatar_url": "https://avatars0.githubusercontent.com/u/11377826?v=4",
"profile": "https://github.com/Y0hy0h",
"contributions": [
"userTesting"
]
},
{
"login": "vViktorPL",
"name": "Wiktor Toporek",
"avatar_url": "https://avatars1.githubusercontent.com/u/2961541?v=4",
"profile": "https://github.com/vViktorPL",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,

119
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,119 @@
name: Elm CI
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 13
uses: actions/setup-node@v1
with:
node-version: 13
- uses: actions/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- uses: actions/cache@v1
id: elm-cache
with:
path: ~/.elm
key: ${{ runner.os }}-elm-home-${{ hashFiles('**/elm.json') }}
- run: npm ci
- run: ./node_modules/.bin/elm make --output /dev/null && cd generator && ../node_modules/.bin/elm make --output /dev/null src/Main.elm
- run: npm test
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 13
uses: actions/setup-node@v1
with:
node-version: 13
- uses: actions/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- uses: actions/cache@v1
id: elm-cache
with:
path: ~/.elm
key: ${{ runner.os }}-elm-home-${{ hashFiles('**/elm.json') }}
- run: npm ci
- run: ./node_modules/.bin/elm make --output /dev/null && cd generator && ../node_modules/.bin/elm make --output /dev/null src/Main.elm
- name: elm-format
run: npx --no-install elm-format --validate
validate-package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 13
uses: actions/setup-node@v1
with:
node-version: 13
- uses: actions/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- uses: actions/cache@v1
id: elm-cache
with:
path: ~/.elm
key: ${{ runner.os }}-elm-home-${{ hashFiles('**/elm.json') }}
- run: npm ci
- run: ./node_modules/.bin/elm make --output /dev/null && cd generator && ../node_modules/.bin/elm make --output /dev/null src/Main.elm
- name: Build elm docs
run: ./node_modules/.bin/elm make --docs docs.json
publish:
needs: [test, lint, validate-package]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 13
uses: actions/setup-node@v1
with:
node-version: 13
- uses: actions/cache@v1
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- uses: actions/cache@v1
id: elm-cache
with:
path: ~/.elm
key: ${{ runner.os }}-elm-home-${{ hashFiles('**/elm.json') }}
- run: npm ci
- run: ./node_modules/.bin/elm make --output /dev/null && cd generator && ../node_modules/.bin/elm make --output /dev/null src/Main.elm
- name: Elm Publish
uses: dillonkearns/elm-publish-action@1.0.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-elm: ./node_modules/.bin/elm
- run: npm run build
- id: npm-publish
if: github.ref == 'refs/heads/master'
uses: JS-DevTools/npm-publish@v1
with:
token: ${{ secrets.NPM_TOKEN }}

View File

@ -1,19 +0,0 @@
language: elm
elm: latest-0.19.1
elm-test: latest-0.19.1
elm-format: 0.8.2
install:
- npm ci
script:
- npm test
deploy:
- provider: npm
email: dillon@dillonkearns.com
api_key: $NPM_TOKEN
skip_cleanup: true
on:
all_branches: true
condition: $TRAVIS_TAG =~ ^v

View File

@ -9,6 +9,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
## [4.0.1] - 2020-03-28
### Added
- You can now host your `elm-pages` site in a sub-directory. For example, you could host it at mysite.com/blog, where the top-level mysite.com/ is hosting a different app.
This works using [HTML `<base>` tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base). The paths you get from `PagePath.toString` and `ImagePath.toString`
will use relative paths (e.g. `blog/my-article`) instead of absolute URLs (e.g. `/blog/my-article`), so you can take advantage of this functionality by just making sure you
use the path helpers and don't hardcode any absolute URL strings. See https://github.com/dillonkearns/elm-pages/pull/73.
## [4.0.0] - 2020-03-04
### Changed

View File

@ -9,6 +9,23 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
## [1.2.12] - 2020-03-28
### Added
- You can now host your `elm-pages` site in a sub-directory. For example, you could host it at mysite.com/blog, where the top-level mysite.com/ is hosting a different app.
This works using [HTML `<base>` tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base). The paths you get from `PagePath.toString` and `ImagePath.toString`
will use relative paths (e.g. `blog/my-article`) instead of absolute URLs (e.g. `/blog/my-article`), so you can take advantage of this functionality by just making sure you
use the path helpers and don't hardcode any absolute URL strings. See https://github.com/dillonkearns/elm-pages/pull/73.
## [1.2.11] - 2020-03-18
### Fixed
- Triple quoted strings in content files are now escaped properly (see [#26](https://github.com/dillonkearns/elm-pages/issues/26)).
- Fixed a path delimiter bug for Windows. Dev server appears to work smoothly on Windows now. See [#82](https://github.com/dillonkearns/elm-pages/pull/82).
There's currently an issue with running a production build on windows because of Google Closure Compiler. We're investigating possible fixes.
A big thank you [@vViktorPL](https://github.com/vViktorPL) for these two fixes!
## [1.2.10] - 2020-02-25
- Turn off offline service worker fallbacks for now. This will likely be revisited
@ -24,116 +41,136 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [1.2.8] - 2020-02-08
### Fixed
- Colorize elm make output for initial elm-pages build step. See [#66](https://github.com/dillonkearns/elm-pages/issues/66).
Note, this patch still hasn't propogated through to `elm-webpack-loader` (see https://github.com/elm-community/elm-webpack-loader/issues/166).
So there may still be non-colorized output for errors as you make changes while the dev server is running.
Note, this patch still hasn't propogated through to `elm-webpack-loader` (see https://github.com/elm-community/elm-webpack-loader/issues/166).
So there may still be non-colorized output for errors as you make changes while the dev server is running.
## [1.2.7] - 2020-02-03
### Fixed
- Don't serve fallback HTML from service worker when a page 404s... only when it fails to load (i.e. when
you're offline). 404s will go through from the server if you're online now.
you're offline). 404s will go through from the server if you're online now.
## [1.2.6] - 2020-02-03
### Fixed
- Only serve up the root route's HTML as a fallback when you're offline. This fixes the flash of root page content
when you are online. When you're offline, you will currently still see the root page flash when you load a page,
but you will be able to navigate to any cached pages as long as their content.json is in the service worker cache.
when you are online. When you're offline, you will currently still see the root page flash when you load a page,
but you will be able to navigate to any cached pages as long as their content.json is in the service worker cache.
## [1.2.5] - 2020-01-31
### Fixed
- Make sure that pre-render trigger event fires to fix pre-rendering hanging.
## [1.2.4] - 2020-01-30
### Fixed
- Don't pre-fetch content.json files for unknown paths: https://github.com/dillonkearns/elm-pages/pull/60.
- Fix race condition where pre-rendered content sometimes didn't have body: https://github.com/dillonkearns/elm-pages/pull/62.
## [1.2.2] - 2020-01-27
### Fixed
- Dev server only terminates with unrecoverable build errors, and now will
continue running with recoverable errors like metadata parsing errors.
See [#58](https://github.com/dillonkearns/elm-pages/pull/58).
continue running with recoverable errors like metadata parsing errors.
See [#58](https://github.com/dillonkearns/elm-pages/pull/58).
### Added
- The `pagesInit` function that wraps the way you initialize your app in `index.js` now returns a Promise
so you can wire up ports to it once it's initialized. See [#50](https://github.com/dillonkearns/elm-pages/pull/50).
Thank you [@icidasset](https://github.com/icidasset)! 🙏
so you can wire up ports to it once it's initialized. See [#50](https://github.com/dillonkearns/elm-pages/pull/50).
Thank you [@icidasset](https://github.com/icidasset)! 🙏
## [1.2.1] - 2020-01-20
### Fixed
- Removed a couple of debug console.log statements from the CLI.
## [1.2.0] - 2020-01-20
### Changed
- Changed the CLI generator to expect code from the new Elm package from the new
`generateFiles` hook in `Pages.Platform.application`.
`generateFiles` hook in `Pages.Platform.application`.
## [1.1.8] - 2020-01-20
### Fixed
- "Missing content" message no longer flashes between pre-rendered HTML and the Elm app hydrating and taking over the page. See [#48](https://github.com/dillonkearns/elm-pages/pull/48).
## [1.1.7] - 2020-01-12
### Fixed
- Newlines and escaped double quotes (`"`s) are handled properly in content frontmatter now. See [#41](https://github.com/dillonkearns/elm-pages/pull/41). Thank you [Luke](https://github.com/lukewestby)! 🎉🙏
## [1.1.6] - 2020-01-04
### Added
- Added hot reloading for code changes! That means that in dev mode (`elm-pages develop`),
you can change your code and the changes will be reloaded in your browser for you instantly.
Note that changing files in your `content` folder won't yet be instantly reloaded, that will
be a future task. See [#35](https://github.com/dillonkearns/elm-pages/pull/35).
you can change your code and the changes will be reloaded in your browser for you instantly.
Note that changing files in your `content` folder won't yet be instantly reloaded, that will
be a future task. See [#35](https://github.com/dillonkearns/elm-pages/pull/35).
## [1.1.5] - 2020-01-03
### Fixed
- Fixed the bug that showed blank pages and failed page reloads when you change files in the `content` folder. Thank you so much [@danmarcab](https://github.com/danmarcab) for contributing the fix! See [#23](https://github.com/dillonkearns/elm-pages/pull/23).
## [1.1.4] - 2020-01-03
### Changed
- Updated `favicons-webpack-plugin` to latest version. Had to upgrade to `html-webpack-plugin@4.0.0-beta.11`
for this. See [#32](https://github.com/dillonkearns/elm-pages/issues/32).
## [1.1.3] - 2020-01-03
*Check out [this upgrade checklist](https://github.com/dillonkearns/elm-pages/blob/master/docs/upgrade-guide.md#upgrading-to-elm-package-110-and-npm-package-113) for more details and steps for upgrading your project.
\*Check out [this upgrade checklist](https://github.com/dillonkearns/elm-pages/blob/master/docs/upgrade-guide.md#upgrading-to-elm-package-110-and-npm-package-113) for more details and steps for upgrading your project.
### Changed
- Added `StaticHttp` requests in the CLI process (see the Elm package changelog).
## [1.0.41] - 2019-11-14
### Fixed
- Fixed a regression where elm-markup frontmatter was being incorrectly parsed as JSON
(fixes [#20](https://github.com/dillonkearns/elm-pages/issues/20)).
(fixes [#20](https://github.com/dillonkearns/elm-pages/issues/20)).
## [1.0.40] - 2019-11-04
### Fixed
- Generate files for extensions other than `.md` and `.emu` (fixes [#16](https://github.com/dillonkearns/elm-pages/issues/16)).
As always, be sure to also use the latest Elm package.
As always, be sure to also use the latest Elm package.
### Added
- Ability to use a custom port for dev server ([#10](https://github.com/dillonkearns/elm-pages/pull/10); thank you [@leojpod](https://github.com/leojpod)! 🎉)
## [1.0.39] - 2019-10-18
### Fixed
- Use hidden `<div>` to listen for Elm view renders instead of wrapping entire
page in an extra div. Fixes [#5](https://github.com/dillonkearns/elm-pages/issues/5).
page in an extra div. Fixes [#5](https://github.com/dillonkearns/elm-pages/issues/5).
### Changed
- Add `onPageChange : PagePath Pages.PathKey -> userMsg` field to `Pages.application` config record.
This is analagous to `onUrlChange` in `Browser.application`, except that you get a
type-safe `PagePath Pages.PathKey` because it is guaranteed that you will only
go to one of your static routes when this `Msg` is fired. Fixes [#4](https://github.com/dillonkearns/elm-pages/issues/4).
This is analagous to `onUrlChange` in `Browser.application`, except that you get a
type-safe `PagePath Pages.PathKey` because it is guaranteed that you will only
go to one of your static routes when this `Msg` is fired. Fixes [#4](https://github.com/dillonkearns/elm-pages/issues/4).

View File

@ -1,6 +1,9 @@
# `elm-pages` [![Netlify Status](https://api.netlify.com/api/v1/badges/8ee4a674-4f37-4f16-b99e-607c0a02ee75/deploy-status)](https://app.netlify.com/sites/elm-pages/deploys) [![Build Status](https://travis-ci.org/dillonkearns/elm-pages.svg?branch=master)](https://travis-ci.org/dillonkearns/elm-pages) [![npm](https://img.shields.io/npm/v/elm-pages.svg)](https://npmjs.com/package/elm-pages) [![Elm package](https://img.shields.io/elm-package/v/dillonkearns/elm-pages.svg)](https://package.elm-lang.org/packages/dillonkearns/elm-pages/latest/)
# `elm-pages` [![Netlify Status](https://api.netlify.com/api/v1/badges/8ee4a674-4f37-4f16-b99e-607c0a02ee75/deploy-status)](https://app.netlify.com/sites/elm-pages/deploys) [![Build Status](https://github.com/dillonkearns/elm-pages/workflows/Elm%20CI/badge.svg)](https://github.com/dillonkearns/elm-pages/actions?query=branch%3Amaster) [![npm](https://img.shields.io/npm/v/elm-pages.svg)](https://npmjs.com/package/elm-pages) [![Elm package](https://img.shields.io/elm-package/v/dillonkearns/elm-pages.svg)](https://package.elm-lang.org/packages/dillonkearns/elm-pages/latest/)
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/dillonkearns/elm-pages-starter)
@ -160,11 +163,14 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<tr>
<td align="center"><a href="https://github.com/danmarcab"><img src="https://avatars2.githubusercontent.com/u/1517969?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Marín</b></sub></a><br /><a href="https://github.com/dillonkearns/elm-pages/commits?author=danmarcab" title="Code">💻</a></td>
<td align="center"><a href="https://citric.id"><img src="https://avatars1.githubusercontent.com/u/296665?v=4" width="100px;" alt=""/><br /><sub><b>Steven Vandevelde</b></sub></a><br /><a href="https://github.com/dillonkearns/elm-pages/commits?author=icidasset" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Y0hy0h"><img src="https://avatars0.githubusercontent.com/u/11377826?v=4" width="100px;" alt=""/><br /><sub><b>Johannes Maas</b></sub></a><br /><a href="#userTesting-Y0hy0h" title="User Testing">📓</a></td>
<td align="center"><a href="https://github.com/vViktorPL"><img src="https://avatars1.githubusercontent.com/u/2961541?v=4" width="100px;" alt=""/><br /><sub><b>Wiktor Toporek</b></sub></a><br /><a href="https://github.com/dillonkearns/elm-pages/commits?author=vViktorPL" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

22
docs/FAQ.md Normal file
View File

@ -0,0 +1,22 @@
## Can you pass flags in to your `elm-pages` app?
There's no way to pass flags in right now. I'm collecting use cases and trying to figure out what the most intuitive thing would be conceptual, given that the value of flags will be different during Pre-Rendering and Client-Side Rendering.
So for example, if you get the window dimensions from the flags and do responsive design based on that, then you'll see a flash after the client-side code takes over since it will run with a different value for flags. So that semantics of the flags are not quite intuitive there. You can achieve the same thing with a port, but the semantics are a little more obvious there because you now have to explicitly say how to handle the case where you don't have access to flags.
## How do you handle responsive layouts when you don't the browser dimensions at build time?
A lot of users are building their `elm-pages` views with `elm-ui`, so this is a common question because
`elm-ui` is designed to do responsive layouts by storing the browser dimensions in the Model and
doing conditionals based on that state.
With `elm-pages`, and static sites in general, we are building pre-rendered HTML so we can serve it up
really quickly through a CDN, rather than serving it up with a traditional server framework. That means
that to have responsive pages that don't have a page flash, we need to use media queries to make our pages responsive.
That way, the view is the same no matter what the dimensions are, so it will pre-render and look right on whatever
device the user is on because the media queries will take care of making it responsive.
Since `elm-ui` isn't currently built with media queries in mind, it isn't a first-class experience to use them with
`elm-ui`. One workaround you can use is to define some responsive classes that simply show or hide an element based on
a media query, and apply those classes. For example, you could show the mobile or desktop version of the navbar
by having a `mobile-responsive` and `desktop-responsive` class and rendering one element with each respsective class.
But the media query will only show one at a time based on the dimensions.

View File

@ -3,7 +3,7 @@
"name": "dillonkearns/elm-pages",
"summary": "A statically typed site generator.",
"license": "BSD-3-Clause",
"version": "4.0.0",
"version": "4.0.1",
"exposed-modules": [
"Head",
"Head.Seo",

View File

@ -4,7 +4,7 @@
"author": "Dillon Kearns",
"title": "Extensible Markdown Parsing in Pure Elm",
"description": "Introducing a new parser that extends your palette with no additional syntax",
"image": "/images/article-covers/extensible-markdown-parsing.jpg",
"image": "images/article-covers/extensible-markdown-parsing.jpg",
"published": "2019-10-08",
}
---

View File

@ -5,7 +5,7 @@
"author": "Dillon Kearns",
"title": "Generating Files with Pure Elm",
"description": "Introducing a new parser that extends your palette with no additional syntax",
"image": "/images/article-covers/generating-files.jpg",
"image": "images/article-covers/generating-files.jpg",
"published": "2020-01-28",
}
---

View File

@ -4,7 +4,7 @@
"author": "Dillon Kearns",
"title": "Introducing elm-pages 🚀 - a type-centric static site generator",
"description": "Elm is the perfect fit for a static site generator. Learn about some of the features and philosophy behind elm-pages.",
"image": "/images/article-covers/introducing-elm-pages.jpg",
"image": "images/article-covers/introducing-elm-pages.jpg",
"published": "2019-09-24",
}
---

View File

@ -4,7 +4,7 @@
"author": "Dillon Kearns",
"title": "A is for API - Introducing Static HTTP Requests",
"description": "",
"image": "/images/article-covers/static-http.jpg",
"image": "images/article-covers/static-http.jpg",
"published": "2019-12-10",
}
---
@ -85,7 +85,7 @@ head : Int -> List (Head.Tag Pages.PathKey)
head starCount =
Seo.summaryLarge
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages - "
, siteName = "elm-pages - "
++ String.fromInt starCount
++ " GitHub Stars"
, image =
@ -197,4 +197,4 @@ You can [take a look at this an end-to-end example app that uses the new `Static
Or just use the [`elm-pages-starter` repo](https://github.com/dillonkearns/elm-pages-starter) and start building something cool! Let me know your thoughts on Slack, I'd love to hear from you! Or continue the conversation on Twitter!
<Oembed url="https://twitter.com/dillontkearns/status/1214238507163471872" />
<Oembed url="https://twitter.com/dillontkearns/status/1214238507163471872" />

View File

@ -4,7 +4,7 @@
"author": "Dillon Kearns",
"title": "Types Over Conventions",
"description": "How elm-pages approaches configuration, using type-safe Elm.",
"image": "/images/article-covers/introducing-elm-pages.jpg",
"image": "images/article-covers/introducing-elm-pages.jpg",
"draft": true,
"published": "2019-09-21",
}
@ -164,6 +164,6 @@ Now, in our `elm-pages` app, our `view` function will get the markdown that we r
## Takeaways
So which is better, configuration through types or configuration by convention?
They both have their benefits. If you're like me, then you enjoy being able to figure out what your Elm code is doing by just following the types. And I hope you'll agree that `elm-pages` gives you that experience for wiring up your content and your parsers.
They both have their benefits. If you're like me, then you enjoy being able to figure out what your Elm code is doing by just following the types. And I hope you'll agree that `elm-pages` gives you that experience for wiring up your content and your parsers.
And when you need to do something more advanced, you've got all the typed data right there and you're empowered to solve the problem using Elm!
And when you need to do something more advanced, you've got all the typed data right there and you're empowered to solve the problem using Elm!

View File

@ -260,6 +260,7 @@
"chokidar": "^2.1.5",
"closure-webpack-plugin": "^2.0.1",
"copy-webpack-plugin": "^5.0.4",
"cross-spawn": "6.0.5",
"css-loader": "^3.2.0",
"elm": "^0.19.1-3",
"elm-hot-webpack-loader": "^1.1.2",
@ -267,18 +268,20 @@
"express": "^4.17.1",
"favicons-webpack-plugin": "^3.0.0",
"file-loader": "^4.2.0",
"find-elm-dependencies": "2.0.2",
"globby": "^10.0.1",
"google-closure-compiler": "^20190909.0.0",
"gray-matter": "^4.0.2",
"html-webpack-plugin": "^4.0.0-beta.11",
"imagemin-mozjpeg": "^8.0.0",
"imagemin-webpack-plugin": "^2.4.2",
"node-elm-compiler": "^5.0.4",
"lodash": "4.17.15",
"node-sass": "^4.12.0",
"prerender-spa-plugin": "^3.4.0",
"sass-loader": "^8.0.0",
"script-ext-html-webpack-plugin": "^2.1.4",
"style-loader": "^1.0.0",
"temp": "^0.9.0",
"webpack": "^4.41.5",
"webpack-dev-middleware": "^3.7.0",
"webpack-hot-middleware": "^2.25.0",

View File

@ -111,9 +111,6 @@ imageDecoder =
findMatchingImage : String -> Maybe (ImagePath Pages.PathKey)
findMatchingImage imageAssetPath =
Pages.allImages
|> List.Extra.find
(\image ->
ImagePath.toString image
== imageAssetPath
)
List.Extra.find
(\image -> ImagePath.toString image == imageAssetPath)
Pages.allImages

View File

@ -0,0 +1,3 @@
---
title: "Hello!"
---

View File

@ -2,7 +2,7 @@ import "elm-oembed";
import "./style.css";
// @ts-ignore
const { Elm } = require("./src/Main.elm");
const pagesInit = require("elm-pages");
const pagesInit = require("../../index.js");
pagesInit({
mainElmModule: Elm.Main

File diff suppressed because it is too large Load Diff

View File

@ -185,7 +185,7 @@ view siteMetadata page =
\model viewForPage ->
{ title = "Landing Page"
, body =
[ header starCount
[ header page starCount
, Element.text "Built at: "
, Element.text <| String.fromInt <| Time.posixToMillis Pages.builtAt
, pokemon
@ -234,8 +234,8 @@ articleImageView articleImage =
}
header : Int -> Element msg
header starCount =
header : { path : PagePath Pages.PathKey, frontmatter : Metadata } -> Int -> Element msg
header currentPage starCount =
Element.column [ Element.width Element.fill ]
[ Element.el
[ Element.height (Element.px 4)
@ -258,7 +258,12 @@ header starCount =
, Element.Border.color (Element.rgba255 40 80 40 0.4)
]
[ Element.link []
{ url = "/"
{ url =
if currentPage.path == pages.index then
PagePath.toString pages.otherPage
else
PagePath.toString pages.index
, label =
Element.row
[ Font.size 30

View File

@ -36,11 +36,10 @@ prerenderRcFormattedPath : String -> String
prerenderRcFormattedPath path =
path
|> dropExtension
|> chopForwardSlashes
|> String.split "/"
|> dropIndexFromLast
|> List.drop 1
|> String.join "/"
|> (\pathSoFar -> "/" ++ pathSoFar)
dropIndexFromLast : List String -> List String
@ -50,7 +49,7 @@ dropIndexFromLast path =
|> (\reversePath ->
case List.head reversePath of
Just "index" ->
reversePath |> List.drop 1
List.drop 1 reversePath
_ ->
reversePath
@ -58,12 +57,17 @@ dropIndexFromLast path =
|> List.reverse
chopForwardSlashes : String -> String
chopForwardSlashes =
String.split "/" >> List.filter ((/=) "") >> String.join "/"
pathFor : { entry | path : String } -> String
pathFor page =
page.path
|> dropExtension
|> chopForwardSlashes
|> String.split "/"
|> List.drop 1
|> dropIndexFromLast
|> List.map (\pathPart -> String.concat [ "\"", pathPart, "\"" ])
|> String.join ", "
@ -72,11 +76,11 @@ pathFor page =
dropExtension : String -> String
dropExtension path =
if path |> String.endsWith ".emu" then
path |> String.dropRight 4
if String.endsWith ".emu" path then
String.dropRight 4 path
else if path |> String.endsWith ".md" then
path |> String.dropRight 3
else if String.endsWith ".md" path then
String.dropRight 3 path
else
path
@ -219,14 +223,13 @@ init flags cliOptions =
generateFileContents : List MarkdownContent -> List ( String, String )
generateFileContents markdownFiles =
markdownFiles
|> List.map
(\file ->
( prerenderRcFormattedPath file.path |> String.dropLeft 1
, file.body
)
generateFileContents =
List.map
(\file ->
( prerenderRcFormattedPath file.path
, file.body
)
)
main : Program.StatelessProgram Never Extras

View File

@ -35,10 +35,7 @@ module.exports = class AddFilesPlugin {
// but I found the example code for it here:
// https://github.com/jantimon/html-webpack-plugin/blob/35a154186501fba3ecddb819b6f632556d37a58f/index.js#L470-L478
let route = `/${file.baseRoute}`.replace(/\/$/, '');
if (route === '') {
route = '/';
}
let route = file.baseRoute.replace(/\/$/, '');
const staticRequests = this.pagesWithRequests[route];
const filename = path.join(file.baseRoute, "content.json");

View File

@ -31,8 +31,7 @@ function start({ routes, debug, customPort, manifestConfig, routesWithRequests,
hot: true,
inline: true,
host: "localhost",
stats: "errors-only",
publicPath: "/"
stats: "errors-only"
};
const app = express();
@ -49,12 +48,20 @@ function start({ routes, debug, customPort, manifestConfig, routesWithRequests,
// don't know why this works, but it does
// see: https://github.com/jantimon/html-webpack-plugin/issues/145#issuecomment-170554832
const filename = path.join(compiler.outputPath, "index.html");
const route = req.originalUrl.replace(/(\w)\/$/, "$1").replace(/^\//, "");
const isPage = routes.includes(route);
compiler.outputFileSystem.readFile(filename, function(err, result) {
const contents = isPage
? replaceBaseAndLinks(result.toString(), route)
: result
if (err) {
return next(err);
}
res.set("content-type", "text/html");
res.send(result);
res.send(contents);
res.end();
});
});
@ -167,9 +174,15 @@ function webpackOptions(
defaultAttribute: 'defer'
}),
new FaviconsWebpackPlugin({
logo: path.resolve(process.cwd(), `./${manifestConfig.sourceIcon}`),
logo: `./${manifestConfig.sourceIcon}`,
prefix: "assets/",
publicPath: "",
outputPath: "",
favicons: {
path: "/", // Path for overriding default icons path. `string`
manifestRelativePaths: true,
path: "", // Path for overriding default icons path. `string`
appName: manifestConfig.name, // Your application's name. `string`
appShortName: manifestConfig.short_name, // Your application's short_name. `string`. Optional. If not set, appName will be used
appDescription: manifestConfig.description, // Your application's description. `string`
@ -226,9 +239,7 @@ function webpackOptions(
// (drag-and-drop `events.json` file into Chrome performance tab)
// , new webpack.debug.ProfilingPlugin()
],
output: {
publicPath: "/"
},
output: {},
resolve: {
modules: [
path.resolve(process.cwd(), `./node_modules`),
@ -307,10 +318,22 @@ function webpackOptions(
}),
new PrerenderSPAPlugin({
staticDir: path.join(process.cwd(), "dist"),
routes: routes,
routes: routes.map(r => `/${r}`),
renderer: new PrerenderSPAPlugin.PuppeteerRenderer({
renderAfterDocumentEvent: "prerender-trigger",
})
headless: true,
devtools: false
}),
postProcess: renderedRoute => {
renderedRoute.html = replaceBaseAndLinks(
renderedRoute.html,
renderedRoute.route
)
return renderedRoute
}
})
],
module: {
@ -361,3 +384,27 @@ function webpackOptions(
});
}
}
function cleanRoute(route) {
return route.replace(/(^\/|\/$)/, "")
}
function pathToRoot(cleanedRoute) {
return cleanedRoute === ""
? cleanedRoute
: cleanedRoute
.split("/")
.map(_ => "..")
.join("/")
.replace(/\.$/, "./")
}
function replaceBaseAndLinks(html, route) {
const cleanedRoute = cleanRoute(route)
const href = cleanedRoute === '' ? './' : pathToRoot(cleanedRoute)
return (html || "").replace(`<base href="/"`, `<base href="${href}"`)
}

View File

@ -12,6 +12,7 @@ ${staticRoutes.routeRecord}
${staticRoutes.imageAssetsRecord}
allImages : List (ImagePath PathKey)
allImages =
[${staticRoutes.allImages.join("\n , ")}
@ -64,6 +65,7 @@ builtAt : Time.Posix
builtAt =
Time.millisToPosix ${Math.round((global.builtAt).getTime())}
type PathKey
= PathKey
@ -73,7 +75,6 @@ buildImage path =
ImagePath.build PathKey ("images" :: path)
buildPage : List String -> PagePath PathKey
buildPage path =
PagePath.build PathKey path
@ -99,7 +100,7 @@ internals =
, content = content
, pathKey = PathKey
}
${staticRouteStuff(staticRoutes)}
${generateRawContent(markdownContent, markupContent, false)}
@ -130,6 +131,7 @@ builtAt : Time.Posix
builtAt =
Time.millisToPosix ${Math.round((global.builtAt).getTime())}
type PathKey
= PathKey

View File

@ -54,6 +54,7 @@ function run() {
.map(({ path, contents }) => {
return parseMarkdown(path, contents);
});
const images = globby
.sync("images/**/*", {})
.filter(imagePath => !fs.lstatSync(imagePath).isDirectory());
@ -117,7 +118,7 @@ function run() {
}
ensureDirSync("./gen");
// prevent compilation errors if migrating from previous elm-pages version
deleteIfExists("./gen/Pages/ContentCache.elm");
deleteIfExists("./gen/Pages/Platform.elm");
@ -169,7 +170,8 @@ function toRoute(entry) {
let fullPath = entry.path
.replace(/(index)?\.[^/.]+$/, "")
.split("/")
.filter(item => item !== "");
fullPath.splice(0, 1);
return `/${fullPath.join("/")}`;
.filter(item => item !== "")
.slice(1);
return fullPath.join("/");
}

View File

@ -26,11 +26,16 @@ function toEntry(entry, includeBody) {
`;
}
function multilineElmString(string) {
const escapedString = string
.replace(/\\/g, "\\\\")
.replace(/"""/g, '\\"\\"\\"');
return `"""${escapedString}"""`;
}
function body(entry, includeBody) {
if (includeBody) {
return `Just """${entry.body.replace(/\\/g, "\\\\")}
"""
`;
return `Just ${multilineElmString(entry.body)}`;
} else {
return `Nothing`;
}

View File

@ -5,6 +5,9 @@ const glob = require("glob");
const fs = require("fs");
const parseFrontmatter = require("./frontmatter.js");
// Because we use node-glob, we must use `/` as a separator on all platforms. See https://github.com/isaacs/node-glob#windows
const PATH_SEPARATOR = '/';
module.exports = function wrapper() {
return generate(scan());
};
@ -41,9 +44,9 @@ function unpackFile() {
function relativeImagePath(imageFilepath) {
var pathFragments = imageFilepath;
//remove extesion and split into fragments
const fragmentsWithExtension = pathFragments.split(path.sep);
const fragmentsWithExtension = pathFragments.split(PATH_SEPARATOR);
fragmentsWithExtension.splice(0, 1);
pathFragments = pathFragments.replace(/\.[^/.]+$/, "").split(path.sep);
pathFragments = pathFragments.replace(/\.[^/.]+$/, "").split(PATH_SEPARATOR);
pathFragments.splice(0, 1);
const fullPath = imageFilepath;
var relative = imageFilepath.slice(dir.length - 1);
@ -69,7 +72,7 @@ function generate(scanned) {
for (var i = 0; i < scanned.length; i++) {
var pathFragments = scanned[i].path;
//remove extesion and split into fragments
pathFragments = pathFragments.replace(/\.[^/.]+$/, "").split(path.sep);
pathFragments = pathFragments.replace(/\.[^/.]+$/, "").split(PATH_SEPARATOR);
const is404 = pathFragments.length == 1 && pathFragments[0] == "404";
const ext = path.extname(scanned[i].path);

View File

@ -1,13 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="preload" href="./content.json" as="fetch" crossorigin />
<link rel="preload" href="content.json" as="fetch" crossorigin />
<base href="/" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>
if ("serviceWorker" in navigator) {
window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js");
navigator.serviceWorker.register("service-worker.js");
});
} else {
console.log("No service worker registered.");

View File

@ -13,12 +13,15 @@ module.exports = function pagesInit(
return new Promise(function(resolve, reject) {
document.addEventListener("DOMContentLoaded", _ => {
new MutationObserver(function() {
elmViewRendered = true;
if (headTagsAdded) {
document.dispatchEvent(new Event("prerender-trigger"));
}
}).observe(document.body, { attributes: true, childList: true, subtree: true});
new MutationObserver(function() {
elmViewRendered = true;
if (headTagsAdded) {
document.dispatchEvent(new Event("prerender-trigger"));
}
}).observe(
document.body,
{ attributes: true, childList: true, subtree: true }
);
loadContentAndInitializeApp(mainElmModule).then(resolve, reject);
});
@ -26,12 +29,18 @@ module.exports = function pagesInit(
};
function loadContentAndInitializeApp(/** @type { init: any } */ mainElmModule) {
return httpGet(`${window.location.origin}${window.location.pathname}/content.json`).then(function(/** @type JSON */ contentJson) {
const isPrerendering = navigator.userAgent.indexOf("Headless") >= 0
const path = window.location.pathname.replace(/(\w)$/, "$1/")
return httpGet(`${window.location.origin}${path}content.json`).then(function(/** @type JSON */ contentJson) {
const app = mainElmModule.init({
flags: {
secrets: null,
isPrerendering: navigator.userAgent.indexOf("Headless") >= 0,
baseUrl: isPrerendering
? window.location.origin
: document.baseURI,
isPrerendering: isPrerendering,
contentJson
}
});
@ -46,8 +55,8 @@ function loadContentAndInitializeApp(/** @type { init: any } */ mainElmModule)
["content", `elm-pages v${elmPagesVersion}`]
]
});
window.allRoutes = fromElm.allRoutes;
window.allRoutes = fromElm.allRoutes.map(route => new URL(route, document.baseURI).href);
if (navigator.userAgent.indexOf("Headless") >= 0) {
fromElm.head.forEach(headTag => {
@ -141,8 +150,8 @@ function prefetchIfNeeded(/** @type {HTMLAnchorElement} */ target) {
if (target.host === window.location.host) {
if (prefetchedPages.includes(target.pathname)) {
// console.log("Already preloaded", target.href);
} else if (!allRoutes.includes(target.pathname)) {
// console.log("Not a known route, skipping preload", target.pathname);
} else if (!allRoutes.includes(new URL(target.pathname, document.baseURI).href)) {
}
else {
prefetchedPages.push(target.pathname);

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "elm-pages",
"version": "1.2.10",
"version": "1.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "elm-pages",
"version": "1.2.10",
"version": "1.3.0",
"homepage": "http://elm-pages.com",
"description": "Type-safe static sites, written in pure elm with your own custom elm-markup syntax.",
"main": "index.js",

View File

@ -33,6 +33,7 @@ writing a plugin package to extend `elm-pages`.
import Json.Encode
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.Internal.String as String
import Pages.PagePath as PagePath exposing (PagePath)
@ -217,8 +218,4 @@ encodeProperty canonicalSiteUrl currentPagePath ( name, value ) =
joinPaths : String -> String -> String
joinPaths base path =
if (base |> String.endsWith "/") && (path |> String.startsWith "/") then
base ++ String.dropLeft 1 path
else
base ++ path
String.chopEnd "/" base ++ "/" ++ String.chopStart "/" path

View File

@ -24,6 +24,7 @@ import Json.Decode as Decode
import Mark
import Mark.Error
import Pages.Document as Document exposing (Document)
import Pages.Internal.String as String
import Pages.PagePath as PagePath exposing (PagePath)
import Result.Extra
import Task exposing (Task)
@ -88,22 +89,21 @@ pagesWithErrors cache =
cache
|> Result.map
(\okCache ->
okCache
|> Dict.toList
|> List.filterMap
(\( path, value ) ->
case value of
Parsed metadata { body } ->
case body of
Err parseError ->
createBuildError path parseError |> Just
List.filterMap
(\( path, value ) ->
case value of
Parsed metadata { body } ->
case body of
Err parseError ->
createBuildError path parseError |> Just
_ ->
Nothing
_ ->
Nothing
_ ->
Nothing
)
_ ->
Nothing
)
(Dict.toList okCache)
)
|> Result.withDefault []
@ -114,22 +114,17 @@ init :
-> Maybe { contentJson : ContentJson String, initialUrl : Url }
-> ContentCache metadata view
init document content maybeInitialPageContent =
parseMetadata maybeInitialPageContent document content
content
|> parseMetadata maybeInitialPageContent document
|> List.map
(\tuple ->
Tuple.mapSecond
(\result ->
result
|> Result.mapError
(\error ->
-- ( Tuple.first tuple, error )
createErrors (Tuple.first tuple) error
)
)
tuple
tuple
|> Tuple.first
|> createErrors
|> Result.mapError
|> (\f -> Tuple.mapSecond f tuple)
)
|> combineTupleResults
-- |> Result.mapError Dict.fromList
|> Result.map Dict.fromList
@ -142,7 +137,7 @@ createBuildError path decodeError =
{ title = "Metadata Decode Error"
, message =
[ Terminal.text "I ran into a problem when parsing the metadata for the page with this path: "
, Terminal.text ("/" ++ (path |> String.join "/"))
, Terminal.text (String.join "/" path)
, Terminal.text "\n\n"
, Terminal.text decodeError
]
@ -156,44 +151,43 @@ parseMetadata :
-> List ( List String, { extension : String, frontMatter : String, body : Maybe String } )
-> List ( List String, Result String (Entry metadata view) )
parseMetadata maybeInitialPageContent document content =
content
|> List.map
(\( path, { frontMatter, extension, body } ) ->
let
maybeDocumentEntry =
Document.get extension document
in
case maybeDocumentEntry of
Just documentEntry ->
frontMatter
|> documentEntry.frontmatterParser
|> Result.map
(\metadata ->
let
renderer =
\value ->
parseContent extension value document
in
case maybeInitialPageContent of
Just { contentJson, initialUrl } ->
if normalizePath initialUrl.path == (String.join "/" path |> normalizePath) then
Parsed metadata
{ body = renderer contentJson.body
, staticData = contentJson.staticData
}
List.map
(\( path, { frontMatter, extension, body } ) ->
let
maybeDocumentEntry =
Document.get extension document
in
case maybeDocumentEntry of
Just documentEntry ->
frontMatter
|> documentEntry.frontmatterParser
|> Result.map
(\metadata ->
let
renderer value =
parseContent extension value document
in
case maybeInitialPageContent of
Just { contentJson, initialUrl } ->
if normalizePath initialUrl.path == (String.join "/" path |> normalizePath) then
Parsed metadata
{ body = renderer contentJson.body
, staticData = contentJson.staticData
}
else
NeedContent extension metadata
Nothing ->
else
NeedContent extension metadata
)
|> Tuple.pair path
Nothing ->
Err ("Could not find extension '" ++ extension ++ "'")
|> Tuple.pair path
)
Nothing ->
NeedContent extension metadata
)
|> Tuple.pair path
Nothing ->
Err ("Could not find extension '" ++ extension ++ "'")
|> Tuple.pair path
)
content
normalizePath : String -> String
@ -206,16 +200,15 @@ normalizePath pathString =
String.endsWith "/" pathString
in
if pathString == "" then
"/"
pathString
else
String.concat
[ if hasPrefix then
""
String.dropLeft 1 pathString
else
"/"
, pathString
pathString
, if hasSuffix then
""
@ -257,7 +250,7 @@ createHtmlError : List String -> String -> Html msg
createHtmlError path error =
Html.div []
[ Html.h2 []
[ Html.text ("/" ++ (path |> String.join "/"))
[ Html.text (String.join "/" path)
]
, Html.p [] [ Html.text "I couldn't parse the frontmatter in this page. I ran into this error with your JSON decoder:" ]
, Html.pre [] [ Html.text error ]
@ -269,7 +262,6 @@ routes record =
record
|> List.map Tuple.first
|> List.map (String.join "/")
|> List.map (\route -> "/" ++ route)
routesForCache : ContentCache metadata view -> List String
@ -294,7 +286,7 @@ type alias Page metadata view pathKey =
renderErrors : ( List String, List Mark.Error.Error ) -> Html msg
renderErrors ( path, errors ) =
Html.div []
[ Html.text (path |> String.join "/")
[ Html.text (String.join "/" path)
, errors
|> List.map (Mark.Error.toHtml Mark.Error.Light)
|> Html.div []
@ -349,37 +341,40 @@ parse it before returning it and store the parsed version in the Cache
-}
lazyLoad :
Document metadata view
-> Url
-> { currentUrl : Url, baseUrl : Url }
-> ContentCache metadata view
-> Task Http.Error (ContentCache metadata view)
lazyLoad document url cacheResult =
lazyLoad document urls cacheResult =
case cacheResult of
Err _ ->
Task.succeed cacheResult
Ok cache ->
case Dict.get (pathForUrl url) cache of
case Dict.get (pathForUrl urls) cache of
Just entry ->
case entry of
NeedContent extension _ ->
httpTask url
urls.currentUrl
|> httpTask
|> Task.map
(\downloadedContent ->
update cacheResult
update
cacheResult
(\value ->
parseContent extension value document
)
url
urls
downloadedContent
)
Unparsed extension metadata content ->
update cacheResult
(\thing ->
parseContent extension thing document
)
url
content
content
|> update
cacheResult
(\thing ->
parseContent extension thing document
)
urls
|> Task.succeed
Parsed _ _ ->
@ -395,12 +390,13 @@ httpTask url =
{ method = "GET"
, headers = []
, url =
Url.Builder.absolute
((url.path |> String.split "/" |> List.filter (not << String.isEmpty))
++ [ "content.json"
]
)
[]
url.path
|> String.chopForwardSlashes
|> String.split "/"
|> List.filter ((/=) "")
|> (\l -> l ++ [ "content.json" ])
|> String.join "/"
|> String.append "/"
, body = Http.emptyBody
, resolver =
Http.stringResolver
@ -443,13 +439,14 @@ contentJsonDecoder =
update :
ContentCache metadata view
-> (String -> Result ParseError view)
-> Url
-> { currentUrl : Url, baseUrl : Url }
-> ContentJson String
-> ContentCache metadata view
update cacheResult renderer url rawContent =
update cacheResult renderer urls rawContent =
case cacheResult of
Ok cache ->
Dict.update (pathForUrl url)
Dict.update
(pathForUrl urls)
(\entry ->
case entry of
Just (Parsed metadata view) ->
@ -482,27 +479,29 @@ update cacheResult renderer url rawContent =
Err error
pathForUrl : Url -> Path
pathForUrl url =
url.path
|> dropTrailingSlash
pathForUrl : { currentUrl : Url, baseUrl : Url } -> Path
pathForUrl { currentUrl, baseUrl } =
currentUrl.path
|> String.dropLeft (String.length baseUrl.path)
|> String.chopForwardSlashes
|> String.split "/"
|> List.drop 1
|> List.filter ((/=) "")
lookup :
pathKey
-> ContentCache metadata view
-> Url
-> { currentUrl : Url, baseUrl : Url }
-> Maybe ( PagePath pathKey, Entry metadata view )
lookup pathKey content url =
lookup pathKey content urls =
case content of
Ok dict ->
let
path =
pathForUrl url
pathForUrl urls
in
Dict.get path dict
dict
|> Dict.get path
|> Maybe.map
(\entry ->
( PagePath.build pathKey path, entry )
@ -515,10 +514,11 @@ lookup pathKey content url =
lookupMetadata :
pathKey
-> ContentCache metadata view
-> Url
-> { currentUrl : Url, baseUrl : Url }
-> Maybe ( PagePath pathKey, metadata )
lookupMetadata pathKey content url =
lookup pathKey content url
lookupMetadata pathKey content urls =
urls
|> lookup pathKey content
|> Maybe.map
(\( pagePath, entry ) ->
case entry of
@ -531,11 +531,3 @@ lookupMetadata pathKey content url =
Parsed metadata _ ->
( pagePath, metadata )
)
dropTrailingSlash path =
if path |> String.endsWith "/" then
String.dropRight 1 path
else
path

View File

@ -116,7 +116,7 @@ includes (Directory key allPagePaths directoryPath) pagePath =
Pages.pages.blog.directory
-- blogDirectory |> Directory.indexPath |> PagePath.toString
-- => "/blog"
-- => "blog"
See `Directory.includes` for an example of this in action.
@ -141,9 +141,8 @@ basePath (Directory key allPagePaths directoryPath) =
toString : List String -> String
toString rawPath =
"/"
++ (rawPath |> String.join "/")
toString =
String.join "/"
{-| Used by the generated `Pages.elm` module. There's no need to use this

View File

@ -25,7 +25,7 @@ This gives you a record, based on all the files in your local
Pages.pages.index
-- ImagePath.toString homePath
-- => "/"
-- => ""
or
@ -37,7 +37,7 @@ or
Pages.images.profilePhotos.dillon
-- ImagePath.toString helloWorldPostPath
-- => "/images/profile-photos/dillon.jpg"
-- => "images/profile-photos/dillon.jpg"
@docs ImagePath, toString, external
@ -65,7 +65,7 @@ type ImagePath key
| External String
{-| Gives you the image's absolute URL as a String. This is useful for constructing `<img>` tags:
{-| Gives you the image's relative URL as a String. This is useful for constructing `<img>` tags:
import Html exposing (Html, img)
import Html.Attributes exposing (src)
@ -87,8 +87,7 @@ toString : ImagePath key -> String
toString path =
case path of
Internal rawPath ->
"/"
++ (rawPath |> String.join "/")
String.join "/" rawPath
External url ->
url

View File

@ -15,6 +15,7 @@ import Mark
import Pages.ContentCache as ContentCache exposing (ContentCache)
import Pages.Document
import Pages.Internal.Platform.Cli
import Pages.Internal.String as String
import Pages.Manifest as Manifest
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
@ -24,14 +25,6 @@ import Task exposing (Task)
import Url exposing (Url)
dropTrailingSlash path =
if path |> String.endsWith "/" then
String.dropRight 1 path
else
path
type alias Page metadata view pathKey =
{ metadata : metadata
, path : PagePath pathKey
@ -81,12 +74,13 @@ mainView pathKey pageView model =
}
urlToPagePath : pathKey -> Url -> PagePath pathKey
urlToPagePath pathKey url =
urlToPagePath : pathKey -> Url -> Url -> PagePath pathKey
urlToPagePath pathKey url baseUrl =
url.path
|> dropTrailingSlash
|> String.dropLeft (String.length baseUrl.path)
|> String.chopForwardSlashes
|> String.split "/"
|> List.drop 1
|> List.filter ((/=) "")
|> PagePath.build pathKey
@ -108,19 +102,25 @@ pageViewOrError :
-> ContentCache metadata view
-> { title : String, body : Html userMsg }
pageViewOrError pathKey viewFn model cache =
case ContentCache.lookup pathKey cache model.url of
let
urls =
{ currentUrl = model.url
, baseUrl = model.baseUrl
}
in
case ContentCache.lookup pathKey cache urls of
Just ( pagePath, entry ) ->
case entry of
ContentCache.Parsed metadata viewResult ->
let
viewFnResult =
viewFn
(cache
|> Result.map (ContentCache.extractMetadata pathKey)
|> Result.withDefault []
-- TODO handle error better
)
{ path = pagePath, frontmatter = metadata }
{ path = pagePath, frontmatter = metadata }
|> viewFn
(cache
|> Result.map (ContentCache.extractMetadata pathKey)
|> Result.withDefault []
-- TODO handle error better
)
|> (\request ->
StaticHttpRequest.resolve request viewResult.staticData
)
@ -263,7 +263,10 @@ init :
init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel flags url key =
let
contentCache =
ContentCache.init document content (Maybe.map (\cj -> { contentJson = cj, initialUrl = url }) contentJson)
ContentCache.init
document
content
(Maybe.map (\cj -> { contentJson = cj, initialUrl = url }) contentJson)
contentJson =
flags
@ -275,6 +278,18 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
Decode.map2 ContentJson
(Decode.field "body" Decode.string)
(Decode.field "staticData" (Decode.dict Decode.string))
baseUrl =
flags
|> Decode.decodeValue (Decode.field "baseUrl" Decode.string)
|> Result.toMaybe
|> Maybe.andThen Url.fromString
|> Maybe.withDefault url
urls =
{ currentUrl = url
, baseUrl = baseUrl
}
in
case contentCache of
Ok okCache ->
@ -291,23 +306,24 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
Client
( userModel, userCmd ) =
initUserModel
(maybePagePath
|> Maybe.map
(\pagePath ->
{ path = pagePath
, query = url.query
, fragment = url.fragment
}
)
)
maybePagePath
|> Maybe.map
(\pagePath ->
{ path = pagePath
, query = url.query
, fragment = url.fragment
}
)
|> initUserModel
cmd =
case ( maybePagePath, maybeMetadata ) of
( Just pagePath, Just frontmatter ) ->
[ userCmd |> Cmd.map UserMsg |> Just
[ userCmd
|> Cmd.map UserMsg
|> Just
, contentCache
|> ContentCache.lazyLoad document url
|> ContentCache.lazyLoad document urls
|> Task.attempt UpdateCache
|> Just
]
@ -318,7 +334,7 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
Cmd.none
( maybePagePath, maybeMetadata ) =
case ContentCache.lookupMetadata pathKey (Ok okCache) url of
case ContentCache.lookupMetadata pathKey (Ok okCache) urls of
Just ( pagePath, metadata ) ->
( Just pagePath, Just metadata )
@ -327,6 +343,7 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
in
( { key = key
, url = url
, baseUrl = baseUrl
, userModel = userModel
, contentCache = contentCache
, phase = phase
@ -341,6 +358,7 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
in
( { key = key
, url = url
, baseUrl = baseUrl
, userModel = userModel
, contentCache = contentCache
, phase = Client
@ -381,7 +399,8 @@ type Model userModel userMsg metadata view
type alias ModelDetails userModel metadata view =
{ key : Browser.Navigation.Key
, url : Url.Url
, url : Url
, baseUrl : Url
, contentCache : ContentCache metadata view
, userModel : userModel
, phase : Phase
@ -432,10 +451,7 @@ update allRoutes canonicalSiteUrl viewFunction pathKey maybeOnPageChangeMsg toJs
Browser.Internal url ->
let
navigatingToSamePage =
url.path
== model.url.path
&& url
/= model.url
(url.path == model.url.path) && (url /= model.url)
in
if navigatingToSamePage then
-- this is a workaround for an issue with anchor fragment navigation
@ -451,10 +467,12 @@ update allRoutes canonicalSiteUrl viewFunction pathKey maybeOnPageChangeMsg toJs
UrlChanged url ->
let
navigatingToSamePage =
url.path
== model.url.path
&& url
/= model.url
(url.path == model.url.path) && (url /= model.url)
urls =
{ currentUrl = url
, baseUrl = model.baseUrl
}
in
( model
, if navigatingToSamePage then
@ -467,7 +485,7 @@ update allRoutes canonicalSiteUrl viewFunction pathKey maybeOnPageChangeMsg toJs
else
model.contentCache
|> ContentCache.lazyLoad document url
|> ContentCache.lazyLoad document urls
|> Task.attempt (UpdateCacheAndUrl url)
)
@ -484,8 +502,13 @@ update allRoutes canonicalSiteUrl viewFunction pathKey maybeOnPageChangeMsg toJs
-- to keep track of the last url change
Ok updatedCache ->
let
urls =
{ currentUrl = model.url
, baseUrl = model.baseUrl
}
maybeCmd =
case ContentCache.lookup pathKey updatedCache model.url of
case ContentCache.lookup pathKey updatedCache urls of
Just ( pagePath, entry ) ->
case entry of
ContentCache.Parsed frontmatter viewResult ->
@ -536,7 +559,7 @@ update allRoutes canonicalSiteUrl viewFunction pathKey maybeOnPageChangeMsg toJs
Just onPageChangeMsg ->
userUpdate
(onPageChangeMsg
{ path = url |> urlToPagePath pathKey
{ path = urlToPagePath pathKey url model.baseUrl
, query = url.query
, fragment = url.fragment
}
@ -666,7 +689,6 @@ application config =
config.content
|> List.map Tuple.first
|> List.map (String.join "/")
|> List.map (\route -> "/" ++ route)
in
update allRoutes config.canonicalSiteUrl config.view config.pathKey config.onPageChange config.toJsPort config.document userUpdate msg model
|> Tuple.mapFirst Model

View File

@ -279,10 +279,10 @@ perform cliMsgConstructor toJsPort effect =
|> Cmd.batch
FetchHttp ({ unmasked, masked } as requests) ->
--let
-- _ =
-- Debug.log "Fetching" masked.url
--in
-- let
-- _ =
-- Debug.log "Fetching" masked.url
-- in
Http.request
{ method = unmasked.method
, url = unmasked.url
@ -329,15 +329,15 @@ init toModel contentCache siteMetadata config flags =
Ok ( secrets, mode ) ->
case contentCache of
Ok _ ->
case contentCache |> ContentCache.pagesWithErrors of
case ContentCache.pagesWithErrors contentCache of
[] ->
let
requests =
siteMetadata
|> Result.andThen
(\metadata ->
staticResponseForPage metadata config.view
)
Result.andThen
(\metadata ->
staticResponseForPage metadata config.view
)
siteMetadata
staticResponses : StaticResponses
staticResponses =
@ -359,11 +359,11 @@ init toModel contentCache siteMetadata config flags =
pageErrors ->
let
requests =
siteMetadata
|> Result.andThen
(\metadata ->
staticResponseForPage metadata config.view
)
Result.andThen
(\metadata ->
staticResponseForPage metadata config.view
)
siteMetadata
staticResponses : StaticResponses
staticResponses =
@ -453,25 +453,25 @@ update siteMetadata config msg model =
case msg of
GotStaticHttpResponse { request, response } ->
let
--_ =
-- Debug.log "Got response" request.masked.url
-- _ =
-- Debug.log "Got response" request.masked.url
--
updatedModel =
(case response of
Ok okResponse ->
staticResponsesUpdate
{ request = request
, response =
response |> Result.mapError (\_ -> ())
, response = Result.mapError (\_ -> ()) response
}
model
Err error ->
{ model
| errors =
model.errors
++ [ { title = "Static HTTP Error"
, message =
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
@ -496,16 +496,15 @@ update siteMetadata config msg model =
Pages.Http.NetworkError ->
Terminal.text "Network error"
]
, fatal = True
}
]
, fatal = True
}
]
}
)
|> staticResponsesUpdate
-- TODO for hash pass in RequestDetails here
{ request = request
, response =
response |> Result.mapError (\_ -> ())
, response = Result.mapError (\_ -> ()) response
}
( updatedAllRawResponses, effect ) =
@ -527,10 +526,9 @@ performStaticHttpRequests allRawResponses secrets staticRequests =
staticRequests
|> List.map
(\( pagePath, request ) ->
StaticHttpRequest.resolveUrls request
(allRawResponses
|> dictCompact
)
allRawResponses
|> dictCompact
|> StaticHttpRequest.resolveUrls request
|> Tuple.second
)
|> List.concat
@ -539,10 +537,13 @@ performStaticHttpRequests allRawResponses secrets staticRequests =
-- |> Set.toList
|> List.map
(\urlBuilder ->
Secrets.lookup secrets urlBuilder
urlBuilder
|> Secrets.lookup secrets
|> Result.map
(\unmasked ->
{ unmasked = unmasked, masked = Secrets.maskedLookup urlBuilder }
{ unmasked = unmasked
, masked = Secrets.maskedLookup urlBuilder
}
)
)
|> combineMultipleErrors
@ -636,41 +637,47 @@ staticResponsesUpdate : { request : { masked : RequestDetails, unmasked : Reques
staticResponsesUpdate newEntry model =
let
updatedAllResponses =
model.allRawResponses
-- @@@@@@@@@ TODO handle errors here, change Dict to have `Result` instead of `Maybe`
|> Dict.insert (HashRequest.hash newEntry.request.masked) (Just (newEntry.response |> Result.withDefault "TODO"))
-- @@@@@@@@@ TODO handle errors here, change Dict to have `Result` instead of `Maybe`
Dict.insert
(HashRequest.hash newEntry.request.masked)
(Just <| Result.withDefault "TODO" newEntry.response)
model.allRawResponses
in
{ model
| allRawResponses = updatedAllResponses
, staticResponses =
model.staticResponses
|> Dict.map
(\pageUrl entry ->
case entry of
NotFetched request rawResponses ->
Dict.map
(\pageUrl entry ->
case entry of
NotFetched request rawResponses ->
let
realUrls =
updatedAllResponses
|> dictCompact
|> StaticHttpRequest.resolveUrls request
|> Tuple.second
|> List.map Secrets.maskedLookup
|> List.map HashRequest.hash
includesUrl =
List.member
(HashRequest.hash newEntry.request.masked)
realUrls
in
if includesUrl then
let
realUrls =
StaticHttpRequest.resolveUrls request
(updatedAllResponses |> dictCompact)
|> Tuple.second
|> List.map Secrets.maskedLookup
|> List.map HashRequest.hash
includesUrl =
List.member (HashRequest.hash newEntry.request.masked)
realUrls
in
if includesUrl then
let
updatedRawResponses =
updatedRawResponses =
Dict.insert
(HashRequest.hash newEntry.request.masked)
newEntry.response
rawResponses
|> Dict.insert (HashRequest.hash newEntry.request.masked) newEntry.response
in
NotFetched request updatedRawResponses
in
NotFetched request updatedRawResponses
else
entry
)
else
entry
)
model.staticResponses
}
@ -704,20 +711,21 @@ sendStaticResponsesIfDone config siteMetadata mode secrets allRawResponses error
let
usableRawResponses : Dict String String
usableRawResponses =
rawResponses
|> Dict.Extra.filterMap
(\key value ->
value
|> Result.map Just
|> Result.withDefault Nothing
)
Dict.Extra.filterMap
(\key value ->
value
|> Result.map Just
|> Result.withDefault Nothing
)
rawResponses
hasPermanentError =
StaticHttpRequest.permanentError request usableRawResponses
usableRawResponses
|> StaticHttpRequest.permanentError request
|> isJust
hasPermanentHttpError =
not <| List.isEmpty errors
not (List.isEmpty errors)
--|> List.any
-- (\error ->
@ -860,7 +868,7 @@ sendStaticResponsesIfDone config siteMetadata mode secrets allRawResponses error
PagePath.toString pagePath
currentContentPath =
"/" ++ (path |> String.join "/")
String.join "/" path
in
if pagePathToGenerate == currentContentPath then
Just body
@ -962,13 +970,12 @@ encodeStaticResponses mode staticResponses =
NotFetched request rawResponsesDict ->
let
relevantResponses =
rawResponsesDict
|> Dict.map
(\key value ->
value
-- TODO avoid running this code at all if there are errors here
|> Result.withDefault ""
)
Dict.map
(\_ ->
-- TODO avoid running this code at all if there are errors here
Result.withDefault ""
)
rawResponsesDict
strippedResponses : Dict String String
strippedResponses =

View File

@ -0,0 +1,43 @@
module Pages.Internal.String exposing (..)
{-| Remove a piece from the beginning of a string until it's not there anymore.
>>> chopStart "{" "{{{<-"
"<-"
-}
chopStart : String -> String -> String
chopStart needle string =
if String.startsWith needle string then
string
|> String.dropLeft (String.length needle)
|> chopStart needle
else
string
{-| Remove a piece from the end of a string until it's not there anymore.
>>> chopEnd "}" "->}}}"
"->"
-}
chopEnd : String -> String -> String
chopEnd needle string =
if String.endsWith needle string then
string
|> String.dropRight (String.length needle)
|> chopEnd needle
else
string
{-| Removes `/` characters from both ends of a string.
-}
chopForwardSlashes : String -> String
chopForwardSlashes =
chopStart "/" >> chopEnd "/"

View File

@ -259,7 +259,7 @@ toJson config =
)
, ( "serviceworker"
, Encode.object
[ ( "src", Encode.string "/service-worker.js" )
[ ( "src", Encode.string "../service-worker.js" )
, ( "scope", Encode.string "/" )
, ( "type", Encode.string "" )
, ( "update_via_cache", Encode.string "none" )

View File

@ -44,7 +44,7 @@ This gives you a record, based on your local `content` directory, that lets you
Pages.pages.index
-- PagePath.toString homePath
-- => "/"
-- => ""
or
@ -56,7 +56,7 @@ or
Pages.pages.blog.helloWorld
-- PagePath.toString helloWorldPostPath
-- => "/blog/hello-world"
-- => "blog/hello-world"
Note that in the `hello-world` example it changes from the kebab casing of the actual
URL to camelCasing for the record key.
@ -93,7 +93,7 @@ type PagePath key
| External String
{-| Gives you the page's absolute URL as a String. This is useful for constructing links:
{-| Gives you the page's relative URL as a String. This is useful for constructing links:
import Html exposing (Html, a)
import Html.Attributes exposing (href)
@ -115,8 +115,7 @@ toString : PagePath key -> String
toString path =
case path of
Internal rawPath ->
"/"
++ (rawPath |> String.join "/")
String.join "/" rawPath
External url ->
url

View File

@ -20,10 +20,10 @@ And your StaticHttp request in your Elm code looks like this:
StaticHttp.request
(Secrets.succeed
(\githubToken stripeoken ->
(\apiKey githubToken ->
{ url = "https://api.github.com/repos/dillonkearns/elm-pages?apiKey=" ++ apiKey
, method = "GET"
, headers = [ ( "Authorization", "Bearer " ++ bearer ) ]
, headers = [ ( "Authorization", "Bearer " ++ githubToken ) ]
}
)
|> Secrets.with "API_KEY"

View File

@ -15,7 +15,7 @@ The key differences are:
- `StaticHttp.Request`s are performed once at build time (`Http.Request`s are performed at runtime, at whenever point you perform them)
- `StaticHttp.Request`s strip out unused JSON data from the data your decoder doesn't touch to minimize the JSON payload
- `StaticHttp.Request`s can use [`Pages.Secrets`](Pages.Secrets) to securely use credentials from your environemnt variables which are completely masked in the production assets.
- `StaticHttp.Request`s can use [`Pages.Secrets`](Pages.Secrets) to securely use credentials from your environment variables which are completely masked in the production assets.
- `StaticHttp.Request`s have a built-in `StaticHttp.andThen` that allows you to perform follow-up requests without using tasks
@ -167,10 +167,8 @@ map fn requestInfo =
{-| Helper to remove an inner layer of Request wrapping.
-}
resolve : Request (List (Request value)) -> Request (List value)
resolve topRequest =
topRequest
|> andThen
(\continuationRequests -> combine continuationRequests)
resolve =
andThen combine
{-| Turn a list of `StaticHttp.Request`s into a single one.
@ -208,9 +206,8 @@ resolve topRequest =
-}
combine : List (Request value) -> Request (List value)
combine requests =
requests
|> List.foldl (map2 (::)) (succeed [])
combine =
List.foldl (map2 (::)) (succeed [])
{-| Like map, but it takes in two `Request`s.
@ -462,15 +459,15 @@ get :
-> Request a
get url decoder =
request
(url
|> Secrets.map
(\okUrl ->
{ url = okUrl
, method = "GET"
, headers = []
, body = emptyBody
}
)
(Secrets.map
(\okUrl ->
{ url = okUrl
, method = "GET"
, headers = []
, body = emptyBody
}
)
url
)
decoder

View File

@ -42,7 +42,7 @@ all =
"https://api.github.com/repos/dillonkearns/elm-pages"
"""{ "stargazer_count": 86 }"""
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
, """{"stargazer_count":86}"""
)
@ -69,7 +69,7 @@ all =
"NEXT-REQUEST"
"""null"""
|> expectSuccess
[ ( "/elm-pages"
[ ( "elm-pages"
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
, """null"""
)
@ -162,7 +162,7 @@ all =
"url10"
"""{"image": "image10.jpg"}"""
|> expectSuccess
[ ( "/elm-pages"
[ ( "elm-pages"
, [ ( 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"}]"""
)
@ -218,13 +218,13 @@ all =
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
"""{ "stargazer_count": 22 }"""
|> expectSuccess
[ ( "/elm-pages"
[ ( "elm-pages"
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
, """{"stargazer_count":86}"""
)
]
)
, ( "/elm-pages-starter"
, ( "elm-pages-starter"
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages-starter"
, """{"stargazer_count":22}"""
)
@ -243,7 +243,7 @@ all =
"https://api.github.com/repos/dillonkearns/elm-pages"
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
, """{"stargazer_count":86}"""
)
@ -272,7 +272,7 @@ all =
"https://api.github.com/repos/dillonkearns/elm-pages"
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
, """{ "stargazer_count": 86, "unused_field": 123 }"""
)
@ -299,7 +299,7 @@ all =
"https://example.com/file.txt"
"This is a raw text file."
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( get "https://example.com/file.txt"
, "This is a raw text file."
)
@ -339,7 +339,7 @@ all =
(expectErrorsPort
"""-- STATIC HTTP DECODING ERROR ----------------------------------------------------- elm-pages
/
String was not uppercased"""
)
@ -363,7 +363,7 @@ String was not uppercased"""
"https://api.github.com/repos/dillonkearns/elm-pages"
"""{ "stargazer_count": 86, "unused_field": 123 }"""
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( { method = "POST"
, url = "https://api.github.com/repos/dillonkearns/elm-pages"
, headers = []
@ -394,7 +394,7 @@ String was not uppercased"""
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
"""{ "stargazer_count": 50, "unused_field": 456 }"""
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
, """{"stargazer_count":100}"""
)
@ -422,7 +422,7 @@ String was not uppercased"""
"https://api.github.com/repos/dillonkearns/elm-pages-starter"
"""{ "stargazer_count": 50, "unused_field": 456 }"""
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( get "https://api.github.com/repos/dillonkearns/elm-pages"
, """{"stargazer_count":100}"""
)
@ -439,7 +439,7 @@ String was not uppercased"""
, StaticHttp.succeed ()
)
]
|> expectSuccess [ ( "/", [] ) ]
|> expectSuccess [ ( "", [] ) ]
, test "the port sends out when there are duplicate http requests for the same page" <|
\() ->
start
@ -454,7 +454,7 @@ String was not uppercased"""
"http://example.com"
"""null"""
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( get "http://example.com"
, """null"""
)
@ -478,7 +478,7 @@ String was not uppercased"""
(expectErrorsPort
"""-- STATIC HTTP DECODING ERROR ----------------------------------------------------- elm-pages
/elm-pages
elm-pages
I encountered some errors while decoding this JSON:
@ -591,7 +591,7 @@ Body: """)
}
)
|> expectSuccess
[ ( "/"
[ ( ""
, [ ( { method = "GET"
, url = "https://api.github.com/repos/dillonkearns/elm-pages?apiKey=<API_KEY>"
, headers =
@ -650,7 +650,6 @@ start pages =
|> Dict.get
(page.path
|> PagePath.toString
|> String.dropLeft 1
|> String.split "/"
|> List.filter (\pathPart -> pathPart /= "")
)
@ -806,7 +805,7 @@ starDecoder =
thingy =
[ ( "/"
[ ( ""
, [ ( { method = "GET"
, url = "https://api.github.com/repos/dillonkearns/elm-pages"
, headers = []