@web/rollup-plugin-html 0.0.0-canary-20230316175616

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.
Files changed (113) hide show
  1. package/README.md +5 -0
  2. package/dist/RollupPluginHTMLOptions.d.ts +68 -0
  3. package/dist/RollupPluginHTMLOptions.d.ts.map +1 -0
  4. package/dist/RollupPluginHTMLOptions.js +3 -0
  5. package/dist/RollupPluginHTMLOptions.js.map +1 -0
  6. package/dist/assets/utils.d.ts +7 -0
  7. package/dist/assets/utils.d.ts.map +1 -0
  8. package/dist/assets/utils.js +138 -0
  9. package/dist/assets/utils.js.map +1 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +7 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/input/InputData.d.ts +16 -0
  15. package/dist/input/InputData.d.ts.map +1 -0
  16. package/dist/input/InputData.js +3 -0
  17. package/dist/input/InputData.js.map +1 -0
  18. package/dist/input/addRollupInput.d.ts +4 -0
  19. package/dist/input/addRollupInput.d.ts.map +1 -0
  20. package/dist/input/addRollupInput.js +36 -0
  21. package/dist/input/addRollupInput.js.map +1 -0
  22. package/dist/input/extract/extractAssets.d.ts +11 -0
  23. package/dist/input/extract/extractAssets.d.ts.map +1 -0
  24. package/dist/input/extract/extractAssets.js +37 -0
  25. package/dist/input/extract/extractAssets.js.map +1 -0
  26. package/dist/input/extract/extractModules.d.ts +13 -0
  27. package/dist/input/extract/extractModules.d.ts.map +1 -0
  28. package/dist/input/extract/extractModules.js +68 -0
  29. package/dist/input/extract/extractModules.js.map +1 -0
  30. package/dist/input/extract/extractModulesAndAssets.d.ts +14 -0
  31. package/dist/input/extract/extractModulesAndAssets.d.ts.map +1 -0
  32. package/dist/input/extract/extractModulesAndAssets.js +30 -0
  33. package/dist/input/extract/extractModulesAndAssets.js.map +1 -0
  34. package/dist/input/getInputData.d.ts +13 -0
  35. package/dist/input/getInputData.d.ts.map +1 -0
  36. package/dist/input/getInputData.js +94 -0
  37. package/dist/input/getInputData.js.map +1 -0
  38. package/dist/input/normalizeInputOptions.d.ts +4 -0
  39. package/dist/input/normalizeInputOptions.d.ts.map +1 -0
  40. package/dist/input/normalizeInputOptions.js +38 -0
  41. package/dist/input/normalizeInputOptions.js.map +1 -0
  42. package/dist/output/createHTMLOutput.d.ts +33 -0
  43. package/dist/output/createHTMLOutput.d.ts.map +1 -0
  44. package/dist/output/createHTMLOutput.js +39 -0
  45. package/dist/output/createHTMLOutput.js.map +1 -0
  46. package/dist/output/emitAssets.d.ts +12 -0
  47. package/dist/output/emitAssets.d.ts.map +1 -0
  48. package/dist/output/emitAssets.js +69 -0
  49. package/dist/output/emitAssets.js.map +1 -0
  50. package/dist/output/getEntrypointBundles.d.ts +18 -0
  51. package/dist/output/getEntrypointBundles.d.ts.map +1 -0
  52. package/dist/output/getEntrypointBundles.js +59 -0
  53. package/dist/output/getEntrypointBundles.js.map +1 -0
  54. package/dist/output/getOutputHTML.d.ts +18 -0
  55. package/dist/output/getOutputHTML.d.ts.map +1 -0
  56. package/dist/output/getOutputHTML.js +91 -0
  57. package/dist/output/getOutputHTML.js.map +1 -0
  58. package/dist/output/hashInlineScripts.d.ts +3 -0
  59. package/dist/output/hashInlineScripts.d.ts.map +1 -0
  60. package/dist/output/hashInlineScripts.js +131 -0
  61. package/dist/output/hashInlineScripts.js.map +1 -0
  62. package/dist/output/injectAbsoluteBaseUrl.d.ts +3 -0
  63. package/dist/output/injectAbsoluteBaseUrl.d.ts.map +1 -0
  64. package/dist/output/injectAbsoluteBaseUrl.js +37 -0
  65. package/dist/output/injectAbsoluteBaseUrl.js.map +1 -0
  66. package/dist/output/injectBundles.d.ts +5 -0
  67. package/dist/output/injectBundles.d.ts.map +1 -0
  68. package/dist/output/injectBundles.js +36 -0
  69. package/dist/output/injectBundles.js.map +1 -0
  70. package/dist/output/injectServiceWorkerRegistration.d.ts +9 -0
  71. package/dist/output/injectServiceWorkerRegistration.d.ts.map +1 -0
  72. package/dist/output/injectServiceWorkerRegistration.js +37 -0
  73. package/dist/output/injectServiceWorkerRegistration.js.map +1 -0
  74. package/dist/output/injectedUpdatedAssetPaths.d.ts +14 -0
  75. package/dist/output/injectedUpdatedAssetPaths.d.ts.map +1 -0
  76. package/dist/output/injectedUpdatedAssetPaths.js +60 -0
  77. package/dist/output/injectedUpdatedAssetPaths.js.map +1 -0
  78. package/dist/output/utils.d.ts +2 -0
  79. package/dist/output/utils.d.ts.map +1 -0
  80. package/dist/output/utils.js +12 -0
  81. package/dist/output/utils.js.map +1 -0
  82. package/dist/rollupPluginHTML.d.ts +13 -0
  83. package/dist/rollupPluginHTML.d.ts.map +1 -0
  84. package/dist/rollupPluginHTML.js +183 -0
  85. package/dist/rollupPluginHTML.js.map +1 -0
  86. package/dist/utils.d.ts +4 -0
  87. package/dist/utils.d.ts.map +1 -0
  88. package/dist/utils.js +10 -0
  89. package/dist/utils.js.map +1 -0
  90. package/index.mjs +6 -0
  91. package/package.json +55 -0
  92. package/src/RollupPluginHTMLOptions.ts +92 -0
  93. package/src/assets/utils.ts +145 -0
  94. package/src/index.ts +14 -0
  95. package/src/input/InputData.ts +16 -0
  96. package/src/input/addRollupInput.ts +55 -0
  97. package/src/input/extract/extractAssets.ts +53 -0
  98. package/src/input/extract/extractModules.ts +78 -0
  99. package/src/input/extract/extractModulesAndAssets.ts +34 -0
  100. package/src/input/getInputData.ts +120 -0
  101. package/src/input/normalizeInputOptions.ts +47 -0
  102. package/src/output/createHTMLOutput.ts +87 -0
  103. package/src/output/emitAssets.ts +80 -0
  104. package/src/output/getEntrypointBundles.ts +88 -0
  105. package/src/output/getOutputHTML.ts +115 -0
  106. package/src/output/hashInlineScripts.ts +153 -0
  107. package/src/output/injectAbsoluteBaseUrl.ts +41 -0
  108. package/src/output/injectBundles.ts +44 -0
  109. package/src/output/injectServiceWorkerRegistration.ts +48 -0
  110. package/src/output/injectedUpdatedAssetPaths.ts +89 -0
  111. package/src/output/utils.ts +5 -0
  112. package/src/rollupPluginHTML.ts +225 -0
  113. package/src/utils.ts +9 -0
