@rollup-extras/plugin-html 2.0.1 → 2.1.1

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 CHANGED
@@ -1,3 +1,5 @@
1
+ ![Coverage](https://raw.githubusercontent.com/kshutkin/rollup-extras/refs/heads/coverage/plugin-html/badge.svg)
2
+
1
3
  # Plugin Html
2
4
 
3
5
  Rollup plugin to inject assets names into html template.
@@ -108,6 +110,12 @@ Optional, boolean, default: undefined.
108
110
 
109
111
  Defines if the plugin adds `nomodule` attributes for non-modular js chunks. By default, it is done only if we have an `es` output in one of the bundles processed by the plugin. Also plugin values `iife` outputs higher than `umd` and if we have both filters out `umd` ones. This can be changed only by providing a custom `assetFactory`.
110
112
 
113
+ ### modulepreload
114
+
115
+ Optional, boolean, default: true.
116
+
117
+ When enabled and the output format is `es`, the plugin automatically injects `<link rel="modulepreload">` tags into `<head>` for all static dependencies (including transitive ones) of each entry chunk. This eliminates the browser's sequential module discovery waterfall, allowing all chunks to be fetched in parallel as soon as the HTML is parsed.
118
+
111
119
  ### injectIntoHead
112
120
 
113
121
  Optional, RegExp | function | boolean, default: `(fileName: string) => fileName.endsWith(cssExtention)`.
@@ -35,10 +35,15 @@ export function simpleModuleScript(predicateSource) {
35
35
 
36
36
  export function combineAssetFactories(...factories) {
37
37
  return (fileName, content, type) => {
38
- for (const factory of factories) {
39
- const result = factory(fileName, content, type);
40
- if (result) {
41
- return result;
38
+ return next(0);
39
+
40
+ function next(start) {
41
+ for (let index = start; index < factories.length; index++) {
42
+ const result = factories[index](fileName, content, type);
43
+ if (result instanceof Promise) {
44
+ return result.then(asset => asset || next(index + 1));
45
+ }
46
+ if (result) return result;
42
47
  }
43
48
  }
44
49
  };
package/index.d.ts CHANGED
@@ -33,6 +33,7 @@ declare module '@rollup-extras/plugin-html' {
33
33
  useWriteBundle?: boolean;
34
34
  useEmittedTemplate?: boolean;
35
35
  conditionalLoading?: boolean;
36
+ modulepreload?: boolean;
36
37
  injectIntoHead?: PredicateSource;
37
38
  ignore?: PredicateSource;
38
39
  assetsFactory?: AssetFactory;
package/index.d.ts.map CHANGED
@@ -26,6 +26,6 @@
26
26
  null,
27
27
  null
28
28
  ],
29
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCQgBW,eAAeA;;iBASfC,uBAAuBA;;iBASvBC,kBAAkBA;;iBASlBC,qBAAqBA;;;;;;;;",
29
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBCQgBW,eAAeA;;iBASfC,uBAAuBA;;iBASvBC,kBAAkBA;;iBASlBC,qBAAqBA;;;;;;;;",
30
30
  "ignoreList": []
31
31
  }
package/index.js CHANGED
@@ -7,7 +7,7 @@ import logger from '@rollup-extras/utils/logger';
7
7
  import { multiConfigPluginBase } from '@rollup-extras/utils/multi-config-plugin-base';
8
8
  import { getOptionsObject } from '@rollup-extras/utils/options';
9
9
 
10
- import { getLinkElement, getModuleScriptElement, getNonModuleScriptElement, toAssetPredicate } from './shared.js';
10
+ import { getLinkElement, getModulePreloadElement, getModuleScriptElement, getNonModuleScriptElement, toAssetPredicate } from './shared.js';
11
11
 
12
12
  const defaultTemplate = '<!DOCTYPE html><html><head></head><body></body></html>';
13
13
  const headClosingElement = '</head>';
@@ -20,6 +20,7 @@ const defaults = {
20
20
  watch: true,
21
21
  useWriteBundle: false,
22
22
  emitFile: 'auto',
23
+ modulepreload: true,
23
24
  injectIntoHead: (( fileName) => fileName.endsWith(cssExtention)),
24
25
  ignore: (toAssetPredicate(false)),
25
26
  templateFactory: (
@@ -42,6 +43,7 @@ export default function (options = {}) {
42
43
  watch,
43
44
  emitFile,
44
45
  conditionalLoading,
46
+ modulepreload,
45
47
  logger,
46
48
  useWriteBundle,
47
49
  logLevel,
@@ -77,18 +79,18 @@ export default function (options = {}) {
77
79
  handleTemplateReadError(e);
78
80
  }
79
81
  } else {
80
- templateString = new Promise(resolve => {
81
- readFile(template, { encoding: 'utf8' })
82
- .then(newTemplateString => {
83
- if (!isUsableByDefaultTemplateFactory(newTemplateString) && !hasCustomTemplateFactory) {
84
- warnAboutUsingDefaultTemplate();
85
- resolve(defaultTemplate);
86
- } else {
87
- resolve(newTemplateString);
88
- }
89
- })
90
- .catch(handleTemplateReadError);
91
- });
82
+ templateString = readFile(template, { encoding: 'utf8' })
83
+ .then(newTemplateString => {
84
+ if (!isUsableByDefaultTemplateFactory(newTemplateString) && !hasCustomTemplateFactory) {
85
+ warnAboutUsingDefaultTemplate();
86
+ return defaultTemplate;
87
+ }
88
+ return newTemplateString;
89
+ })
90
+ .catch(e => {
91
+ handleTemplateReadError(e);
92
+ return defaultTemplate;
93
+ });
92
94
  }
93
95
  }
94
96
  }
@@ -280,6 +282,17 @@ export default function (options = {}) {
280
282
  type: 'asset',
281
283
  });
282
284
  }
285
+ if (modulepreload && options.format === 'es') {
286
+ const transitiveImports = collectTransitiveImports(bundle, fileName);
287
+ for (const importFileName of transitiveImports) {
288
+ const importAssetPath = relative(initialDir, join(dir, importFileName));
289
+ (assets.asset).push({
290
+ html: getModulePreloadElement(importAssetPath),
291
+ head: true,
292
+ type: 'asset',
293
+ });
294
+ }
295
+ }
283
296
  }
284
297
  }
