elm-pages 3.0.0-beta.7 → 3.0.0-beta.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -1
- package/codegen/elm-pages-codegen.js +121 -102
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/Runner.elm.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/dead-code-review/elm-stuff/tests-0.19.1/js/node_supervisor.js +4 -4
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/d.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm-stuff/0.19.1/o.dat +0 -0
- package/generator/review/elm-stuff/tests-0.19.1/elm.json +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/Reporter.elm.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/Runner.elm.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/node_runner.js +1 -1
- package/generator/review/elm-stuff/tests-0.19.1/js/node_supervisor.js +4 -4
- package/generator/src/build.js +53 -13
- package/generator/src/config.js +12 -10
- package/generator/src/dev-server.js +15 -6
- package/generator/src/pre-render-html.js +4 -6
- package/generator/src/render.js +2 -0
- package/generator/src/seo-renderer.js +11 -4
- package/generator/template/app/Api.elm +1 -1
- package/generator/template/app/Site.elm +6 -1
- package/package.json +1 -1
- package/src/ApiRoute.elm +0 -3
- package/src/DataSource.elm +11 -3
- package/src/Head.elm +126 -0
- package/src/HtmlPrinter.elm +7 -3
- package/src/Pages/Generate.elm +101 -10
- package/src/Pages/Internal/Platform/Cli.elm +52 -8
- package/src/Pages/Internal/Platform/Cli.elm.bak +1276 -0
- package/src/Pages/Internal/Platform/CompatibilityKey.elm +6 -0
- package/src/Pages/ProgramConfig.elm +5 -2
|
@@ -75,19 +75,19 @@ let testsCount, todoTests;
|
|
|
75
75
|
let reporter;
|
|
76
76
|
let runners = [];
|
|
77
77
|
let working = false;
|
|
78
|
-
let workersCount =
|
|
78
|
+
let workersCount = 10;
|
|
79
79
|
let startWorkCallback = function(){};
|
|
80
80
|
const verbosity = 0;
|
|
81
81
|
|
|
82
82
|
// Create a long lived reporter worker
|
|
83
83
|
const { Elm } = require("./Reporter.elm.js");
|
|
84
84
|
const flags = {
|
|
85
|
-
initialSeed:
|
|
85
|
+
initialSeed: 188222656,
|
|
86
86
|
fuzzRuns: 100,
|
|
87
|
-
mode: "
|
|
87
|
+
mode: "consoleColor",
|
|
88
88
|
verbosity: verbosity,
|
|
89
89
|
globs: [],
|
|
90
|
-
paths: ["/
|
|
90
|
+
paths: ["/Users/dillonkearns/src/github.com/dillonkearns/elm-pages-v3-beta/generator/review/tests/Pages/Review/NoContractViolationsTest.elm"],
|
|
91
91
|
};
|
|
92
92
|
reporter = Elm.Reporter.init({ flags: flags });
|
|
93
93
|
|
package/generator/src/build.js
CHANGED
|
@@ -18,6 +18,7 @@ const esbuild = require("esbuild");
|
|
|
18
18
|
const { createHash } = require("crypto");
|
|
19
19
|
const { merge_vite_configs } = require("./vite-utils.js");
|
|
20
20
|
const { resolveConfig } = require("./config.js");
|
|
21
|
+
const globby = require("globby");
|
|
21
22
|
|
|
22
23
|
let pool = [];
|
|
23
24
|
let pagesReady;
|
|
@@ -117,10 +118,25 @@ async function run(options) {
|
|
|
117
118
|
"dist/elm-stuff/elm-pages/index.html",
|
|
118
119
|
"utf-8"
|
|
119
120
|
);
|
|
120
|
-
const
|
|
121
|
+
const preloadFiles = [
|
|
122
|
+
`elm.${browserElmHash}.js`,
|
|
123
|
+
...Object.entries(manifest).map((entry) => entry[1].file),
|
|
124
|
+
].map((file) => path.join(options.base, file));
|
|
125
|
+
const userProcessedPreloads = preloadFiles.flatMap((file) => {
|
|
126
|
+
const userPreloadForFile = config.preloadTagForFile(file);
|
|
127
|
+
if (userPreloadForFile === true) {
|
|
128
|
+
return [defaultPreloadForFile(file)];
|
|
129
|
+
} else if (userPreloadForFile === false) {
|
|
130
|
+
return [];
|
|
131
|
+
} else if (typeof userPreloadForFile === "string") {
|
|
132
|
+
return [userPreloadForFile];
|
|
133
|
+
} else {
|
|
134
|
+
throw `I expected preloadTagForFile in elm-pages.config.mjs to return a string or boolean, but instead it returned: ${userPreloadForFile}`;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
121
137
|
|
|
122
138
|
const processedIndexTemplate = indexTemplate
|
|
123
|
-
.replace("<!-- PLACEHOLDER_PRELOADS -->",
|
|
139
|
+
.replace("<!-- PLACEHOLDER_PRELOADS -->", userProcessedPreloads.join(""))
|
|
124
140
|
.replace(
|
|
125
141
|
'<script defer src="/elm.js" type="text/javascript"></script>',
|
|
126
142
|
`<script defer src="/elm.${browserElmHash}.js" type="text/javascript"></script>`
|
|
@@ -139,21 +155,20 @@ async function run(options) {
|
|
|
139
155
|
metafile: true,
|
|
140
156
|
bundle: true,
|
|
141
157
|
watch: false,
|
|
142
|
-
logLevel: "
|
|
158
|
+
logLevel: "silent",
|
|
143
159
|
})
|
|
144
160
|
.then((result) => {
|
|
145
|
-
|
|
161
|
+
try {
|
|
162
|
+
global.portsFilePath = Object.keys(result.metafile.outputs)[0];
|
|
163
|
+
} catch (e) {}
|
|
146
164
|
})
|
|
147
165
|
.catch((error) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
console.warn("No port-data-source file found.");
|
|
155
|
-
} else {
|
|
156
|
-
console.error("Failed to load port-data-source file", error);
|
|
166
|
+
const portDataSourceFileFound =
|
|
167
|
+
globby.sync("./port-data-source.*").length > 0;
|
|
168
|
+
if (portDataSourceFileFound) {
|
|
169
|
+
// don't present error if there are no files matching port-data-source
|
|
170
|
+
// if there are files matching port-data-source, warn the user in case something went wrong loading it
|
|
171
|
+
console.error("Failed to start port-data-source watcher", error);
|
|
157
172
|
}
|
|
158
173
|
});
|
|
159
174
|
// TODO extract common code for compiling ports file?
|
|
@@ -620,6 +635,31 @@ async function runAdapter(adaptFn, processedIndexTemplate) {
|
|
|
620
635
|
}
|
|
621
636
|
}
|
|
622
637
|
|
|
638
|
+
// Source: https://github.com/vitejs/vite/blob/c53ffec3465d2d28d08d29ca61313469e03f5dd6/playground/ssr-vue/src/entry-server.js#L50-L68
|
|
639
|
+
/**
|
|
640
|
+
* @param {string} file
|
|
641
|
+
*/
|
|
642
|
+
function defaultPreloadForFile(file) {
|
|
643
|
+
if (file.endsWith(".js")) {
|
|
644
|
+
return `<link rel="modulepreload" crossorigin href="${file}">`;
|
|
645
|
+
} else if (file.endsWith(".css")) {
|
|
646
|
+
return `<link rel="preload" href="${file}" as="style">`;
|
|
647
|
+
} else if (file.endsWith(".woff")) {
|
|
648
|
+
return ` <link rel="preload" href="${file}" as="font" type="font/woff" crossorigin>`;
|
|
649
|
+
} else if (file.endsWith(".woff2")) {
|
|
650
|
+
return ` <link rel="preload" href="${file}" as="font" type="font/woff2" crossorigin>`;
|
|
651
|
+
} else if (file.endsWith(".gif")) {
|
|
652
|
+
return ` <link rel="preload" href="${file}" as="image" type="image/gif">`;
|
|
653
|
+
} else if (file.endsWith(".jpg") || file.endsWith(".jpeg")) {
|
|
654
|
+
return ` <link rel="preload" href="${file}" as="image" type="image/jpeg">`;
|
|
655
|
+
} else if (file.endsWith(".png")) {
|
|
656
|
+
return ` <link rel="preload" href="${file}" as="image" type="image/png">`;
|
|
657
|
+
} else {
|
|
658
|
+
// TODO
|
|
659
|
+
return "";
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
623
663
|
/** @typedef { { route : string; contentJson : string; head : SeoTag[]; html: string; body: string; } } FromElm */
|
|
624
664
|
/** @typedef {HeadTag | JsonLdTag} SeoTag */
|
|
625
665
|
/** @typedef {{ name: string; attributes: string[][]; type: 'head' }} HeadTag */
|
package/generator/src/config.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
const path = require("path");
|
|
2
2
|
|
|
3
3
|
async function resolveConfig() {
|
|
4
|
-
|
|
4
|
+
const initialConfig = await await import(
|
|
5
|
+
path.join(process.cwd(), "elm-pages.config.mjs")
|
|
6
|
+
)
|
|
5
7
|
.then(async (elmPagesConfig) => {
|
|
6
8
|
return (
|
|
7
9
|
elmPagesConfig.default || {
|
|
@@ -18,21 +20,21 @@ async function resolveConfig() {
|
|
|
18
20
|
vite: {},
|
|
19
21
|
};
|
|
20
22
|
});
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
preloadTagForFile: function () {
|
|
26
|
+
return true;
|
|
27
|
+
},
|
|
28
|
+
headTagsTemplate: defaultHeadTagsTemplate,
|
|
29
|
+
vite: {},
|
|
30
|
+
...initialConfig,
|
|
31
|
+
};
|
|
21
32
|
}
|
|
22
33
|
|
|
23
34
|
function defaultHeadTagsTemplate(context) {
|
|
24
35
|
return `
|
|
25
36
|
<link rel="stylesheet" href="/style.css" />
|
|
26
|
-
<meta charset="UTF-8" />
|
|
27
|
-
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
28
37
|
<meta name="generator" content="elm-pages v${context.cliVersion}" />
|
|
29
|
-
<meta name="mobile-web-app-capable" content="yes" />
|
|
30
|
-
<meta name="theme-color" content="#ffffff" />
|
|
31
|
-
<meta name="apple-mobile-web-app-capable" content="yes" />
|
|
32
|
-
<meta
|
|
33
|
-
name="apple-mobile-web-app-status-bar-style"
|
|
34
|
-
content="black-translucent"
|
|
35
|
-
/>
|
|
36
38
|
`;
|
|
37
39
|
}
|
|
38
40
|
|
|
@@ -27,6 +27,7 @@ const esbuild = require("esbuild");
|
|
|
27
27
|
const { merge_vite_configs } = require("./vite-utils.js");
|
|
28
28
|
const { templateHtml } = require("./pre-render-html.js");
|
|
29
29
|
const { resolveConfig } = require("./config.js");
|
|
30
|
+
const globby = require("globby");
|
|
30
31
|
|
|
31
32
|
/**
|
|
32
33
|
* @param {{ port: string; base: string; https: boolean; debug: boolean; }} options
|
|
@@ -147,7 +148,7 @@ async function start(options) {
|
|
|
147
148
|
metafile: true,
|
|
148
149
|
bundle: true,
|
|
149
150
|
watch: true,
|
|
150
|
-
logLevel: "
|
|
151
|
+
logLevel: "silent",
|
|
151
152
|
|
|
152
153
|
outdir: ".elm-pages/compiled-ports",
|
|
153
154
|
entryNames: "[dir]/[name]-[hash]",
|
|
@@ -157,11 +158,13 @@ async function start(options) {
|
|
|
157
158
|
name: "example",
|
|
158
159
|
setup(build) {
|
|
159
160
|
build.onEnd((result) => {
|
|
160
|
-
|
|
161
|
+
try {
|
|
162
|
+
global.portsFilePath = Object.keys(result.metafile.outputs)[0];
|
|
161
163
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
clients.forEach((client) => {
|
|
165
|
+
client.response.write(`data: content.dat\n\n`);
|
|
166
|
+
});
|
|
167
|
+
} catch (e) {}
|
|
165
168
|
});
|
|
166
169
|
},
|
|
167
170
|
},
|
|
@@ -171,7 +174,13 @@ async function start(options) {
|
|
|
171
174
|
console.log("Watching port-data-source...");
|
|
172
175
|
})
|
|
173
176
|
.catch((error) => {
|
|
174
|
-
|
|
177
|
+
const portDataSourceFileFound =
|
|
178
|
+
globby.sync("./port-data-source.*").length > 0;
|
|
179
|
+
if (portDataSourceFileFound) {
|
|
180
|
+
// don't present error if there are no files matching port-data-source
|
|
181
|
+
// if there are files matching port-data-source, warn the user in case something went wrong loading it
|
|
182
|
+
console.error("Failed to start port-data-source watcher", error);
|
|
183
|
+
}
|
|
175
184
|
});
|
|
176
185
|
|
|
177
186
|
const app = connect()
|
|
@@ -25,20 +25,18 @@ function templateHtml(devMode, userHeadTagsTemplate) {
|
|
|
25
25
|
return /* html */ `<!DOCTYPE html>
|
|
26
26
|
<!-- ROOT --><html lang="en">
|
|
27
27
|
<head>
|
|
28
|
+
<meta charset="UTF-8" />
|
|
28
29
|
<title><!-- PLACEHOLDER_TITLE --></title>
|
|
29
30
|
${
|
|
30
31
|
devMode
|
|
31
|
-
?
|
|
32
|
-
<script src="/hmr.js" type="text/javascript"></script>
|
|
32
|
+
? `<script src="/hmr.js" type="text/javascript"></script>
|
|
33
33
|
<link rel="stylesheet" href="/dev-style.css">`
|
|
34
|
-
:
|
|
35
|
-
<!-- PLACEHOLDER_PRELOADS -->`
|
|
34
|
+
: `<!-- PLACEHOLDER_PRELOADS -->`
|
|
36
35
|
}
|
|
37
36
|
<script defer src="/elm.js" type="text/javascript"></script>
|
|
38
37
|
${
|
|
39
38
|
devMode
|
|
40
|
-
? `<script src="/elm-pages.js" type="module"></script
|
|
41
|
-
`
|
|
39
|
+
? `<script src="/elm-pages.js" type="module"></script>`
|
|
42
40
|
: `<script defer src="${path.join(
|
|
43
41
|
__dirname,
|
|
44
42
|
"../static-code/elm-pages.js"
|
package/generator/src/render.js
CHANGED
|
@@ -9,6 +9,7 @@ const preRenderHtml = require("./pre-render-html.js");
|
|
|
9
9
|
const { lookupOrPerform } = require("./request-cache.js");
|
|
10
10
|
const kleur = require("kleur");
|
|
11
11
|
const cookie = require("cookie-signature");
|
|
12
|
+
const { compatibilityKey } = require("../compatibility-key.js");
|
|
12
13
|
kleur.enabled = true;
|
|
13
14
|
|
|
14
15
|
process.on("unhandledRejection", (error) => {
|
|
@@ -98,6 +99,7 @@ function runElmApp(
|
|
|
98
99
|
app = elmModule.Elm.Main.init({
|
|
99
100
|
flags: {
|
|
100
101
|
mode,
|
|
102
|
+
compatibilityKey,
|
|
101
103
|
request: {
|
|
102
104
|
payload: modifiedRequest,
|
|
103
105
|
kind: "single-page",
|
|
@@ -43,11 +43,16 @@ function headTag(rootModifiers) {
|
|
|
43
43
|
|
|
44
44
|
function toString(/** @type { SeoTag[] } */ tags) {
|
|
45
45
|
return tags
|
|
46
|
-
.
|
|
46
|
+
.flatMap((headTag) => {
|
|
47
47
|
if (headTag.type === "head") {
|
|
48
|
-
return appendTag(headTag);
|
|
48
|
+
return [appendTag(headTag)];
|
|
49
49
|
} else if (headTag.type === "json-ld") {
|
|
50
|
-
return appendJsonLdTag(headTag);
|
|
50
|
+
return [appendJsonLdTag(headTag)];
|
|
51
|
+
} else if (headTag.type === "stripped") {
|
|
52
|
+
console.warn(
|
|
53
|
+
`WARNING: Head.nonLoadingTag value ignored because it used a loading tag: ${headTag.message}`
|
|
54
|
+
);
|
|
55
|
+
return [];
|
|
51
56
|
} else {
|
|
52
57
|
throw new Error(`Unknown tag type ${JSON.stringify(headTag)}`);
|
|
53
58
|
}
|
|
@@ -55,7 +60,7 @@ function toString(/** @type { SeoTag[] } */ tags) {
|
|
|
55
60
|
.join("");
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
/** @typedef {HeadTag | JsonLdTag} SeoTag */
|
|
63
|
+
/** @typedef {HeadTag | JsonLdTag | StrippedTag} SeoTag */
|
|
59
64
|
|
|
60
65
|
/** @typedef {{ name: string; attributes: string[][]; type: 'head' }} HeadTag */
|
|
61
66
|
function appendTag(/** @type {HeadTag} */ tagDetails) {
|
|
@@ -66,6 +71,8 @@ function appendTag(/** @type {HeadTag} */ tagDetails) {
|
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
/** @typedef {{ contents: Object; type: 'json-ld' }} JsonLdTag */
|
|
74
|
+
/** @typedef {{ message: string; type: 'stripped' }} StrippedTag */
|
|
75
|
+
|
|
69
76
|
function appendJsonLdTag(/** @type {JsonLdTag} */ tagDetails) {
|
|
70
77
|
return `<script type="application/ld+json">
|
|
71
78
|
${JSON.stringify(tagDetails.contents)}
|
|
@@ -14,6 +14,11 @@ config =
|
|
|
14
14
|
|
|
15
15
|
head : DataSource (List Head.Tag)
|
|
16
16
|
head =
|
|
17
|
-
[ Head.
|
|
17
|
+
[ Head.metaName "viewport" (Head.raw "width=device-width,initial-scale=1")
|
|
18
|
+
, Head.metaName "mobile-web-app-capable" (Head.raw "yes")
|
|
19
|
+
, Head.metaName "theme-color" (Head.raw "#ffffff")
|
|
20
|
+
, Head.metaName "apple-mobile-web-app-capable" (Head.raw "yes")
|
|
21
|
+
, Head.metaName "apple-mobile-web-app-status-bar-style" (Head.raw "black-translucent")
|
|
22
|
+
, Head.sitemapLink "/sitemap.xml"
|
|
18
23
|
]
|
|
19
24
|
|> DataSource.succeed
|
package/package.json
CHANGED
package/src/ApiRoute.elm
CHANGED
|
@@ -11,9 +11,6 @@ module ApiRoute exposing
|
|
|
11
11
|
to a DataSource so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in
|
|
12
12
|
the DataSource for your ApiRoutes, and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.
|
|
13
13
|
|
|
14
|
-
In a future release, ApiRoutes may be able to run at request-time in a serverless function, allowing you to use pure Elm code to create dynamic APIs, and even pulling in data from
|
|
15
|
-
DataSources dynamically.
|
|
16
|
-
|
|
17
14
|
@docs ApiRoute, ApiRouteBuilder, Response
|
|
18
15
|
|
|
19
16
|
@docs capture, literal, slash, succeed
|
package/src/DataSource.elm
CHANGED
|
@@ -7,7 +7,7 @@ module DataSource exposing
|
|
|
7
7
|
, map2, map3, map4, map5, map6, map7, map8, map9
|
|
8
8
|
)
|
|
9
9
|
|
|
10
|
-
{-| In an `elm-pages` app, each
|
|
10
|
+
{-| In an `elm-pages` app, each Route Module can define a value `data` which is a `DataSource` that will be resolved **before** `init` is called. That means it is also available
|
|
11
11
|
when the page's HTML is pre-rendered during the build step. You can also access the resolved data in `head` to use it for the page's SEO meta tags.
|
|
12
12
|
|
|
13
13
|
A `DataSource` lets you pull in data from:
|
|
@@ -20,9 +20,17 @@ A `DataSource` lets you pull in data from:
|
|
|
20
20
|
- Or any combination of the above, using `DataSource.map2`, `DataSource.andThen`, or other combining/continuing helpers from this module
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## DataSource's vs. Effect's/Cmd's
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
DataSource's are always resolved before the page is rendered and sent to the browser. A DataSource is never executed
|
|
26
|
+
in the Browser. Instead, the resolved data from the DataSource is passed down to the Browser - it has been resolved
|
|
27
|
+
before any client-side JavaScript ever executes. In the case of a pre-rendered route, this is during the CLI build phase,
|
|
28
|
+
and for server-rendered routes its DataSource is resolved on the server.
|
|
29
|
+
|
|
30
|
+
Effect's/Cmd's are never executed on the CLI or server, they are only executed in the Browser. The data from a Route Module's
|
|
31
|
+
`init` function is used to render the initial HTML on the server or build step, but the Effect isn't executed and `update` is never called
|
|
32
|
+
before the page is hydrated in the Browser. This gives a deterministic mental model of what the first render will look like,
|
|
33
|
+
and a nicely typed way to define the initial `Data` you have to render your initial view.
|
|
26
34
|
|
|
27
35
|
Because `elm-pages` hydrates into a full Elm single-page app, it does need the data in order to initialize the Elm app.
|
|
28
36
|
So why not just get the data the old-fashioned way, with `elm/http`, for example?
|
package/src/Head.elm
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module Head exposing
|
|
2
2
|
( Tag, metaName, metaProperty, metaRedirect
|
|
3
3
|
, rssLink, sitemapLink, rootLanguage, manifestLink
|
|
4
|
+
, nonLoadingNode
|
|
4
5
|
, structuredData
|
|
5
6
|
, AttributeValue
|
|
6
7
|
, currentPageFullUrl, urlAttribute, raw
|
|
@@ -20,6 +21,8 @@ writing a plugin package to extend `elm-pages`.
|
|
|
20
21
|
@docs Tag, metaName, metaProperty, metaRedirect
|
|
21
22
|
@docs rssLink, sitemapLink, rootLanguage, manifestLink
|
|
22
23
|
|
|
24
|
+
@docs nonLoadingNode
|
|
25
|
+
|
|
23
26
|
|
|
24
27
|
## Structured Data
|
|
25
28
|
|
|
@@ -45,9 +48,11 @@ writing a plugin package to extend `elm-pages`.
|
|
|
45
48
|
|
|
46
49
|
import Json.Encode
|
|
47
50
|
import LanguageTag exposing (LanguageTag)
|
|
51
|
+
import List.Extra
|
|
48
52
|
import MimeType
|
|
49
53
|
import Pages.Internal.String as String
|
|
50
54
|
import Pages.Url
|
|
55
|
+
import Regex
|
|
51
56
|
|
|
52
57
|
|
|
53
58
|
{-| Values that can be passed to the generated `Pages.application` config
|
|
@@ -57,6 +62,7 @@ type Tag
|
|
|
57
62
|
= Tag Details
|
|
58
63
|
| StructuredData Json.Encode.Value
|
|
59
64
|
| RootModifier String String
|
|
65
|
+
| Stripped String
|
|
60
66
|
|
|
61
67
|
|
|
62
68
|
type alias Details =
|
|
@@ -211,6 +217,120 @@ canonicalLink maybePath =
|
|
|
211
217
|
]
|
|
212
218
|
|
|
213
219
|
|
|
220
|
+
{-| Escape hatch for any head tags that don't have high-level helpers. This lets you build arbitrary head nodes as long as they
|
|
221
|
+
are not loading or preloading directives.
|
|
222
|
+
|
|
223
|
+
Tags that do loading/pre-loading will not work from this function. `elm-pages` uses ViteJS for loading assets like
|
|
224
|
+
script tags, stylesheets, fonts, etc., and allows you to customize which assets to preload and how through the elm-pages.config.mjs file.
|
|
225
|
+
See the full discussion of the design in [#339](https://github.com/dillonkearns/elm-pages/discussions/339).
|
|
226
|
+
|
|
227
|
+
So for example the following tags would _not_ load if defined through `nonLoadingNode`, and would instead need to be registered through Vite:
|
|
228
|
+
|
|
229
|
+
- `<script src="...">`
|
|
230
|
+
- `<link href="/style.css">`
|
|
231
|
+
- `<link rel="preload">`
|
|
232
|
+
|
|
233
|
+
The following tag would successfully render as it is a non-loading tag:
|
|
234
|
+
|
|
235
|
+
Head.nonLoadingNode "link"
|
|
236
|
+
[ ( "rel", Head.raw "alternate" )
|
|
237
|
+
, ( "type", Head.raw "application/atom+xml" )
|
|
238
|
+
, ( "title", Head.raw "News" )
|
|
239
|
+
, ( "href", Head.raw "/news/atom" )
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
Renders the head tag:
|
|
243
|
+
|
|
244
|
+
`<link rel="alternate" type="application/atom+xml" title="News" href="/news/atom">`
|
|
245
|
+
|
|
246
|
+
-}
|
|
247
|
+
nonLoadingNode : String -> List ( String, AttributeValue ) -> Tag
|
|
248
|
+
nonLoadingNode nodeName attributes =
|
|
249
|
+
let
|
|
250
|
+
relTag : Maybe AttributeValue
|
|
251
|
+
relTag =
|
|
252
|
+
attributes
|
|
253
|
+
|> List.Extra.find (\( key, _ ) -> key == "rel")
|
|
254
|
+
|> Maybe.map Tuple.second
|
|
255
|
+
|
|
256
|
+
isPreloadDirective : Bool
|
|
257
|
+
isPreloadDirective =
|
|
258
|
+
case relTag of
|
|
259
|
+
Just (Raw rel) ->
|
|
260
|
+
let
|
|
261
|
+
isLinkTag : Bool
|
|
262
|
+
isLinkTag =
|
|
263
|
+
nodeName |> matchesKeyword "link"
|
|
264
|
+
in
|
|
265
|
+
isLinkTag
|
|
266
|
+
&& (rel
|
|
267
|
+
|> matchesOneKeyword
|
|
268
|
+
[ "preload"
|
|
269
|
+
, "modulepreload"
|
|
270
|
+
, "preconnect"
|
|
271
|
+
, "dns-prefetch"
|
|
272
|
+
, "stylesheet"
|
|
273
|
+
]
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
_ ->
|
|
277
|
+
False
|
|
278
|
+
in
|
|
279
|
+
if
|
|
280
|
+
(nodeName |> matchesKeyword "script")
|
|
281
|
+
|| isPreloadDirective
|
|
282
|
+
then
|
|
283
|
+
Stripped
|
|
284
|
+
("<"
|
|
285
|
+
++ nodeName
|
|
286
|
+
++ " "
|
|
287
|
+
++ (attributes
|
|
288
|
+
|> List.map
|
|
289
|
+
(\( name, value ) ->
|
|
290
|
+
name
|
|
291
|
+
++ "=\""
|
|
292
|
+
++ ((case value of
|
|
293
|
+
Raw rawValue ->
|
|
294
|
+
rawValue
|
|
295
|
+
|
|
296
|
+
_ ->
|
|
297
|
+
"<internals>"
|
|
298
|
+
)
|
|
299
|
+
++ "\""
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
|> String.join " "
|
|
303
|
+
)
|
|
304
|
+
++ " />"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
else
|
|
308
|
+
node nodeName attributes
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
matchesOneKeyword : List String -> String -> Bool
|
|
312
|
+
matchesOneKeyword keywords string =
|
|
313
|
+
List.any
|
|
314
|
+
(\keyword -> string |> matchesKeyword keyword)
|
|
315
|
+
keywords
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
matchesKeyword : String -> String -> Bool
|
|
319
|
+
matchesKeyword regex string =
|
|
320
|
+
string |> matchesRegex ("^\\s*" ++ regex ++ "\\s*$")
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
matchesRegex : String -> String -> Bool
|
|
324
|
+
matchesRegex regex string =
|
|
325
|
+
string
|
|
326
|
+
|> Regex.contains
|
|
327
|
+
(Regex.fromStringWith
|
|
328
|
+
{ caseInsensitive = True, multiline = True }
|
|
329
|
+
regex
|
|
330
|
+
|> Maybe.withDefault Regex.never
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
214
334
|
{-| Let's you link to your manifest.json file, see <https://developer.mozilla.org/en-US/docs/Web/Manifest#deploying_a_manifest>.
|
|
215
335
|
-}
|
|
216
336
|
manifestLink : String -> Tag
|
|
@@ -443,6 +563,12 @@ toJson canonicalSiteUrl currentPagePath tag =
|
|
|
443
563
|
, ( "keyValuePair", Json.Encode.list Json.Encode.string [ key, value ] )
|
|
444
564
|
]
|
|
445
565
|
|
|
566
|
+
Stripped message ->
|
|
567
|
+
Json.Encode.object
|
|
568
|
+
[ ( "type", Json.Encode.string "stripped" )
|
|
569
|
+
, ( "message", Json.Encode.string message )
|
|
570
|
+
]
|
|
571
|
+
|
|
446
572
|
|
|
447
573
|
encodeProperty : String -> String -> ( String, AttributeValue ) -> Json.Encode.Value
|
|
448
574
|
encodeProperty canonicalSiteUrl currentPagePath ( name, value ) =
|
package/src/HtmlPrinter.elm
CHANGED
|
@@ -8,15 +8,19 @@ import Test.Html.Internal.ElmHtml.ToString exposing (defaultFormatOptions, nodeT
|
|
|
8
8
|
import VirtualDom
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
htmlToString : Html msg -> String
|
|
12
|
-
htmlToString viewHtml =
|
|
11
|
+
htmlToString : Maybe { indent : Int, newLines : Bool } -> Html msg -> String
|
|
12
|
+
htmlToString formatOptions viewHtml =
|
|
13
13
|
case
|
|
14
14
|
Decode.decodeValue
|
|
15
15
|
(decodeElmHtml (\_ _ -> VirtualDom.Normal (Decode.succeed ())))
|
|
16
16
|
(asJsonView viewHtml)
|
|
17
17
|
of
|
|
18
18
|
Ok str ->
|
|
19
|
-
nodeToStringWithOptions
|
|
19
|
+
nodeToStringWithOptions
|
|
20
|
+
(formatOptions
|
|
21
|
+
|> Maybe.withDefault defaultFormatOptions
|
|
22
|
+
)
|
|
23
|
+
str
|
|
20
24
|
|
|
21
25
|
Err err ->
|
|
22
26
|
"Error pre-rendering HTML in HtmlPrinter.elm: " ++ Decode.errorToString err
|