package/dist/utils.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createError = exports.NOOP_IMPORT = void 0;
4
+ const PLUGIN = '[@web/rollup-plugin-html]';
5
+ exports.NOOP_IMPORT = { importPath: '@web/rollup-plugin-html-noop' };
6
+ function createError(msg) {
7
+ return new Error(`${PLUGIN} ${msg}`);
8
+ }
9
+ exports.createError = createError;
10
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";;;AAEA,MAAM,MAAM,GAAG,2BAA2B,CAAC;AAE9B,QAAA,WAAW,GAAoB,EAAE,UAAU,EAAE,8BAA8B,EAAE,CAAC;AAE3F,SAAgB,WAAW,CAAC,GAAW;IACrC,OAAO,IAAI,KAAK,CAAC,GAAG,MAAM,IAAI,GAAG,EAAE,CAAC,CAAC;AACvC,CAAC;AAFD,kCAEC"}
package/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ // this file is autogenerated with the generate-mjs-dts-entrypoints script
2
+ import cjsEntrypoint from './dist/index.js';
3
+
4
+ const { rollupPluginHTML } = cjsEntrypoint;
5
+
6
+ export { rollupPluginHTML };
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@web/rollup-plugin-html",
3
+ "version": "0.0.0-canary-20230316175616",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Rollup plugin for bundling HTML files",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/modernweb-dev/web.git",
12
+ "directory": "packages/rollup-plugin-html"
13
+ },
14
+ "author": "modern-web",
15
+ "homepage": "https://github.com/modernweb-dev/web/tree/master/packages/rollup-plugin-html",
16
+ "main": "dist/index.js",
17
+ "exports": {
18
+ ".": {
19
+ "import": "./index.mjs",
20
+ "require": "./dist/index.js"
21
+ }
22
+ },
23
+ "engines": {
24
+ "node": ">=12.0.0"
25
+ },
26
+ "scripts": {
27
+ "demo:mpa": "rm -rf demo/dist && rollup -c demo/mpa/rollup.config.js --watch & yarn serve-demo",
28
+ "demo:spa": "rm -rf demo/dist && rollup -c demo/spa/rollup.config.js --watch & yarn serve-demo",
29
+ "serve-demo": "node ../dev-server/dist/bin.js --watch --root-dir demo/dist --app-index index.html --compatibility none --open",
30
+ "test": "mocha test/**/*.test.ts --require ts-node/register",
31
+ "test:watch": "mocha test/**/*.test.ts --require ts-node/register --watch --watch-files src,test"
32
+ },
33
+ "files": [
34
+ "*.js",
35
+ "*.mjs",
36
+ "dist",
37
+ "src"
38
+ ],
39
+ "keywords": [
40
+ "rollup",
41
+ "plugin",
42
+ "rollup-plugin",
43
+ "html"
44
+ ],
45
+ "dependencies": {
46
+ "@web/parse5-utils": "^1.3.0",
47
+ "glob": "^7.1.6",
48
+ "html-minifier-terser": "^6.0.0",
49
+ "parse5": "^6.0.1"
50
+ },
51
+ "devDependencies": {
52
+ "@types/html-minifier-terser": "^6.0.0",
53
+ "rollup": "^3.15.0"
54
+ }
55
+ }
@@ -0,0 +1,92 @@
1
+ import { OutputChunk, OutputOptions, OutputBundle } from 'rollup';
2
+ import { Attribute } from 'parse5';
3
+
4
+ export interface InputHTMLOptions {
5
+ /** The html source code. If set, overwrites path. */
6
+ html?: string;
7
+ /** Name of the HTML files when using the html option. */
8
+ name?: string;
9
+ /** Path to the HTML file, or glob to multiple HTML files. */
10
+ path?: string;
11
+ }
12
+
13
+ export interface RollupPluginHTMLOptions {
14
+ /** HTML file(s) to use as input. If not set, uses rollup input option. */
15
+ input?: string | InputHTMLOptions | (string | InputHTMLOptions)[];
16
+ /** HTML file glob pattern or patterns to ignore */
17
+ exclude?: string | string[];
18
+ /** Whether to minify the output HTML. */
19
+ minify?: boolean;
20
+ /** Whether to preserve or flatten the directory structure of the HTML file. */
21
+ flattenOutput?: boolean;
22
+ /** Directory to resolve absolute paths relative to, and to use as base for non-flatted filename output. */
23
+ rootDir?: string;
24
+ /** Path to load modules and assets from at runtime. */
25
+ publicPath?: string;
26
+ /** Transform asset source before output. */
27
+ transformAsset?: TransformAssetFunction | TransformAssetFunction[];
28
+ /** Transform HTML file before output. */
29
+ transformHtml?: TransformHtmlFunction | TransformHtmlFunction[];
30
+ /** Whether to extract and bundle assets referenced in HTML. Defaults to true. */
31
+ extractAssets?: boolean;
32
+ /** Define a full absolute url to your site (e.g. https://domain.com) */
33
+ absoluteBaseUrl?: string;
34
+ /** Whether to set full absolute urls for ['meta[property=og:image]', 'link[rel=canonical]', 'meta[property=og:url]'] or not. Requires a absoluteBaseUrl to be set. Default to true. */
35
+ absoluteSocialMediaUrls?: boolean;
36
+ /** Should a service worker registration script be injected. Defaults to false. */
37
+ injectServiceWorker?: boolean;
38
+ /** File system path to the generated service worker file */
39
+ serviceWorkerPath?: string;
40
+ /** Prefix to strip from absolute paths when resolving assets and scripts, for example when using a base path that does not exist on disk. */
41
+ absolutePathPrefix?: string;
42
+ /** When set to true, will insert meta tags for CSP and add script-src values for inline scripts by sha256-hashing the contents */
43
+ strictCSPInlineScripts?: boolean;
44
+ }
45
+
46
+ export interface GeneratedBundle {
47
+ name: string;
48
+ options: OutputOptions;
49
+ bundle: OutputBundle;
50
+ }
51
+
52
+ export interface ScriptModuleTag {
53
+ importPath: string;
54
+ attributes?: Attribute[];
55
+ code?: string;
56
+ }
57
+
58
+ export interface EntrypointBundle extends GeneratedBundle {
59
+ entrypoints: {
60
+ // path to import the entrypoint, can be used in an import statement
61
+ // or script tag directly
62
+ importPath: string;
63
+ // associated rollup chunk, useful if you need to get more information
64
+ // about the chunk. See the rollup docs for type definitions
65
+ chunk: OutputChunk;
66
+ attributes?: Attribute[];
67
+ }[];
68
+ }
69
+
70
+ export interface TransformHtmlArgs {
71
+ // the rollup bundle to be injected on the page. if there are multiple
72
+ // rollup output options, this will reference the first bundle
73
+ //
74
+ // if one of the input options was set, only the bundled module script contained
75
+ // in the HTML input are available to be injected in both the bundle and bundles
76
+ // options
77
+ bundle: EntrypointBundle;
78
+ // the rollup bundles to be injected on the page. if there is only one
79
+ // build output options, this will be an array with one option
80
+ bundles: Record<string, EntrypointBundle>;
81
+ htmlFileName: string;
82
+ }
83
+
84
+ export type TransformHtmlFunction = (
85
+ html: string,
86
+ args: TransformHtmlArgs,
87
+ ) => string | Promise<string>;
88
+
89
+ export type TransformAssetFunction = (
90
+ content: Buffer,
91
+ filePath: string,
92
+ ) => string | Buffer | Promise<string | Buffer>;
@@ -0,0 +1,145 @@
1
+ import { Document, Element } from 'parse5';
2
+ import path from 'path';
3
+ import { findElements, getTagName, getAttribute } from '@web/parse5-utils';
4
+ import { createError } from '../utils';
5
+ import { serialize } from 'v8';
6
+
7
+ const hashedLinkRels = ['stylesheet'];
8
+ const linkRels = [...hashedLinkRels, 'icon', 'manifest', 'apple-touch-icon', 'mask-icon'];
9
+
10
+ function getSrcSetUrls(srcset: string) {
11
+ if (!srcset) {
12
+ return [];
13
+ }
14
+ const srcsetParts = srcset.includes(',') ? srcset.split(',') : [srcset];
15
+ const urls = srcsetParts
16
+ .map(url => url.trim())
17
+ .map(url => (url.includes(' ') ? url.split(' ')[0] : url));
18
+ return urls;
19
+ }
20
+
21
+ function extractFirstUrlOfSrcSet(node: Element) {
22
+ const srcset = getAttribute(node, 'srcset');
23
+ if (!srcset) {
24
+ return '';
25
+ }
26
+ const urls = getSrcSetUrls(srcset);
27
+ return urls[0];
28
+ }
29
+
30
+ function isAsset(node: Element) {
31
+ let path = '';
32
+ switch (getTagName(node)) {
33
+ case 'img':
34
+ path = getAttribute(node, 'src') ?? '';
35
+ break;
36
+ case 'source':
37
+ if (getAttribute(node, 'src')) {
38
+ path = getAttribute(node, 'src') ?? '';
39
+ } else {
40
+ path = extractFirstUrlOfSrcSet(node) ?? '';
41
+ }
42
+ break;
43
+ case 'link':
44
+ if (linkRels.includes(getAttribute(node, 'rel') ?? '')) {
45
+ path = getAttribute(node, 'href') ?? '';
46
+ }
47
+ break;
48
+ case 'meta':
49
+ if (getAttribute(node, 'property') === 'og:image' && getAttribute(node, 'content')) {
50
+ path = getAttribute(node, 'content') ?? '';
51
+ }
52
+ break;
53
+ case 'script':
54
+ if (getAttribute(node, 'type') !== 'module' && getAttribute(node, 'src')) {
55
+ path = getAttribute(node, 'src') ?? '';
56
+ }
57
+ break;
58
+ default:
59
+ return false;
60
+ }
61
+ if (!path) {
62
+ return false;
63
+ }
64
+ try {
65
+ new URL(path);
66
+ return false;
67
+ } catch (e) {
68
+ return true;
69
+ }
70
+ }
71
+
72
+ export function isHashedAsset(node: Element) {
73
+ switch (getTagName(node)) {
74
+ case 'img':
75
+ return true;
76
+ case 'source':
77
+ return true;
78
+ case 'script':
79
+ return true;
80
+ case 'link':
81
+ return hashedLinkRels.includes(getAttribute(node, 'rel')!);
82
+ case 'meta':
83
+ return true;
84
+ default:
85
+ return false;
86
+ }
87
+ }
88
+
89
+ export function resolveAssetFilePath(
90
+ browserPath: string,
91
+ htmlDir: string,
92
+ projectRootDir: string,
93
+ absolutePathPrefix?: string,
94
+ ) {
95
+ const _browserPath =
96
+ absolutePathPrefix && browserPath[0] === '/'
97
+ ? '/' + path.posix.relative(absolutePathPrefix, browserPath)
98
+ : browserPath;
99
+ return path.join(
100
+ _browserPath.startsWith('/') ? projectRootDir : htmlDir,
101
+ _browserPath.split('/').join(path.sep),
102
+ );
103
+ }
104
+
105
+ export function getSourceAttribute(node: Element) {
106
+ switch (getTagName(node)) {
107
+ case 'img': {
108
+ return 'src';
109
+ }
110
+ case 'source': {
111
+ return getAttribute(node, 'src') ? 'src' : 'srcset';
112
+ }
113
+ case 'link': {
114
+ return 'href';
115
+ }
116
+ case 'script': {
117
+ return 'src';
118
+ }
119
+ case 'meta': {
120
+ return 'content';
121
+ }
122
+ default:
123
+ throw new Error(`Unknown node with tagname ${getTagName(node)}`);
124
+ }
125
+ }
126
+
127
+ export function getSourcePaths(node: Element) {
128
+ const key = getSourceAttribute(node);
129
+
130
+ const src = getAttribute(node, key);
131
+ if (typeof key !== 'string' || src === '') {
132
+ throw createError(`Missing attribute ${key} in element ${serialize(node)}`);
133
+ }
134
+
135
+ let paths: string[] = [];
136
+ if (src) {
137
+ paths = key !== 'srcset' ? [src] : getSrcSetUrls(src);
138
+ }
139
+
140
+ return paths;
141
+ }
142
+
143
+ export function findAssets(document: Document) {
144
+ return findElements(document, isAsset);
145
+ }
package/src/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { rollupPluginHTML, RollupPluginHtml } from './rollupPluginHTML';
2
+
3
+ export {
4
+ InputHTMLOptions,
5
+ RollupPluginHTMLOptions,
6
+ GeneratedBundle,
7
+ EntrypointBundle,
8
+ TransformHtmlArgs,
9
+ TransformHtmlFunction,
10
+ TransformAssetFunction,
11
+ } from './RollupPluginHTMLOptions';
12
+
13
+ export { rollupPluginHTML, RollupPluginHtml };
14
+ export default rollupPluginHTML;
@@ -0,0 +1,16 @@
1
+ import { ScriptModuleTag } from '../RollupPluginHTMLOptions';
2
+
3
+ export interface InputAsset {
4
+ filePath: string;
5
+ hashed: boolean;
6
+ content: Buffer;
7
+ }
8
+
9
+ export interface InputData {
10
+ html: string;
11
+ name: string;
12
+ moduleImports: ScriptModuleTag[];
13
+ inlineModules: ScriptModuleTag[];
14
+ assets: InputAsset[];
15
+ filePath?: string;
16
+ }
@@ -0,0 +1,55 @@
1
+ import { InputOptions } from 'rollup';
2
+ import { ScriptModuleTag } from '../RollupPluginHTMLOptions';
3
+ import { createError } from '../utils';
4
+
5
+ function fromEntries<V>(entries: [string, V][]) {
6
+ const obj: Record<string, V> = {};
7
+ for (const [k, v] of entries) {
8
+ obj[k] = v;
9
+ }
10
+ return obj;
11
+ }
12
+
13
+ export function addRollupInput(
14
+ inputOptions: InputOptions,
15
+ inputModuleIds: ScriptModuleTag[],
16
+ ): InputOptions {
17
+ // Add input module ids to existing input option, whether it's a string, array or object
18
+ // this way you can use multiple html plugins all adding their own inputs
19
+ if (!inputOptions.input) {
20
+ return { ...inputOptions, input: inputModuleIds.map(mod => mod.importPath) };
21
+ }
22
+
23
+ if (typeof inputOptions.input === 'string') {
24
+ return {
25
+ ...inputOptions,
26
+ input: [
27
+ ...(inputOptions?.input?.endsWith('.html') ? [] : [inputOptions.input]),
28
+ ...inputModuleIds.map(mod => mod.importPath),
29
+ ],
30
+ };
31
+ }
32
+
33
+ if (Array.isArray(inputOptions.input)) {
34
+ return {
35
+ ...inputOptions,
36
+ input: [...inputOptions.input, ...inputModuleIds.map(mod => mod.importPath)],
37
+ };
38
+ }
39
+
40
+ if (typeof inputOptions.input === 'object') {
41
+ return {
42
+ ...inputOptions,
43
+ input: {
44
+ ...inputOptions.input,
45
+ ...fromEntries(
46
+ inputModuleIds
47
+ .map(mod => mod.importPath)
48
+ .map(i => [i.split('/').slice(-1)[0].split('.')[0], i]),
49
+ ),
50
+ },
51
+ };
52
+ }
53
+
54
+ throw createError(`Unknown rollup input type. Supported inputs are string, array and object.`);
55
+ }
@@ -0,0 +1,53 @@
1
+ import { Document, serialize } from 'parse5';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { InputAsset } from '../InputData';
5
+ import {
6
+ findAssets,
7
+ getSourcePaths,
8
+ isHashedAsset,
9
+ resolveAssetFilePath,
10
+ } from '../../assets/utils';
11
+
12
+ export interface ExtractAssetsParams {
13
+ document: Document;
14
+ htmlFilePath: string;
15
+ htmlDir: string;
16
+ rootDir: string;
17
+ absolutePathPrefix?: string;
18
+ }
19
+
20
+ export function extractAssets(params: ExtractAssetsParams): InputAsset[] {
21
+ const assetNodes = findAssets(params.document);
22
+ const allAssets: InputAsset[] = [];
23
+
24
+ for (const node of assetNodes) {
25
+ const sourcePaths = getSourcePaths(node);
26
+ for (const sourcePath of sourcePaths) {
27
+ const filePath = resolveAssetFilePath(
28
+ sourcePath,
29
+ params.htmlDir,
30
+ params.rootDir,
31
+ params.absolutePathPrefix,
32
+ );
33
+ const hashed = isHashedAsset(node);
34
+ const alreadyHandled = allAssets.find(a => a.filePath === filePath && a.hashed === hashed);
35
+ if (!alreadyHandled) {
36
+ try {
37
+ fs.accessSync(filePath);
38
+ } catch (error) {
39
+ const elStr = serialize(node);
40
+ const htmlPath = path.relative(process.cwd(), params.htmlFilePath);
41
+ throw new Error(
42
+ `Could not find ${filePath} referenced from HTML file ${htmlPath} from element ${elStr}.`,
43
+ );
44
+ }
45
+
46
+ const content = fs.readFileSync(filePath);
47
+ allAssets.push({ filePath, hashed, content });
48
+ }
49
+ }
50
+ }
51
+
52
+ return allAssets;
53
+ }
@@ -0,0 +1,78 @@
1
+ import { findElements, getAttribute, getTagName, getTextContent, remove } from '@web/parse5-utils';
2
+ import { Document, Attribute } from 'parse5';
3
+ import path from 'path';
4
+ import crypto from 'crypto';
5
+ import { resolveAssetFilePath } from '../../assets/utils';
6
+ import { getAttributes } from '@web/parse5-utils';
7
+ import { ScriptModuleTag } from '../../RollupPluginHTMLOptions';
8
+
9
+ export interface ExtractModulesParams {
10
+ document: Document;
11
+ htmlDir: string;
12
+ rootDir: string;
13
+ absolutePathPrefix?: string;
14
+ }
15
+
16
+ function createContentHash(content: string) {
17
+ return crypto.createHash('md5').update(content).digest('hex');
18
+ }
19
+
20
+ function isAbsolute(src: string) {
21
+ try {
22
+ new URL(src);
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ export function extractModules(params: ExtractModulesParams) {
30
+ const { document, htmlDir, rootDir, absolutePathPrefix } = params;
31
+ const scriptNodes = findElements(
32
+ document,
33
+ e => getTagName(e) === 'script' && getAttribute(e, 'type') === 'module',
34
+ );
35
+
36
+ const moduleImports: ScriptModuleTag[] = [];
37
+ const inlineModules: ScriptModuleTag[] = [];
38
+
39
+ for (const scriptNode of scriptNodes) {
40
+ const src = getAttribute(scriptNode, 'src');
41
+
42
+ const allAttributes = getAttributes(scriptNode);
43
+ const attributes: Attribute[] = [];
44
+ for (const attributeName of Object.keys(allAttributes)) {
45
+ if (attributeName !== 'src' && attributeName !== 'type') {
46
+ attributes.push({ name: attributeName, value: allAttributes[attributeName] });
47
+ }
48
+ }
49
+
50
+ if (!src) {
51
+ // turn inline module (<script type="module"> ...code ... </script>)
52
+ const code = getTextContent(scriptNode);
53
+ // inline modules should be relative to the HTML file to resolve relative imports
54
+ // we make it unique with a content hash, so that duplicate modules are deduplicated
55
+ const importPath = path.posix.join(htmlDir, `/inline-module-${createContentHash(code)}.js`);
56
+ if (!inlineModules.find(tag => tag.importPath === importPath)) {
57
+ inlineModules.push({
58
+ importPath,
59
+ attributes,
60
+ code,
61
+ });
62
+ }
63
+ remove(scriptNode);
64
+ } else {
65
+ if (!isAbsolute(src)) {
66
+ // external script <script type="module" src="./foo.js"></script>
67
+ const importPath = resolveAssetFilePath(src, htmlDir, rootDir, absolutePathPrefix);
68
+ moduleImports.push({
69
+ importPath,
70
+ attributes,
71
+ });
72
+ remove(scriptNode);
73
+ }
74
+ }
75
+ }
76
+
77
+ return { moduleImports, inlineModules };
78
+ }
@@ -0,0 +1,34 @@
1
+ import path from 'path';
2
+ import { parse, serialize } from 'parse5';
3
+ import { extractModules } from './extractModules';
4
+ import { extractAssets } from './extractAssets';
5
+
6
+ export interface ExtractParams {
7
+ html: string;
8
+ htmlFilePath: string;
9
+ rootDir: string;
10
+ extractAssets: boolean;
11
+ absolutePathPrefix?: string;
12
+ }
13
+
14
+ export function extractModulesAndAssets(params: ExtractParams) {
15
+ const { html, htmlFilePath, rootDir, absolutePathPrefix } = params;
16
+ const htmlDir = path.dirname(htmlFilePath);
17
+ const document = parse(html);
18
+
19
+ // extract functions mutate the AST
20
+ const { moduleImports, inlineModules } = extractModules({
21
+ document,
22
+ htmlDir,
23
+ rootDir,
24
+ absolutePathPrefix,
25
+ });
26
+ const assets = params.extractAssets
27
+ ? extractAssets({ document, htmlDir, htmlFilePath, rootDir, absolutePathPrefix })
28
+ : [];
29
+
30
+ // turn mutated AST back to a string
31
+ const updatedHtmlString = serialize(document);
32
+
33
+ return { moduleImports, inlineModules, assets, htmlWithoutModules: updatedHtmlString };
34
+ }
@@ -0,0 +1,120 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import glob from 'glob';
4
+
5
+ import { createError } from '../utils';
6
+ import { RollupPluginHTMLOptions } from '../RollupPluginHTMLOptions';
7
+ import { InputData } from './InputData';
8
+ import { normalizeInputOptions } from './normalizeInputOptions';
9
+ import { extractModulesAndAssets } from './extract/extractModulesAndAssets';
10
+ import { InputOption } from 'rollup';
11
+
12
+ function resolveGlob(fromGlob: string, opts: glob.IOptions) {
13
+ const files = glob.sync(fromGlob, { ...opts, absolute: true });
14
+ return (
15
+ files
16
+ // filter out directories
17
+ .filter(filePath => !fs.lstatSync(filePath).isDirectory())
18
+ );
19
+ }
20
+
21
+ function getName(filePath: string, rootDir: string, flattenOutput = true) {
22
+ if (flattenOutput) {
23
+ return path.basename(filePath);
24
+ }
25
+ return path.relative(rootDir, filePath);
26
+ }
27
+
28
+ export interface CreateInputDataParams {
29
+ name: string;
30
+ html: string;
31
+ rootDir: string;
32
+ filePath?: string;
33
+ extractAssets: boolean;
34
+ absolutePathPrefix?: string;
35
+ }
36
+
37
+ function createInputData(params: CreateInputDataParams): InputData {
38
+ const { name, html, rootDir, filePath, extractAssets, absolutePathPrefix } = params;
39
+ const htmlFilePath = filePath ? filePath : path.resolve(rootDir, name);
40
+ const result = extractModulesAndAssets({
41
+ html,
42
+ htmlFilePath,
43
+ rootDir,
44
+ extractAssets,
45
+ absolutePathPrefix,
46
+ });
47
+
48
+ return {
49
+ html: result.htmlWithoutModules,
50
+ name,
51
+ inlineModules: result.inlineModules,
52
+ moduleImports: [...result.moduleImports, ...result.inlineModules],
53
+ assets: result.assets,
54
+ filePath,
55
+ };
56
+ }
57
+
58
+ export function getInputData(
59
+ pluginOptions: RollupPluginHTMLOptions,
60
+ rollupInput?: InputOption,
61
+ ): InputData[] {
62
+ const {
63
+ rootDir = process.cwd(),
64
+ flattenOutput,
65
+ extractAssets = true,
66
+ absolutePathPrefix,
67
+ exclude: ignore,
68
+ } = pluginOptions;
69
+ const allInputs = normalizeInputOptions(pluginOptions, rollupInput);
70
+
71
+ const result: InputData[] = [];
72
+ for (const input of allInputs) {
73
+ if (typeof input.html === 'string') {
74
+ const name = input.name ?? 'index.html';
75
+ const data = createInputData({
76
+ name,
77
+ html: input.html,
78
+ rootDir,
79
+ extractAssets,
80
+ absolutePathPrefix,
81
+ });
82
+ result.push(data);
83
+ } else if (typeof input.path === 'string') {
84
+ const filePaths = resolveGlob(input.path, { cwd: rootDir, ignore });
85
+ if (filePaths.length === 0) {
86
+ throw new Error(
87
+ `Could not find any HTML files for pattern: ${input.path}, resolved relative to ${rootDir}`,
88
+ );
89
+ }
90
+
91
+ for (const filePath of filePaths) {
92
+ const name = input.name ?? getName(filePath, rootDir, flattenOutput);
93
+ const html = fs.readFileSync(filePath, 'utf-8');
94
+ const data = createInputData({
95
+ name,
96
+ html,
97
+ rootDir,
98
+ filePath,
99
+ extractAssets,
100
+ absolutePathPrefix,
101
+ });
102
+ result.push(data);
103
+ }
104
+ } else {
105
+ throw createError('An input must specify either a path or html.');
106
+ }
107
+ }
108
+
109
+ for (const input of result) {
110
+ if (result.filter(r => r.name === input.name).length !== 1) {
111
+ throw createError(
112
+ `Found multiple HTML inputs configured with the same name, ` +
113
+ 'or with no name which defaults to index.html. Set a unique name on the' +
114
+ 'input option.',
115
+ );
116
+ }
117
+ }
118
+
119
+ return result;
120
+ }