amphora-html 5.0.0 → 5.2.0
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/.eslintrc +1 -1
- package/.github/workflows/ci.yml +40 -0
- package/.github/workflows/push.yml +35 -22
- package/lib/media.js +75 -6
- package/lib/media.test.js +2 -1
- package/lib/render.js +12 -5
- package/package.json +2 -1
- package/.circleci/config.yml +0 -44
package/.eslintrc
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ['**']
|
|
6
|
+
tags: ['**']
|
|
7
|
+
pull_request:
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
test:
|
|
19
|
+
name: Test (Node ${{ matrix.node-version }})
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
node-version: [18, 20]
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
29
|
+
uses: actions/setup-node@v4
|
|
30
|
+
with:
|
|
31
|
+
node-version: ${{ matrix.node-version }}
|
|
32
|
+
cache: npm
|
|
33
|
+
cache-dependency-path: package-lock.json
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: npm ci
|
|
37
|
+
|
|
38
|
+
- name: Run tests
|
|
39
|
+
run: npm test
|
|
40
|
+
|
|
@@ -1,26 +1,39 @@
|
|
|
1
|
-
on: push
|
|
2
1
|
name: Deploy to GitHub Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
|
|
3
7
|
jobs:
|
|
4
|
-
|
|
5
|
-
name:
|
|
8
|
+
deploy-docs:
|
|
9
|
+
name: Deploy docs
|
|
6
10
|
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
contents: write
|
|
7
13
|
steps:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
|
|
18
|
+
- name: Use Node.js
|
|
19
|
+
uses: actions/setup-node@v4
|
|
20
|
+
with:
|
|
21
|
+
node-version: '16'
|
|
22
|
+
|
|
23
|
+
- name: Install website dependencies
|
|
24
|
+
run: npm install
|
|
25
|
+
working-directory: website
|
|
26
|
+
|
|
27
|
+
- name: Update Docusaurus version
|
|
28
|
+
run: npm run version
|
|
29
|
+
working-directory: website
|
|
30
|
+
|
|
31
|
+
- name: Build Docusaurus site
|
|
32
|
+
run: npm run build
|
|
33
|
+
working-directory: website
|
|
34
|
+
|
|
35
|
+
- name: Publish docs to gh-pages
|
|
36
|
+
uses: peaceiris/actions-gh-pages@v4
|
|
37
|
+
with:
|
|
38
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
39
|
+
publish_dir: ./website/build/amphora-html
|
package/lib/media.js
CHANGED
|
@@ -8,6 +8,8 @@ const _ = require('lodash'),
|
|
|
8
8
|
styleguide = require('./styleguide'),
|
|
9
9
|
STYLE_TAG = 'style',
|
|
10
10
|
SCRIPT_TAG = 'scripts',
|
|
11
|
+
MODULE_SCRIPT_TAG = 'module',
|
|
12
|
+
MODULEPRELOAD_TAG = 'modulepreload',
|
|
11
13
|
MEDIA_DIRECTORY = path.join(process.cwd(), 'public');
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -146,15 +148,35 @@ function appendMediaToBottom(scripts, html) {
|
|
|
146
148
|
return splice(html, index, scripts);
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Build inline <link>/<script> tags for an array of asset paths. Used by edit
|
|
153
|
+
* mode (where assets are loaded individually rather than bundled) and by the
|
|
154
|
+
* Vite ESM pipeline (module / modulepreload).
|
|
155
|
+
*
|
|
156
|
+
* @param {string[]} fileArray asset paths
|
|
157
|
+
* @param {string} tag one of STYLE_TAG, SCRIPT_TAG, MODULE_SCRIPT_TAG, MODULEPRELOAD_TAG
|
|
158
|
+
* @returns {Promise<string>} newline-joined tag string
|
|
159
|
+
*/
|
|
160
|
+
function injectTags(fileArray, tag) {
|
|
150
161
|
var buster = module.exports.cacheBuster ? `?version=${module.exports.cacheBuster}` : '';
|
|
162
|
+
// When omitCacheBusterOnModules is enabled, content-hashed ESM files omit the
|
|
163
|
+
// ?version= query string — the hash in the filename already provides cache busting.
|
|
164
|
+
// Defaults to false so existing deployments are unaffected.
|
|
165
|
+
var moduleBuster = module.exports.omitCacheBusterOnModules ? '' : buster;
|
|
151
166
|
|
|
152
167
|
return bluebird.resolve(_.map(fileArray, function (file) {
|
|
153
168
|
if (tag === STYLE_TAG) {
|
|
154
169
|
return `<link rel="stylesheet" type="text/css" href="${file}${buster}">`;
|
|
155
|
-
} else {
|
|
156
|
-
return `<script type="
|
|
170
|
+
} else if (tag === MODULE_SCRIPT_TAG) {
|
|
171
|
+
return `<script type="module" src="${file}${moduleBuster}"></script>`;
|
|
172
|
+
} else if (tag === MODULEPRELOAD_TAG) {
|
|
173
|
+
// <link rel="modulepreload"> tells the browser to fetch ESM scripts early,
|
|
174
|
+
// during HTML parsing, before reaching the <script> tags at </body>.
|
|
175
|
+
return `<link rel="modulepreload" href="${file}${moduleBuster}">`;
|
|
157
176
|
}
|
|
177
|
+
|
|
178
|
+
// Default: classic <script src=...> for SCRIPT_TAG and any other value.
|
|
179
|
+
return `<script type="text/javascript" src="${file}${buster}"></script>`;
|
|
158
180
|
}).join('\n'));
|
|
159
181
|
}
|
|
160
182
|
|
|
@@ -166,7 +188,7 @@ function injectTags(fileArray, site, tag) {
|
|
|
166
188
|
function getMediaMap(state) {
|
|
167
189
|
return {
|
|
168
190
|
styles: module.exports.getStyleFiles(state),
|
|
169
|
-
scripts: module.exports.getScriptFiles(state)
|
|
191
|
+
scripts: module.exports.getScriptFiles(state),
|
|
170
192
|
};
|
|
171
193
|
}
|
|
172
194
|
|
|
@@ -238,6 +260,14 @@ function getStyleFiles(state) {
|
|
|
238
260
|
}
|
|
239
261
|
}
|
|
240
262
|
|
|
263
|
+
// check for clay compile linked? clay compile font files?
|
|
264
|
+
const defaultFontPath = path.join(assetDir, 'css', '_linked-fonts._default.css');
|
|
265
|
+
const siteFontPath = path.join(assetDir, 'css', `_linked-fonts.${siteStyleguide}.css`);
|
|
266
|
+
|
|
267
|
+
// add any default and site font css
|
|
268
|
+
cssFilePaths.push(defaultFontPath);
|
|
269
|
+
cssFilePaths.push(siteFontPath);
|
|
270
|
+
|
|
241
271
|
return cssFilePaths
|
|
242
272
|
.filter(files.fileExists)
|
|
243
273
|
.map(pathJoin(assetHost, assetPath, assetDir));
|
|
@@ -304,6 +334,14 @@ function configure(options, cacheBuster = '') {
|
|
|
304
334
|
if (options && _.isObject(options)) {
|
|
305
335
|
module.exports.editStylesTags = options.styles || false;
|
|
306
336
|
module.exports.editScriptsTags = options.scripts || false;
|
|
337
|
+
// modulepreload: when true, <link rel="modulepreload"> hints are injected
|
|
338
|
+
// into <head> for ESM scripts, and ?version= is omitted from module URLs
|
|
339
|
+
// since content-hashed filenames already provide cache busting.
|
|
340
|
+
// Opt-in only — defaults to false for backwards compatibility.
|
|
341
|
+
if (options.modulepreload !== undefined) {
|
|
342
|
+
module.exports.modulepreload = !!options.modulepreload;
|
|
343
|
+
module.exports.omitCacheBusterOnModules = !!options.modulepreload;
|
|
344
|
+
}
|
|
307
345
|
} else {
|
|
308
346
|
module.exports.editStylesTags = options;
|
|
309
347
|
module.exports.editScriptsTags = options;
|
|
@@ -323,6 +361,7 @@ function configure(options, cacheBuster = '') {
|
|
|
323
361
|
* @param {Object} state
|
|
324
362
|
* @return {Function}
|
|
325
363
|
*/
|
|
364
|
+
/* eslint-disable complexity */
|
|
326
365
|
function injectScriptsAndStyles(state) {
|
|
327
366
|
const { locals } = state;
|
|
328
367
|
|
|
@@ -338,24 +377,50 @@ function injectScriptsAndStyles(state) {
|
|
|
338
377
|
mediaMap = module.exports.getMediaMap(state);
|
|
339
378
|
|
|
340
379
|
// allow site to change the media map before applying it
|
|
380
|
+
// Expose rendered component names so resolveMedia can do per-component script resolution
|
|
381
|
+
// (e.g. pack-next manifest lookup) without needing a reference to the full state object.
|
|
382
|
+
locals._components = state._components;
|
|
341
383
|
if (setup.resolveMedia) mediaMap = setup.resolveMedia(mediaMap, locals) || mediaMap;
|
|
342
384
|
|
|
385
|
+
// moduleScripts: ESM scripts (e.g. from esbuild pack-next) that need type="module"
|
|
386
|
+
const moduleScriptFiles = mediaMap.moduleScripts || [];
|
|
387
|
+
|
|
388
|
+
mediaMap.moduleScripts = moduleScriptFiles.length
|
|
389
|
+
? injectTags(moduleScriptFiles, MODULE_SCRIPT_TAG)
|
|
390
|
+
: bluebird.resolve(false);
|
|
391
|
+
|
|
392
|
+
// modulePreloads: <link rel="modulepreload"> hints for <head>.
|
|
393
|
+
// Only active when configure({ modulepreload: true }) has been called — opt-in so
|
|
394
|
+
// sites not using the clay build pipeline are completely unaffected.
|
|
395
|
+
const modulePreloadFiles = module.exports.modulepreload
|
|
396
|
+
? mediaMap.modulePreloads || []
|
|
397
|
+
: [];
|
|
398
|
+
|
|
399
|
+
mediaMap.modulePreloads = modulePreloadFiles.length
|
|
400
|
+
? injectTags(modulePreloadFiles, MODULEPRELOAD_TAG)
|
|
401
|
+
: bluebird.resolve(false);
|
|
402
|
+
|
|
343
403
|
if (!locals.edit) {
|
|
344
404
|
mediaMap.styles = combineFileContents(mediaMap.styles, 'public/css', '/css/', STYLE_TAG);
|
|
345
405
|
mediaMap.scripts = combineFileContents(mediaMap.scripts, 'public/js', '/js/', SCRIPT_TAG);
|
|
346
406
|
} else {
|
|
347
|
-
mediaMap.styles = module.exports.editStylesTags ? injectTags(mediaMap.styles,
|
|
348
|
-
mediaMap.scripts = module.exports.editScriptsTags ? injectTags(mediaMap.scripts,
|
|
407
|
+
mediaMap.styles = module.exports.editStylesTags ? injectTags(mediaMap.styles, STYLE_TAG) : combineFileContents(mediaMap.styles, 'public/css', '/css/', STYLE_TAG);
|
|
408
|
+
mediaMap.scripts = module.exports.editScriptsTags ? injectTags(mediaMap.scripts, SCRIPT_TAG) : combineFileContents(mediaMap.scripts, 'public/js', '/js/', SCRIPT_TAG);
|
|
349
409
|
}
|
|
350
410
|
|
|
351
411
|
return bluebird.props(mediaMap)
|
|
352
412
|
.then(combinedFiles => {
|
|
413
|
+
// modulepreload hints go first in <head>, before CSS, so the browser can
|
|
414
|
+
// start fetching ESM scripts at the earliest possible moment.
|
|
415
|
+
html = combinedFiles.modulePreloads ? appendMediaToTop(combinedFiles.modulePreloads, html) : html;
|
|
353
416
|
html = combinedFiles.styles ? appendMediaToTop(combinedFiles.styles, html) : html; // If there are styles, append them
|
|
417
|
+
html = combinedFiles.moduleScripts ? appendMediaToBottom(combinedFiles.moduleScripts, html) : html; // ESM module scripts (type="module")
|
|
354
418
|
html = combinedFiles.scripts ? appendMediaToBottom(combinedFiles.scripts, html) : html; // If there are scripts, append them
|
|
355
419
|
return html; // Return the compiled HTML
|
|
356
420
|
});
|
|
357
421
|
};
|
|
358
422
|
}
|
|
423
|
+
/* eslint-enable complexity */
|
|
359
424
|
|
|
360
425
|
module.exports.injectScriptsAndStyles = injectScriptsAndStyles;
|
|
361
426
|
module.exports.getMediaMap = getMediaMap;
|
|
@@ -363,6 +428,10 @@ module.exports.cacheBuster = '';
|
|
|
363
428
|
module.exports.configure = configure;
|
|
364
429
|
module.exports.editStylesTags = false;
|
|
365
430
|
module.exports.editScriptsTags = false;
|
|
431
|
+
// Opt-in flags for the clay build (esbuild) pipeline.
|
|
432
|
+
// Enable via configure({ modulepreload: true }).
|
|
433
|
+
module.exports.modulepreload = false;
|
|
434
|
+
module.exports.omitCacheBusterOnModules = false;
|
|
366
435
|
|
|
367
436
|
// For testing
|
|
368
437
|
module.exports.getScriptFiles = getScriptFiles;
|
package/lib/media.test.js
CHANGED
|
@@ -10,7 +10,8 @@ const _ = require('lodash'),
|
|
|
10
10
|
|
|
11
11
|
jest.mock('nymag-fs', () => ({
|
|
12
12
|
fileExists: () => false,
|
|
13
|
-
readFilePromise: () => Promise.resolve('')
|
|
13
|
+
readFilePromise: () => Promise.resolve(''),
|
|
14
|
+
tryRequire: () => ({})
|
|
14
15
|
}));
|
|
15
16
|
|
|
16
17
|
describe(_.startCase(filename), () => {
|
package/lib/render.js
CHANGED
|
@@ -38,9 +38,12 @@ function applyRenderHooks(ref, data, locals) {
|
|
|
38
38
|
*
|
|
39
39
|
* @param {String} ref
|
|
40
40
|
* @param {Object} locals
|
|
41
|
+
* @param {Object} res
|
|
42
|
+
* @param {String} self Clay ref of the component being rendered, e.g.
|
|
43
|
+
* "site.com/_components/article/instances/foo"
|
|
41
44
|
* @returns {Function}
|
|
42
45
|
*/
|
|
43
|
-
function applyPostRenderHooks(ref, locals) {
|
|
46
|
+
function applyPostRenderHooks(ref, locals, res, self) {
|
|
44
47
|
return (html) => {
|
|
45
48
|
|
|
46
49
|
// skip postRender hooks in edit mode
|
|
@@ -50,7 +53,7 @@ function applyPostRenderHooks(ref, locals) {
|
|
|
50
53
|
|
|
51
54
|
return setup.plugins.filter((plugin) => plugin.postRender)
|
|
52
55
|
.reduce((val, plugin) => {
|
|
53
|
-
return plugin.postRender(ref, html, locals);
|
|
56
|
+
return plugin.postRender(ref, html, locals, res, self);
|
|
54
57
|
}, html);
|
|
55
58
|
};
|
|
56
59
|
}
|
|
@@ -148,7 +151,7 @@ function render(data, meta, res) {
|
|
|
148
151
|
return applyRenderHooks(state._layoutRef || state.self, data, state.locals)
|
|
149
152
|
.then(makeHtml(state))
|
|
150
153
|
.then(mediaService.injectScriptsAndStyles(state))
|
|
151
|
-
.then(applyPostRenderHooks(state._layoutRef || state.self, state.locals))
|
|
154
|
+
.then(applyPostRenderHooks(state._layoutRef || state.self, state.locals, res, meta._ref))
|
|
152
155
|
.then(result => {
|
|
153
156
|
res.type('text/html');
|
|
154
157
|
res.send(result);
|
|
@@ -182,8 +185,12 @@ function logTime(hrStart, msg, route) {
|
|
|
182
185
|
};
|
|
183
186
|
}
|
|
184
187
|
|
|
185
|
-
function configure({ editAssetTags, cacheBuster }) {
|
|
186
|
-
|
|
188
|
+
function configure({ editAssetTags, cacheBuster, modulepreload }) {
|
|
189
|
+
const mediaOptions = editAssetTags && typeof editAssetTags === 'object'
|
|
190
|
+
? Object.assign({}, editAssetTags, modulepreload !== undefined ? { modulepreload } : {})
|
|
191
|
+
: editAssetTags;
|
|
192
|
+
|
|
193
|
+
mediaService.configure(mediaOptions, cacheBuster);
|
|
187
194
|
}
|
|
188
195
|
|
|
189
196
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amphora-html",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"description": "An HTML renderer for component data",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"traverse": "^0.6.6"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
|
+
"clayhandlebars": "^5.0.1",
|
|
23
24
|
"coveralls": "^3.0.2",
|
|
24
25
|
"eslint": "^3.19.0",
|
|
25
26
|
"jest": "23.6.0"
|
package/.circleci/config.yml
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
version: 2
|
|
2
|
-
|
|
3
|
-
# Set defaults to reuse
|
|
4
|
-
defaults: &defaults
|
|
5
|
-
working_directory: ~/repo
|
|
6
|
-
docker:
|
|
7
|
-
- image: circleci/node:8.10.0
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
test:
|
|
11
|
-
<<: *defaults
|
|
12
|
-
steps:
|
|
13
|
-
- checkout
|
|
14
|
-
|
|
15
|
-
- restore_cache:
|
|
16
|
-
keys:
|
|
17
|
-
- v2-dependencies-{{ checksum "package.json" }}
|
|
18
|
-
# fallback to using the latest cache if no exact match is found
|
|
19
|
-
- v2-dependencies-
|
|
20
|
-
|
|
21
|
-
- run: npm install
|
|
22
|
-
- run:
|
|
23
|
-
name: Run tests
|
|
24
|
-
command: npm test
|
|
25
|
-
- run:
|
|
26
|
-
name: Send to Coveralls
|
|
27
|
-
command: cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
|
28
|
-
- save_cache:
|
|
29
|
-
paths:
|
|
30
|
-
- node_modules
|
|
31
|
-
key: v2-dependencies-{{ checksum "package.json" }}
|
|
32
|
-
|
|
33
|
-
- persist_to_workspace:
|
|
34
|
-
root: ~/repo
|
|
35
|
-
paths: .
|
|
36
|
-
|
|
37
|
-
workflows:
|
|
38
|
-
version: 2
|
|
39
|
-
test-deploy:
|
|
40
|
-
jobs:
|
|
41
|
-
- test:
|
|
42
|
-
filters:
|
|
43
|
-
tags:
|
|
44
|
-
only: /^v[0-9]+\.[0-9]+\.[0-9]+/
|