amphora-html 5.1.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.
@@ -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
- filterBranch:
5
- name: Filter branch
8
+ deploy-docs:
9
+ name: Deploy docs
6
10
  runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
7
13
  steps:
8
- - uses: actions/checkout@master
9
- - name: Filter branch
10
- uses: actions/bin/filter@master
11
- with:
12
- args: branch master
13
- - name: Install
14
- uses: actions/npm@master
15
- with:
16
- args: install --prefix ./website
17
- - name: Update version
18
- uses: clay/docusaurus-github-action@master
19
- with:
20
- args: version
21
- - name: Build and push docs
22
- uses: clay/docusaurus-github-action@master
23
- env:
24
- DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
25
- with:
26
- args: deploy
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
@@ -6,9 +6,10 @@ const _ = require('lodash'),
6
6
  bluebird = require('bluebird'),
7
7
  setup = require('./setup'),
8
8
  styleguide = require('./styleguide'),
9
- url = require('url'),
10
9
  STYLE_TAG = 'style',
11
10
  SCRIPT_TAG = 'scripts',
11
+ MODULE_SCRIPT_TAG = 'module',
12
+ MODULEPRELOAD_TAG = 'modulepreload',
12
13
  MEDIA_DIRECTORY = path.join(process.cwd(), 'public');
13
14
 
14
15
  /**
@@ -147,15 +148,35 @@ function appendMediaToBottom(scripts, html) {
147
148
  return splice(html, index, scripts);
148
149
  }
149
150
 
150
- function injectTags(fileArray, site, tag) {
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) {
151
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;
152
166
 
153
167
  return bluebird.resolve(_.map(fileArray, function (file) {
154
168
  if (tag === STYLE_TAG) {
155
169
  return `<link rel="stylesheet" type="text/css" href="${file}${buster}">`;
156
- } else {
157
- return `<script type="text/javascript" src="${file}${buster}"></script>`;
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}">`;
158
176
  }
177
+
178
+ // Default: classic <script src=...> for SCRIPT_TAG and any other value.
179
+ return `<script type="text/javascript" src="${file}${buster}"></script>`;
159
180
  }).join('\n'));
160
181
  }
161
182
 
@@ -313,6 +334,14 @@ function configure(options, cacheBuster = '') {
313
334
  if (options && _.isObject(options)) {
314
335
  module.exports.editStylesTags = options.styles || false;
315
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
+ }
316
345
  } else {
317
346
  module.exports.editStylesTags = options;
318
347
  module.exports.editScriptsTags = options;
@@ -332,6 +361,7 @@ function configure(options, cacheBuster = '') {
332
361
  * @param {Object} state
333
362
  * @return {Function}
334
363
  */
364
+ /* eslint-disable complexity */
335
365
  function injectScriptsAndStyles(state) {
336
366
  const { locals } = state;
337
367
 
@@ -347,24 +377,50 @@ function injectScriptsAndStyles(state) {
347
377
  mediaMap = module.exports.getMediaMap(state);
348
378
 
349
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;
350
383
  if (setup.resolveMedia) mediaMap = setup.resolveMedia(mediaMap, locals) || mediaMap;
351
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
+
352
403
  if (!locals.edit) {
353
404
  mediaMap.styles = combineFileContents(mediaMap.styles, 'public/css', '/css/', STYLE_TAG);
354
405
  mediaMap.scripts = combineFileContents(mediaMap.scripts, 'public/js', '/js/', SCRIPT_TAG);
355
406
  } else {
356
- mediaMap.styles = module.exports.editStylesTags ? injectTags(mediaMap.styles, locals.site, STYLE_TAG) : combineFileContents(mediaMap.styles, 'public/css', '/css/', STYLE_TAG);
357
- mediaMap.scripts = module.exports.editScriptsTags ? injectTags(mediaMap.scripts, locals.site, SCRIPT_TAG) : combineFileContents(mediaMap.scripts, 'public/js', '/js/', SCRIPT_TAG);
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);
358
409
  }
359
410
 
360
411
  return bluebird.props(mediaMap)
361
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;
362
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")
363
418
  html = combinedFiles.scripts ? appendMediaToBottom(combinedFiles.scripts, html) : html; // If there are scripts, append them
364
419
  return html; // Return the compiled HTML
365
420
  });
366
421
  };
367
422
  }
423
+ /* eslint-enable complexity */
368
424
 
369
425
  module.exports.injectScriptsAndStyles = injectScriptsAndStyles;
370
426
  module.exports.getMediaMap = getMediaMap;
@@ -372,6 +428,10 @@ module.exports.cacheBuster = '';
372
428
  module.exports.configure = configure;
373
429
  module.exports.editStylesTags = false;
374
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;
375
435
 
376
436
  // For testing
377
437
  module.exports.getScriptFiles = getScriptFiles;
package/lib/render.js CHANGED
@@ -38,6 +38,9 @@ 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
46
  function applyPostRenderHooks(ref, locals, res, self) {
@@ -182,8 +185,12 @@ function logTime(hrStart, msg, route) {
182
185
  };
183
186
  }
184
187
 
185
- function configure({ editAssetTags, cacheBuster }) {
186
- mediaService.configure(editAssetTags, cacheBuster);
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.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "An HTML renderer for component data",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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]+/