@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.
- package/README.md +5 -0
- package/dist/RollupPluginHTMLOptions.d.ts +68 -0
- package/dist/RollupPluginHTMLOptions.d.ts.map +1 -0
- package/dist/RollupPluginHTMLOptions.js +3 -0
- package/dist/RollupPluginHTMLOptions.js.map +1 -0
- package/dist/assets/utils.d.ts +7 -0
- package/dist/assets/utils.d.ts.map +1 -0
- package/dist/assets/utils.js +138 -0
- package/dist/assets/utils.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/input/InputData.d.ts +16 -0
- package/dist/input/InputData.d.ts.map +1 -0
- package/dist/input/InputData.js +3 -0
- package/dist/input/InputData.js.map +1 -0
- package/dist/input/addRollupInput.d.ts +4 -0
- package/dist/input/addRollupInput.d.ts.map +1 -0
- package/dist/input/addRollupInput.js +36 -0
- package/dist/input/addRollupInput.js.map +1 -0
- package/dist/input/extract/extractAssets.d.ts +11 -0
- package/dist/input/extract/extractAssets.d.ts.map +1 -0
- package/dist/input/extract/extractAssets.js +37 -0
- package/dist/input/extract/extractAssets.js.map +1 -0
- package/dist/input/extract/extractModules.d.ts +13 -0
- package/dist/input/extract/extractModules.d.ts.map +1 -0
- package/dist/input/extract/extractModules.js +68 -0
- package/dist/input/extract/extractModules.js.map +1 -0
- package/dist/input/extract/extractModulesAndAssets.d.ts +14 -0
- package/dist/input/extract/extractModulesAndAssets.d.ts.map +1 -0
- package/dist/input/extract/extractModulesAndAssets.js +30 -0
- package/dist/input/extract/extractModulesAndAssets.js.map +1 -0
- package/dist/input/getInputData.d.ts +13 -0
- package/dist/input/getInputData.d.ts.map +1 -0
- package/dist/input/getInputData.js +94 -0
- package/dist/input/getInputData.js.map +1 -0
- package/dist/input/normalizeInputOptions.d.ts +4 -0
- package/dist/input/normalizeInputOptions.d.ts.map +1 -0
- package/dist/input/normalizeInputOptions.js +38 -0
- package/dist/input/normalizeInputOptions.js.map +1 -0
- package/dist/output/createHTMLOutput.d.ts +33 -0
- package/dist/output/createHTMLOutput.d.ts.map +1 -0
- package/dist/output/createHTMLOutput.js +39 -0
- package/dist/output/createHTMLOutput.js.map +1 -0
- package/dist/output/emitAssets.d.ts +12 -0
- package/dist/output/emitAssets.d.ts.map +1 -0
- package/dist/output/emitAssets.js +69 -0
- package/dist/output/emitAssets.js.map +1 -0
- package/dist/output/getEntrypointBundles.d.ts +18 -0
- package/dist/output/getEntrypointBundles.d.ts.map +1 -0
- package/dist/output/getEntrypointBundles.js +59 -0
- package/dist/output/getEntrypointBundles.js.map +1 -0
- package/dist/output/getOutputHTML.d.ts +18 -0
- package/dist/output/getOutputHTML.d.ts.map +1 -0
- package/dist/output/getOutputHTML.js +91 -0
- package/dist/output/getOutputHTML.js.map +1 -0
- package/dist/output/hashInlineScripts.d.ts +3 -0
- package/dist/output/hashInlineScripts.d.ts.map +1 -0
- package/dist/output/hashInlineScripts.js +131 -0
- package/dist/output/hashInlineScripts.js.map +1 -0
- package/dist/output/injectAbsoluteBaseUrl.d.ts +3 -0
- package/dist/output/injectAbsoluteBaseUrl.d.ts.map +1 -0
- package/dist/output/injectAbsoluteBaseUrl.js +37 -0
- package/dist/output/injectAbsoluteBaseUrl.js.map +1 -0
- package/dist/output/injectBundles.d.ts +5 -0
- package/dist/output/injectBundles.d.ts.map +1 -0
- package/dist/output/injectBundles.js +36 -0
- package/dist/output/injectBundles.js.map +1 -0
- package/dist/output/injectServiceWorkerRegistration.d.ts +9 -0
- package/dist/output/injectServiceWorkerRegistration.d.ts.map +1 -0
- package/dist/output/injectServiceWorkerRegistration.js +37 -0
- package/dist/output/injectServiceWorkerRegistration.js.map +1 -0
- package/dist/output/injectedUpdatedAssetPaths.d.ts +14 -0
- package/dist/output/injectedUpdatedAssetPaths.d.ts.map +1 -0
- package/dist/output/injectedUpdatedAssetPaths.js +60 -0
- package/dist/output/injectedUpdatedAssetPaths.js.map +1 -0
- package/dist/output/utils.d.ts +2 -0
- package/dist/output/utils.d.ts.map +1 -0
- package/dist/output/utils.js +12 -0
- package/dist/output/utils.js.map +1 -0
- package/dist/rollupPluginHTML.d.ts +13 -0
- package/dist/rollupPluginHTML.d.ts.map +1 -0
- package/dist/rollupPluginHTML.js +183 -0
- package/dist/rollupPluginHTML.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +10 -0
- package/dist/utils.js.map +1 -0
- package/index.mjs +6 -0
- package/package.json +55 -0
- package/src/RollupPluginHTMLOptions.ts +92 -0
- package/src/assets/utils.ts +145 -0
- package/src/index.ts +14 -0
- package/src/input/InputData.ts +16 -0
- package/src/input/addRollupInput.ts +55 -0
- package/src/input/extract/extractAssets.ts +53 -0
- package/src/input/extract/extractModules.ts +78 -0
- package/src/input/extract/extractModulesAndAssets.ts +34 -0
- package/src/input/getInputData.ts +120 -0
- package/src/input/normalizeInputOptions.ts +47 -0
- package/src/output/createHTMLOutput.ts +87 -0
- package/src/output/emitAssets.ts +80 -0
- package/src/output/getEntrypointBundles.ts +88 -0
- package/src/output/getOutputHTML.ts +115 -0
- package/src/output/hashInlineScripts.ts +153 -0
- package/src/output/injectAbsoluteBaseUrl.ts +41 -0
- package/src/output/injectBundles.ts +44 -0
- package/src/output/injectServiceWorkerRegistration.ts +48 -0
- package/src/output/injectedUpdatedAssetPaths.ts +89 -0
- package/src/output/utils.ts +5 -0
- package/src/rollupPluginHTML.ts +225 -0
- 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
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
|
+
}
|