285
298
  }
@@ -345,3 +358,23 @@ function defaultTemplateFactory(template, assets) {
345
358
  const bodyIndex = template.toLowerCase().indexOf(bodyClosingElement);
346
359
  return `${template.substring(0, bodyIndex)}${bodyElements.join('')}${template.substring(bodyIndex)}`;
347
360
  }
361
+
362
+ function collectTransitiveImports(bundle, entryFileName) {
363
+
364
+ const collected = new Set();
365
+
366
+ const queue = [... (bundle[entryFileName]).imports];
367
+ while (queue.length > 0) {
368
+ const importFileName = (queue.pop());
369
+
370
+ if (collected.has(importFileName)) {
371
+ continue;
372
+ }
373
+ collected.add(importFileName);
374
+ const chunk = bundle[importFileName];
375
+ if (chunk?.type === 'chunk') {
376
+ queue.push(...chunk.imports);
377
+ }
378
+ }
379
+ return collected;
380
+ }
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.0.1",
2
+ "version": "2.1.1",
3
3
  "license": "MIT",
4
4
  "name": "@rollup-extras/plugin-html",
5
5
  "author": "Konstantin Shutkin",
package/shared.js CHANGED
@@ -10,6 +10,10 @@ export function getModuleScriptElement(fileName) {
10
10
  return `<script src="${fileName}" type="module"></script>`;
11
11
  }
12
12
 
13
+ export function getModulePreloadElement(fileName) {
14
+ return `<link rel="modulepreload" href="${fileName}">`;
15
+ }
16
+
13
17
  export function toAssetPredicate(sourceOption) {
14
18
  if (typeof sourceOption === 'boolean') {
15
19
  return () => sourceOption;