Compare commits
18 Commits
develop
...
feature/re
| Author | SHA1 | Date | |
|---|---|---|---|
| 3b540d0c48 | |||
| 1c55b894c9 | |||
| 3e46055845 | |||
| e96c2248ee | |||
| 2adfbee74b | |||
| 71a377417d | |||
| afd4a3c9ae | |||
| 980d33c48e | |||
| 5c1e528304 | |||
| 5d2a45ef81 | |||
| 6e50208557 | |||
| 48dcdefee1 | |||
| ba09aaf915 | |||
| 9bf026f0c3 | |||
| a784abc1b0 | |||
| 52c104f781 | |||
| b18ac5c361 | |||
| ba07649981 |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,2 +1,28 @@
|
|||||||
|
|
||||||
|
# 0.0.2
|
||||||
|
|
||||||
|
Private release update. Added experimental support for:
|
||||||
|
- multiple-entrypoints (i.e index.html and admin/index.html)
|
||||||
|
- Inlined scripts (i.e <script type="module">...</script>)
|
||||||
|
|
||||||
|
|
||||||
# 0.0.1
|
# 0.0.1
|
||||||
Initial private release
|
Initial private release
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Open issues / Short-term ToDo's:
|
||||||
|
|
||||||
|
- Implement importing style (#1 linking to a pcss, #2 inlined style)
|
||||||
|
- Importing html as a JSModule
|
||||||
|
- Testing on a windows machine and fix whatever issues with paths that come out of it
|
||||||
|
- Code clean-up / Watch-mode support
|
||||||
|
- Properly use 'meta' property, and supporting caching
|
||||||
|
- Supporting 'assets' directly (LoadType) using emitFile({type:'asset',...}). Removes the need for @rollup/plugin-url in small projects (altough it is still the preferred way of including assets)
|
||||||
|
- Getting rid of the module evaluation step if possible
|
||||||
|
- Clean up our API, keeping in mind the configurability desired:
|
||||||
|
- resolving language for inline script/style
|
||||||
|
- excluding non-relative imports (ie unpkg stuff etc)
|
||||||
|
- customizing how to import certain things (LoadType)
|
||||||
|
- support for typescript (might not need extra work, but it should be integrated in tests)
|
||||||
|
- cjs & iifi supported in tests
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -11,9 +11,12 @@
|
|||||||
|
|
||||||
# rollup-plugin-html-entry2
|
# rollup-plugin-html-entry2
|
||||||
| :warning: WARNING |
|
| :warning: WARNING |
|
||||||
|:-------------------------------------------------------------------|
|
|:----------------------------------------------------------------------------------------------------------------------|
|
||||||
| **Experimental-stage** plugin. Expect bugs and missing features... |
|
| **Experimental-stage** plugin. Expect bugs and missing features... |
|
||||||
|
| :warning: WARNING |
|
||||||
|
| :------------------------------------------------------------------- |
|
||||||
|
| **Renaming** Name might change in the future. Consider rollup-plugin-html-bundler |
|
||||||
|
| (because we're basically transforming rollup into a tool for bundling html, might not even contain any JS in the end) |
|
||||||
|
|
||||||
A(nother) rollup plugin that tries to teach Rollup to start from an HTML entry, and the use of (multiple) HTML files in general.
|
A(nother) rollup plugin that tries to teach Rollup to start from an HTML entry, and the use of (multiple) HTML files in general.
|
||||||
The goal is to include assets referenced by the HTML file into the build-process as to copy/inline where appropriate and
|
The goal is to include assets referenced by the HTML file into the build-process as to copy/inline where appropriate and
|
||||||
@@ -64,7 +67,7 @@ Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#comma
|
|||||||
|
|
||||||
### `template`
|
### `template`
|
||||||
|
|
||||||
Type: `Function`<br>
|
Type: `Function`\
|
||||||
Default: `undefined`\
|
Default: `undefined`\
|
||||||
Returns: `String`
|
Returns: `String`
|
||||||
|
|
||||||
@@ -94,27 +97,28 @@ async function build() {
|
|||||||
By default, this plugin supports the `esm` (`es`). Any other format is currently untested as this plugin is in an early state, see [#status](#status)
|
By default, this plugin supports the `esm` (`es`). Any other format is currently untested as this plugin is in an early state, see [#status](#status)
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
This plugin is in an early state. As such not everything that is supported yet, and the options may change.
|
|
||||||
|
|
||||||
### (Rudimentarily) supported
|
### (Rudimentarily) supported
|
||||||
- Importing JS via `<script src="..." type="module">` tags
|
- Importing JS via `<script src="..." type="module">` tags
|
||||||
- Importing assets using @rollup/plugin-url (which could use an update TBH)
|
- Importing assets using @rollup/plugin-url (which could use an update TBH)
|
||||||
- Compatibility with other plugins such as @rollup/plugin-node-resolve, @rollup/plugin-babel, @rollup/plugin-commonjs, @rollup/plugin-terser and rollup-plugin-livereload
|
- Compatibility with other plugins such as @rollup/plugin-node-resolve, @rollup/plugin-babel, @rollup/plugin-commonjs, @rollup/plugin-terser and rollup-plugin-livereload
|
||||||
|
- Inline scripts (i.e `<script>...</script>`)
|
||||||
|
|
||||||
|
|
||||||
### Not (yet) supported
|
### Not (yet/properly) supported
|
||||||
- Inline scripts (i.e `<script>...</script>`)
|
- Sourcemaps (inlined script) (dev-note: we're already including magic-string for this, but do not use it yet, neeeds refactoring)
|
||||||
- Plugins importing CSS files
|
- Plugins importing CSS files
|
||||||
- CommonJS (cjs) and IIFI output formats. (Is UMD actually ever used?)
|
- CommonJS (cjs) and IIFI output formats. (Is UMD actually ever used?)
|
||||||
- Overriding which tags to ignore/include
|
- Overriding which DOM-nodes and resulting URLS to ignore/include (in a clean way)
|
||||||
- Other (various) plugins such as those for HMR etc
|
- Other (various) plugins such as typescript, or those for HMR etc
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
# Contibuting
|
# Contibuting
|
||||||
|
|
||||||
You can be helpful by testing, proving helpful feedback, expanding the documentation, responding to issues/questions being reported, resolving the many ToDo`s in the code, implementating features...\
|
You can be helpful by testing, proving helpful feedback, expanding the documentation, responding to issues/questions being reported, resolving the many ToDo`s in the code, implementating features...\
|
||||||
[Get in touch](mailto:rollup-plugin-html-entry2@cerxes.net) or just dive into [the code](https://git.cerxes.net/rollup-apps/plugin-html) or [issues](https://git.cerxes.net/rollup-apps/plugin-html/issues)
|
[Get in touch](mailto:rollup-plugin-html-entry2@cerxes.net) or just dive into [the code](https://git.cerxes.net/rollup-apps/plugin-html) or [issues](https://git.cerxes.net/rollup-apps/plugin-html/issues).
|
||||||
|
|
||||||
|
See also the ToDo-list at the end of the [changelog](./CHANGELOG.md)
|
||||||
|
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
## git.cerxes.net
|
## git.cerxes.net
|
||||||
|
|||||||
65
package.json
65
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rollup-plugin-html-entry2",
|
"name": "rollup-plugin-html-entry2",
|
||||||
"version": "0.0.1",
|
"version": "0.0.6",
|
||||||
"description": "Teaches rollup how to deal with HTML, allows to use HTML-files as entry-points.",
|
"description": "Teaches rollup how to deal with HTML, allows to use HTML-files as entry-points.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"bugs": "https://git.cerxes.net/rollup-apps/plugin-html/issues",
|
"bugs": "https://git.cerxes.net/rollup-apps/plugin-html/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"main": "dist/es/index.js",
|
"main": "dist/es/index.js",
|
||||||
"module": "./dist/es/index.js",
|
"module": "./dist/es/index.js",
|
||||||
@@ -30,8 +30,8 @@
|
|||||||
"ci:coverage": "nyc pnpm test && nyc report --reporter=text-lcov > coverage.lcov",
|
"ci:coverage": "nyc pnpm test && nyc report --reporter=text-lcov > coverage.lcov",
|
||||||
"ci:lint": "pnpm build && pnpm lint-staged",
|
"ci:lint": "pnpm build && pnpm lint-staged",
|
||||||
"ci:test": "pnpm test -- --verbose",
|
"ci:test": "pnpm test -- --verbose",
|
||||||
"test": "ava",
|
"test": "NODE_OPTIONS='--import tsx' ava",
|
||||||
"save-test": "ava --update-snapshots"
|
"save-test": "NODE_OPTIONS='--import tsx' ava --update-snapshots"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@@ -55,32 +55,44 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rollup/pluginutils": "^5.0.1",
|
"@rollup/pluginutils": "^5.0.5",
|
||||||
|
"magic-string": "^0.30.5",
|
||||||
"parse5": "^7.1.2"
|
"parse5": "^7.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.15.11",
|
"@babel/core": "^7.23.3",
|
||||||
"@rollup/plugin-typescript": "^11.1.0",
|
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||||
"postcss": "^8.4.22",
|
"@babel/preset-typescript": "^7.23.3",
|
||||||
"rollup": "^3.20.3",
|
"@babel/preset-env": "^7.23.6",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"@babel/preset-react": "^7.23.3",
|
||||||
"typescript": "^5.0.4",
|
"@rollup/plugin-babel": "^6.0.4",
|
||||||
"del-cli": "^5.0.0",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"tslib": "^2.5.0",
|
"@rollup/plugin-typescript": "^11.1.5",
|
||||||
"ava": "^5.2.0",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"ts-node": "^10.9.1",
|
"@rollup/plugin-url": "^8.0.2",
|
||||||
"@babel/core": "^7.21.4",
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
"@rollup/plugin-babel": "^6.0.3",
|
"@types/node": "^18.18.12",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
"@types/react": "^18.2.0",
|
||||||
"rollup-plugin-delete": "^2.0.0",
|
"@types/react-dom": "^18.2.0",
|
||||||
"@babel/preset-typescript": "^7.21.4",
|
"ava": "^5.3.1",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"del-cli": "^5.1.0",
|
||||||
|
"handlebars": "^4.7.8",
|
||||||
|
"lint-staged": "^13.3.0",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"lint-staged": "^13.2.1",
|
"postcss": "^8.4.31",
|
||||||
"handlebars": "^4.7.7",
|
"rollup": "^3.29.4",
|
||||||
"@rollup/plugin-url": "^8.0.1",
|
"rollup-plugin-delete": "^2.0.0",
|
||||||
"chalk": "^5.2.0",
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
"rollup-plugin-livereload": "^2.0.5"
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
|
"tslib": "^2.6.2",
|
||||||
|
"tsx": "^4.4.0",
|
||||||
|
"typescript": "^5.3.2",
|
||||||
|
"puppeteer": "^21.5.2",
|
||||||
|
"mime": "^4.0.0",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"ava": {
|
"ava": {
|
||||||
@@ -97,7 +109,6 @@
|
|||||||
"js": true
|
"js": true
|
||||||
},
|
},
|
||||||
"nodeArguments": [
|
"nodeArguments": [
|
||||||
"--loader=ts-node/esm",
|
|
||||||
"--experimental-vm-modules"
|
"--experimental-vm-modules"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
3468
pnpm-lock.yaml
generated
3468
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
|||||||
This method provides the ability to reference external css/js files for the generated html, and supports adjusting the file loading sequence.
|
|
||||||
|
|
||||||
when using it:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import html from '@rollup/plugin-html';
|
|
||||||
import templateExternalFiles from '@rollup/plugin-html/recipes/external-files';
|
|
||||||
import postcss from 'rollup-plugin-postcss';
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
input: ['demo/demo.ts'],
|
|
||||||
output: [{ file: 'dist/demo.js' }],
|
|
||||||
plugins: [
|
|
||||||
postcss({
|
|
||||||
extract: 'demo.css',
|
|
||||||
minimize: false,
|
|
||||||
use: ['sass'],
|
|
||||||
extensions: ['.scss', '.css']
|
|
||||||
}),
|
|
||||||
html({
|
|
||||||
title: 'sdk demo page',
|
|
||||||
publicPath: './',
|
|
||||||
fileName: 'demo.html',
|
|
||||||
attributes: { html: { lang: 'zh-cn' } },
|
|
||||||
template: templateExternalFiles([
|
|
||||||
{ type: 'js', file: 'example1.js', pos: 'before' },
|
|
||||||
{ type: 'js', file: 'example2.js', pos: 'before' },
|
|
||||||
{ type: 'js', file: 'example3.js' },
|
|
||||||
{ type: 'js', file: 'example4.js', pos: 'before' },
|
|
||||||
{ type: 'css', file: 'example1.css', pos: 'before' },
|
|
||||||
{ type: 'css', file: 'example2.css', pos: 'before' },
|
|
||||||
{ type: 'css', file: 'example3.css' },
|
|
||||||
{ type: 'css', file: 'example4.css', pos: 'before' }
|
|
||||||
])
|
|
||||||
})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
The content of the generated html file:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-cn">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>sdk demo page</title>
|
|
||||||
<link href="./example1.css" rel="stylesheet" />
|
|
||||||
<link href="./example2.css" rel="stylesheet" />
|
|
||||||
<link href="./example4.css" rel="stylesheet" />
|
|
||||||
<link href="./demo.css" rel="stylesheet" />
|
|
||||||
<link href="./example3.css" rel="stylesheet" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script src="./example1.js"></script>
|
|
||||||
<script src="./example2.js"></script>
|
|
||||||
<script src="./example4.js"></script>
|
|
||||||
<script src="./demo.js"></script>
|
|
||||||
<script src="./example3.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
/**
|
|
||||||
* Provides the ability to reference external css/js files for the generated html
|
|
||||||
* Method source once issues: https://github.com/rollup/plugins/issues/755
|
|
||||||
* @param {Array} externals List of external files.
|
|
||||||
* The format is: [{ type: 'js', file: '//xxxx1.js', pos: 'before' }, { type: 'css', file: '//xxxx1.css' }]
|
|
||||||
*
|
|
||||||
* @return {Function} The templae method required by plugin-html
|
|
||||||
*/
|
|
||||||
export default function htmlTemplate(externals) {
|
|
||||||
return ({ attributes, files, meta, publicPath, title }) => {
|
|
||||||
let scripts = [...(files.js || [])];
|
|
||||||
let links = [...(files.css || [])];
|
|
||||||
|
|
||||||
// externals = [{ type: 'js', file: '//xxxx1.js', pos: 'before' }, { type: 'css', file: '//xxxx1.css' }]
|
|
||||||
if (Array.isArray(externals)) {
|
|
||||||
const beforeLinks = [];
|
|
||||||
const beforeScripts = [];
|
|
||||||
externals.forEach((node) => {
|
|
||||||
let fileList;
|
|
||||||
const isCssFile = node.type === 'css';
|
|
||||||
if (node.pos === 'before') {
|
|
||||||
fileList = isCssFile ? beforeLinks : beforeScripts;
|
|
||||||
} else {
|
|
||||||
fileList = isCssFile ? links : scripts;
|
|
||||||
}
|
|
||||||
fileList.push({ fileName: node.file });
|
|
||||||
});
|
|
||||||
scripts = beforeScripts.concat(scripts);
|
|
||||||
links = beforeLinks.concat(links);
|
|
||||||
}
|
|
||||||
|
|
||||||
scripts = scripts
|
|
||||||
.map(({ fileName }) => {
|
|
||||||
const attrs = makeHtmlAttributes(attributes.script);
|
|
||||||
return `<script src="${publicPath}${fileName}"${attrs}></script>`;
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
links = links
|
|
||||||
.map(({ fileName }) => {
|
|
||||||
const attrs = makeHtmlAttributes(attributes.link);
|
|
||||||
return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
const metas = meta
|
|
||||||
.map((input) => {
|
|
||||||
const attrs = makeHtmlAttributes(input);
|
|
||||||
return `<meta${attrs}>`;
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
return `
|
|
||||||
<!doctype html>
|
|
||||||
<html${makeHtmlAttributes(attributes.html)}>
|
|
||||||
<head>
|
|
||||||
${metas}
|
|
||||||
<title>${title}</title>
|
|
||||||
${links}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
${scripts}
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeHtmlAttributes(attributes) {
|
|
||||||
if (!attributes) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const keys = Object.keys(attributes);
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
return keys.reduce((result, key) => (result += ` ${key}="${attributes[key]}"`), '');
|
|
||||||
}
|
|
||||||
199
src/index.ts
199
src/index.ts
@@ -1,4 +1,4 @@
|
|||||||
import { extname } from "node:path";
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
Plugin,
|
Plugin,
|
||||||
@@ -19,17 +19,24 @@ import type {
|
|||||||
LoadReference, BodyReference, AttributeReference, LoadFunction
|
LoadReference, BodyReference, AttributeReference, LoadFunction
|
||||||
} from '../types/index.d.ts';
|
} from '../types/index.d.ts';
|
||||||
|
|
||||||
|
// createFilter function is a utility that constructs a filter function from include/exclude patterns.
|
||||||
import {createFilter} from '@rollup/pluginutils';
|
import {createFilter} from '@rollup/pluginutils';
|
||||||
|
// parse5 package is used for parsing HTML.
|
||||||
import {parse as parseHtml, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5";
|
import {parse as parseHtml, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5";
|
||||||
import {readFile} from "node:fs/promises"
|
// magic-string to transform code and keeping a sourcemap aligned
|
||||||
|
import MagicString from "magic-string";
|
||||||
|
|
||||||
|
|
||||||
|
// nodejs imports (io, path)
|
||||||
|
import path, { extname, dirname } from "node:path";
|
||||||
|
import {readFile} from "node:fs/promises"
|
||||||
|
import posix from "node:path/posix";
|
||||||
|
import crypto from "node:crypto";
|
||||||
|
|
||||||
|
// utilities
|
||||||
import {makeLoader, makeInlineId} from "./loader.js";
|
import {makeLoader, makeInlineId} from "./loader.js";
|
||||||
import {HtmlImport, HtmlModule} from "./html-module.js";
|
import {HtmlImport, HtmlModule} from "./html-module.js";
|
||||||
|
|
||||||
import {dirname} from "node:path";
|
|
||||||
import posix from "node:path/posix";
|
|
||||||
|
|
||||||
import crypto from "node:crypto";
|
|
||||||
|
|
||||||
const defaults: RollupHtmlOptions = {
|
const defaults: RollupHtmlOptions = {
|
||||||
transform: (source: string)=>source,// NO-OP
|
transform: (source: string)=>source,// NO-OP
|
||||||
@@ -44,10 +51,17 @@ const defaults: RollupHtmlOptions = {
|
|||||||
const modulePrefix = `// <html-module>`;
|
const modulePrefix = `// <html-module>`;
|
||||||
const moduleSuffix = `// </html-module>`;
|
const moduleSuffix = `// </html-module>`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a Rollup plugin that transforms HTML files.
|
||||||
|
*
|
||||||
|
* @param {RollupHtmlOptions} opts - The options for the plugin.
|
||||||
|
* @returns {Plugin} - The Rollup plugin.
|
||||||
|
*/
|
||||||
export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
||||||
const {
|
const {
|
||||||
publicPath,
|
publicPath,
|
||||||
transform,
|
transform,
|
||||||
|
rewriteUrl,
|
||||||
load,
|
load,
|
||||||
htmlFileNames,
|
htmlFileNames,
|
||||||
resolve,
|
resolve,
|
||||||
@@ -61,12 +75,42 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
if(publicPath){ throw new Error("TODO, do something with the public path or throw it out of the options. this is just to stop typescript complaining")}
|
if(publicPath){ throw new Error("TODO, do something with the public path or throw it out of the options. this is just to stop typescript complaining")}
|
||||||
|
|
||||||
let filter = createFilter(include, exclude, {});
|
let filter = createFilter(include, exclude, {});
|
||||||
let htmlModules = new Map<string, HtmlModule>();// todo clean this per new build?
|
|
||||||
let virtualSources = new Map<string, string>();
|
|
||||||
|
|
||||||
const pluginName = 'html2';
|
// TODO, we need to clear all these properly at sme point to avoid odd bugs in watch mode
|
||||||
|
let virtualSources = new Map<string, string>();
|
||||||
|
let addedEntries = new Map<string, string>();
|
||||||
|
let entryNames = new Map<string,string>();
|
||||||
|
|
||||||
|
const pluginName = 'html2'; // TODO: Need a better name, and work to strip everything noted below except the short summary
|
||||||
|
/**
|
||||||
|
* Short summary:
|
||||||
|
* Intercepts the loading of the html files and parses it with parse5.
|
||||||
|
* The parsed result is iterated to check for external references that need to be including in the rollup build (via for example @rollup/plugin-url).
|
||||||
|
* A .js version of the html file is returned to rollup, optionally including a few imports left for rollup to resolve
|
||||||
|
* When the result is generated the rollup result for the html file and any of its inlined assets are stripped from the output.
|
||||||
|
* and replaced with a html file.
|
||||||
|
*
|
||||||
|
* Caveats:
|
||||||
|
* - to get the resulting html content file we're evaluating the resulting JS module and take its default export
|
||||||
|
* This evaluation step is done in the host NodeJS context, which might screw up things that expect a browser context
|
||||||
|
* [warn] other plugins such as CJS transformer and hot-reload can severely screw this up.
|
||||||
|
* - to fix the naming of resulting html files, and behave properly when files are entryPoints or not... we're fighting rollup alot
|
||||||
|
* issues are likely...
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Rework by testing a stripped down version with JS imports?
|
||||||
|
* - the logic in load should be moved to a transform, properly use rollups ability to specify the plugin should run 'pre' other hooks and see if that allows us to intercept before a commonjs or some other tool horribly transpiles our code
|
||||||
|
* - we might need to know which output is being used to properly extract the html back from the result? (in case of not being included in a JS file)
|
||||||
|
*/
|
||||||
return {
|
return {
|
||||||
name: pluginName,// TODO: Need a better name, original plugin was just named `html` and might still make sense to use in conjunction with this one
|
name: pluginName,
|
||||||
|
|
||||||
|
// Track html entrypoints
|
||||||
|
buildStart(options){
|
||||||
|
entryNames = new Map(Object.entries(typeof(options.input)==='object'?options.input:{[options.input]:[options.input]})
|
||||||
|
.map(([k,v])=>[v,k])
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
resolveId: {
|
resolveId: {
|
||||||
async handler(specifier: string,
|
async handler(specifier: string,
|
||||||
@@ -82,25 +126,21 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(resolved){
|
if(resolved){
|
||||||
const moduleId = resolved.id;
|
|
||||||
const moduleExt = extname(resolved.id);
|
const moduleExt = extname(resolved.id);
|
||||||
const moduleName = specifier.replace(new RegExp(`${moduleExt}\$`),''); // strip extension of the name if any
|
const moduleName = specifier.replace(new RegExp(`${moduleExt}\$`),''); // strip extension of the name if any
|
||||||
const htmlModule : HtmlModule = htmlModules.get(moduleId) ?? {
|
|
||||||
|
return {
|
||||||
|
...resolved,
|
||||||
|
meta: {
|
||||||
|
...resolved.meta,
|
||||||
|
[pluginName]: {
|
||||||
|
specifier: specifier,
|
||||||
id: resolved.id,
|
id: resolved.id,
|
||||||
name: moduleName,
|
name: moduleName,
|
||||||
imports: [],
|
imports: [],
|
||||||
assetId: null,
|
assetId: null,
|
||||||
importers: new Set(),
|
importers: new Set(),
|
||||||
};
|
}
|
||||||
htmlModule.importers.add(importer);
|
|
||||||
|
|
||||||
htmlModules.set(htmlModule.id, htmlModule);
|
|
||||||
// TODO: trigger special handling when imported from a JS file (in which case we want might want to export a module returning the HTML, instead of HTML directly)
|
|
||||||
return {
|
|
||||||
...resolved,
|
|
||||||
meta: {
|
|
||||||
...resolved.meta,
|
|
||||||
[pluginName]: {name: specifier}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,20 +148,43 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
},
|
},
|
||||||
load: {
|
load: {
|
||||||
async handler(id: string) {
|
async handler(id: string) {
|
||||||
if(virtualSources.has(id)) return virtualSources.get(id);
|
if (virtualSources.has(id)) return virtualSources.get(id);
|
||||||
if(!filter(id)) return;
|
// if (!filter(id)) return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transform: {
|
||||||
|
order: 'pre',
|
||||||
|
async handler(...args){
|
||||||
|
const [code, id] = args;
|
||||||
|
if (!filter(id)) return;
|
||||||
|
|
||||||
// Load
|
// parse
|
||||||
const htmlModule = htmlModules.get(id);
|
const moduleInfo = this.getModuleInfo(id);
|
||||||
if(htmlModule) {
|
const moduleMeta = moduleInfo!.meta ?? {};
|
||||||
const contents = await readFile(id, {encoding: "utf-8"});
|
let htmlModule = moduleMeta[pluginName];
|
||||||
|
if(!htmlModule){
|
||||||
|
const moduleExt = extname(id);
|
||||||
|
const moduleName = id.replace(new RegExp(`${moduleExt}\$`),''); // strip extension of the name if any
|
||||||
|
htmlModule = moduleMeta[pluginName] = {
|
||||||
|
id: id,
|
||||||
|
name: moduleName,
|
||||||
|
imports: [],
|
||||||
|
assetId: null,
|
||||||
|
importers: new Set(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const contents = code;
|
||||||
|
|
||||||
const htmlSrc = transform ? await transform(contents, {
|
const htmlSrc = transform ? await transform(contents, {
|
||||||
id,
|
id,
|
||||||
}) : contents;
|
}) : contents;
|
||||||
|
|
||||||
// Parse document and store it (TODO: check for watch mode, we should check if it needs reparsing or not)
|
// Parse document and store it
|
||||||
const document = htmlModule.document = htmlModule.document ?? parseHtml(htmlSrc);
|
const document = htmlModule.document = parseHtml(htmlSrc);
|
||||||
|
|
||||||
|
// TODO working on this: to preserve sourcemaps as much as possible we're starting the magic string on the raw html source
|
||||||
|
// question is if we need to though. sourcemaps only make sense for inlined bits of code
|
||||||
|
//let htmlJS = new MagicString(htmlSrc);// This is where we want to go!
|
||||||
|
|
||||||
// Figure out which references to load from this HTML by iterating all nodes (looking for src or href attributes)
|
// Figure out which references to load from this HTML by iterating all nodes (looking for src or href attributes)
|
||||||
let htmlImports: HtmlImport[] = htmlModule.imports = [];
|
let htmlImports: HtmlImport[] = htmlModule.imports = [];
|
||||||
@@ -142,7 +205,6 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
if(source){
|
if(source){
|
||||||
virtualSources.set(sourceId, source);
|
virtualSources.set(sourceId, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resolved = await this.resolve(sourceId, id, {
|
const resolved = await this.resolve(sourceId, id, {
|
||||||
isEntry: type==='entryChunk',
|
isEntry: type==='entryChunk',
|
||||||
});
|
});
|
||||||
@@ -151,7 +213,17 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selfInfo = this.getModuleInfo(id);
|
const selfInfo = this.getModuleInfo(id);
|
||||||
const importName = (source && selfInfo?.meta[pluginName].name) ? makeInlineId(selfInfo?.meta[pluginName].name, node, extname(sourceId)) : undefined;
|
|
||||||
|
let entryName: string|undefined = undefined;
|
||||||
|
const parentName = entryNames.get(id)??selfInfo?.meta[pluginName].name;
|
||||||
|
if(type==='entryChunk'){
|
||||||
|
entryName= posix.join(posix.dirname(parentName),sourceId);
|
||||||
|
entryName = entryName.slice(0,-(posix.extname(entryName).length)); // Cut off the extension (TODO, is this wise?)
|
||||||
|
}
|
||||||
|
|
||||||
|
const importName = (source && selfInfo?.meta[pluginName].name)
|
||||||
|
? makeInlineId(parentName, node, extname(sourceId))
|
||||||
|
: entryName;
|
||||||
|
|
||||||
const htmlImport: HtmlImport = {
|
const htmlImport: HtmlImport = {
|
||||||
id: <string>sourceId,
|
id: <string>sourceId,
|
||||||
@@ -170,6 +242,9 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
placeholder: `html-import-${crypto.randomBytes(32).toString('base64')}`,
|
placeholder: `html-import-${crypto.randomBytes(32).toString('base64')}`,
|
||||||
index: htmlImports.length,
|
index: htmlImports.length,
|
||||||
}
|
}
|
||||||
|
// if(entryName){
|
||||||
|
// addedEntries.set(resolved.id, entryName);// (we could do this using meta?)
|
||||||
|
// }
|
||||||
htmlImports.push(htmlImport);
|
htmlImports.push(htmlImport);
|
||||||
return htmlImport.placeholder;
|
return htmlImport.placeholder;
|
||||||
}
|
}
|
||||||
@@ -190,45 +265,61 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
} while (nodeQueue.length > 0);
|
} while (nodeQueue.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let html = serializeHtml(htmlModule.document).replaceAll(/`/g,'\\\`').replaceAll(/\$\{/g,'\\${');
|
// Beware leak of AST (we're starting MagicString on a parsed and modified version of the HTML file, sourcemappings in the HTML file will be off. (can't add a sourcemap for a html file anyway, unless it is outputted as JS module)
|
||||||
|
let htmlJS = new MagicString(serializeHtml(htmlModule.document));
|
||||||
|
htmlJS.replaceAll(/`/g,'\\\`').replaceAll(/\$\{/g,'\\${');
|
||||||
|
|
||||||
const moduleImports = [];
|
const moduleImports = [];
|
||||||
for(const htmlImport of htmlImports){
|
for(const htmlImport of htmlImports){
|
||||||
if(htmlImport.type === 'default') {
|
if(htmlImport.type === 'default') {
|
||||||
const assetId: string = `asset${moduleImports.length}`;
|
const assetId: string = `asset${moduleImports.length}`;
|
||||||
moduleImports.push(`import ${assetId} from "${htmlImport.id}";`);// TODO: This is just the easy & safe solution. Would prefer to have recognizable names, and reeuse when something is the exact same resource..
|
moduleImports.push(`import ${assetId} from "${htmlImport.id}";`);// TODO: This is just the easy & safe solution. Would prefer to have recognizable names, and reeuse when something is the exact same resource..
|
||||||
html = html.replace(htmlImport.placeholder, `\${${assetId}}`);// TODO: Should we be worried about windows absolute URLs here?
|
htmlJS = htmlJS.replace(htmlImport.placeholder, `\${${assetId}}`);// TODO: Should we be worried about windows absolute URLs here?
|
||||||
// }else if(htmlImport.type === 'entryChunk' && htmlImport.referenceId){
|
|
||||||
// html = html.replace(htmlImport.placeholder, `\${import.meta.ROLLUP_FILE_URL_${htmlImport.referenceId}\}`);
|
|
||||||
}else{
|
}else{
|
||||||
// TODO: this will probably not do for complicated cases ( presumably no other method then emitting the chunk as file, loading its result but excluding it from the output bundle)
|
// TODO: this will probably not do for complicated cases ( presumably no other method then emitting the chunk as file, loading its result but excluding it from the output bundle)
|
||||||
// html = html.replace(htmlImport.placeholder, htmlImport.loaded?.code||htmlImport.source||'');
|
// html = html.replace(htmlImport.placeholder, htmlImport.loaded?.code||htmlImport.source||'');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO when importing html from .js this will not do. (
|
// Import all dependencies and wrap the HTML in a `...`, assign to a var and export (escaping any ` characters in the HTML)
|
||||||
const htmlJSModule = [
|
htmlJS.prepend([
|
||||||
...moduleImports,
|
...moduleImports,
|
||||||
``,
|
`export const html = \``
|
||||||
`export const html = \`${html}\`;`,
|
].join('\n')).append([
|
||||||
|
`\`;`,
|
||||||
`export default html;`,
|
`export default html;`,
|
||||||
].join('\n');
|
].join('\n'));
|
||||||
|
|
||||||
|
const map = htmlJS.generateMap({
|
||||||
|
source: id,
|
||||||
|
file: `${id}.map`,
|
||||||
|
includeContent: true,
|
||||||
|
hires: 'boundary'
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: htmlJSModule,
|
code: htmlJS.toString(),
|
||||||
|
map: map.toString(),
|
||||||
|
meta: moduleMeta,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
outputOptions(options){
|
outputOptions(options){
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
entryFileNames: (chunkInfo)=>{
|
entryFileNames: (chunkInfo)=>{
|
||||||
const htmlModule = chunkInfo.facadeModuleId ? htmlModules.get(chunkInfo.facadeModuleId!) : null;
|
const moduleInfo = chunkInfo.facadeModuleId? this.getModuleInfo(chunkInfo.facadeModuleId) : null;
|
||||||
|
const htmlModule = moduleInfo?.meta?.[pluginName];
|
||||||
|
// const htmlModule = chunkInfo.facadeModuleId ? htmlModules.get(chunkInfo.facadeModuleId!) : null;
|
||||||
|
const addedEntry = chunkInfo.facadeModuleId ? addedEntries.get(chunkInfo.facadeModuleId!) : null;
|
||||||
const defaultOption = options.entryFileNames ?? "[name]-[hash].js";// This default is copied from the docs. TODO: don't like overwrite it this way, can we remove the need for this or fetch the true default?
|
const defaultOption = options.entryFileNames ?? "[name]-[hash].js";// This default is copied from the docs. TODO: don't like overwrite it this way, can we remove the need for this or fetch the true default?
|
||||||
if(htmlModule){
|
if(htmlModule){
|
||||||
let fileName = typeof (htmlFileNames) === 'string' ? htmlFileNames : (<(chunkInfo:PreRenderedChunk)=>string>htmlFileNames)(chunkInfo);
|
let fileName = typeof (htmlFileNames) === 'string' ? htmlFileNames : (<(chunkInfo:PreRenderedChunk)=>string>htmlFileNames)(chunkInfo);
|
||||||
if(fileName) {
|
if(fileName) {
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
}else if(addedEntry){
|
||||||
|
return addedEntry;
|
||||||
}
|
}
|
||||||
return typeof (defaultOption) === 'string' ? defaultOption : (<(chunkInfo:PreRenderedChunk)=>string>defaultOption)(chunkInfo);
|
return typeof (defaultOption) === 'string' ? defaultOption : (<(chunkInfo:PreRenderedChunk)=>string>defaultOption)(chunkInfo);
|
||||||
},
|
},
|
||||||
@@ -236,7 +327,8 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
resolveFileUrl(options){
|
resolveFileUrl(options){
|
||||||
const htmlModule = htmlModules.get(options.moduleId);
|
const moduleInfo = this.getModuleInfo(options.moduleId);
|
||||||
|
const htmlModule = moduleInfo?.meta?.[pluginName];
|
||||||
if(htmlModule){
|
if(htmlModule){
|
||||||
// Simply use the relative path in our HTML-fileURLs instead of the default `new URL('${fileName}', document.baseURI).href`)
|
// Simply use the relative path in our HTML-fileURLs instead of the default `new URL('${fileName}', document.baseURI).href`)
|
||||||
return `"${options.relativePath}"`;
|
return `"${options.relativePath}"`;
|
||||||
@@ -247,7 +339,9 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
order:'post',
|
order:'post',
|
||||||
handler(chunk: RenderedChunk){
|
handler(chunk: RenderedChunk){
|
||||||
if(chunk.facadeModuleId) {
|
if(chunk.facadeModuleId) {
|
||||||
const htmlModule = htmlModules.get(chunk.facadeModuleId);
|
const moduleInfo = chunk.facadeModuleId? this.getModuleInfo(chunk.facadeModuleId) : null;
|
||||||
|
const htmlModule = moduleInfo?.meta?.[pluginName];
|
||||||
|
// const htmlModule = htmlModules.get(chunk.facadeModuleId);
|
||||||
if (htmlModule) {
|
if (htmlModule) {
|
||||||
return modulePrefix; // Overwrite any added banner with our own
|
return modulePrefix; // Overwrite any added banner with our own
|
||||||
}
|
}
|
||||||
@@ -265,7 +359,11 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
const chunk = (<OutputChunk>bundle);
|
const chunk = (<OutputChunk>bundle);
|
||||||
if(chunk.facadeModuleId) {
|
if(chunk.facadeModuleId) {
|
||||||
facadeToChunk.set(chunk.facadeModuleId, chunk);
|
facadeToChunk.set(chunk.facadeModuleId, chunk);
|
||||||
const htmlModule = htmlModules.get(chunk.facadeModuleId);
|
|
||||||
|
const moduleInfo = this.getModuleInfo(chunk.facadeModuleId);
|
||||||
|
const htmlModule = moduleInfo?.meta?.[pluginName];
|
||||||
|
// const htmlModule = htmlModules.get(chunk.facadeModuleId);
|
||||||
|
|
||||||
if(htmlModule){ htmlResults.set(bundleName, {chunk, htmlModule})}
|
if(htmlModule){ htmlResults.set(bundleName, {chunk, htmlModule})}
|
||||||
else if(virtualSources.has(chunk.facadeModuleId)){
|
else if(virtualSources.has(chunk.facadeModuleId)){
|
||||||
virtualBundles.add(bundleName);
|
virtualBundles.add(bundleName);
|
||||||
@@ -328,7 +426,12 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
htmlContents = htmlContents.replace(htmlImport.placeholder, importResult.code);
|
htmlContents = htmlContents.replace(htmlImport.placeholder, importResult.code);
|
||||||
}else if(htmlImport.type === 'entryChunk'){
|
}else if(htmlImport.type === 'entryChunk'){
|
||||||
const relPath = posix.relative(dirname(chunk.fileName), importResult.fileName);
|
const relPath = posix.relative(dirname(chunk.fileName), importResult.fileName);
|
||||||
htmlContents = htmlContents.replace(htmlImport.placeholder, relPath);
|
const rootPath = path.posix.join(dirname(chunk.fileName), relPath);
|
||||||
|
const rewritten = rewriteUrl? await Promise.resolve(rewriteUrl(relPath, {
|
||||||
|
from: chunk.fileName,
|
||||||
|
rootPath,
|
||||||
|
})): relPath;
|
||||||
|
htmlContents = htmlContents.replace(htmlImport.placeholder, rewritten);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,9 +46,17 @@ export function makeInlineId(sourceId: string, node: DefaultTreeAdapterMap['chil
|
|||||||
return [sourceId, [makeHtmlPath(node), 'js'].join('.')].join('.');
|
return [sourceId, [makeHtmlPath(node), 'js'].join('.')].join('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a loader function that maps node types and attributes to load operations.
|
||||||
|
*
|
||||||
|
* @param mappings - An array of NodeMapping objects specifying how to map and load different nodes.
|
||||||
|
* @returns A LoadNodeCallback function that can be used to load nodes based on the mappings.
|
||||||
|
*/
|
||||||
export function makeLoader(mappings: NodeMapping[] = defaultMapping){
|
export function makeLoader(mappings: NodeMapping[] = defaultMapping){
|
||||||
const fn : LoadNodeCallback = async function ({node, sourceId}, load){
|
const fn : LoadNodeCallback = async function ({node, sourceId}, load){
|
||||||
for(const mapping of mappings){
|
for(const mapping of mappings){
|
||||||
|
|
||||||
|
// Test the mapping for a match
|
||||||
if (mapping.tagName && mapping.tagName !== node.tagName) continue; // No match, skip
|
if (mapping.tagName && mapping.tagName !== node.tagName) continue; // No match, skip
|
||||||
if (mapping.match){
|
if (mapping.match){
|
||||||
if(typeof(mapping.match) === 'function'){
|
if(typeof(mapping.match) === 'function'){
|
||||||
@@ -67,7 +75,10 @@ export function makeLoader(mappings: NodeMapping[] = defaultMapping){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we've gotten this far its a valid mapping. (either inline or a src/href attribute)
|
||||||
if((<AttributeReference>mapping).attr){
|
if((<AttributeReference>mapping).attr){
|
||||||
|
// Mapped on attribute, resolve its src or href (or whatever was returned)
|
||||||
const attr = node.attrs.find(attr=>attr.name === (<AttributeReference>mapping).attr);
|
const attr = node.attrs.find(attr=>attr.name === (<AttributeReference>mapping).attr);
|
||||||
if(!attr) continue ;// No match, skip
|
if(!attr) continue ;// No match, skip
|
||||||
const placeholder = await load({
|
const placeholder = await load({
|
||||||
@@ -76,6 +87,7 @@ export function makeLoader(mappings: NodeMapping[] = defaultMapping){
|
|||||||
});
|
});
|
||||||
attr.value = placeholder;
|
attr.value = placeholder;
|
||||||
}else if((<BodyReference>mapping).body){
|
}else if((<BodyReference>mapping).body){
|
||||||
|
// Mapped as body, use the contents of the DOM element
|
||||||
const body = serializeHtml(node); // unlike what you' might expect, this doesn't serialize the <script>-tag itself, only its contents. Which is what we want.
|
const body = serializeHtml(node); // unlike what you' might expect, this doesn't serialize the <script>-tag itself, only its contents. Which is what we want.
|
||||||
if(!body) continue; // Empty body, skip
|
if(!body) continue; // Empty body, skip
|
||||||
const placeholder = await load({
|
const placeholder = await load({
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
code: undefined,
|
code: undefined,
|
||||||
fileName: 'script.html.body.script.js-e3b82208.js.map',
|
fileName: 'script.body.script.js-e3b82208.js.map',
|
||||||
map: undefined,
|
map: undefined,
|
||||||
source: '{"version":3,"file":"script.html.body.script.js-e3b82208.js","sources":["../batman.js","../script.html.body.script.js"],"sourcesContent":["export const b = ()=>\'batman\';\\nconsole.log(b());\\n","\\n import {b} from \\"./batman.js\\";\\n document.body.appendChild(\\n document.createTextNode(`Inline script including ${b()}`)\\n );\\n "],"names":[],"mappings":"AAAO,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;ACCJ,QAAQ,CAAC,IAAI,CAAC,WAAW;AACrC,gBAAgB,QAAQ,CAAC,cAAc,CAAC,CAAC,wBAAwB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,aAAa"}',
|
source: '{"version":3,"file":"script.body.script.js-e3b82208.js","sources":["../batman.js","../script.html.body.script.js"],"sourcesContent":["export const b = ()=>\'batman\';\\nconsole.log(b());\\n","\\n import {b} from \\"./batman.js\\";\\n document.body.appendChild(\\n document.createTextNode(`Inline script including ${b()}`)\\n );\\n "],"names":[],"mappings":"AAAO,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;ACCJ,QAAQ,CAAC,IAAI,CAAC,WAAW;AACrC,gBAAgB,QAAQ,CAAC,cAAc,CAAC,CAAC,wBAAwB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,aAAa"}',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: undefined,
|
code: undefined,
|
||||||
@@ -77,7 +77,7 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
document.body.appendChild(␊
|
document.body.appendChild(␊
|
||||||
document.createTextNode(\`Inline script including ${b()}\`)␊
|
document.createTextNode(\`Inline script including ${b()}\`)␊
|
||||||
);␊
|
);␊
|
||||||
//# sourceMappingURL=script.html.body.script.js-e3b82208.js.map␊
|
//# sourceMappingURL=script.body.script.js-e3b82208.js.map␊
|
||||||
</script>␊
|
</script>␊
|
||||||
␊
|
␊
|
||||||
␊
|
␊
|
||||||
|
|||||||
Binary file not shown.
@@ -2,7 +2,7 @@ import {join, dirname} from "node:path";
|
|||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
import { rollup } from "rollup";
|
import { rollup } from "rollup";
|
||||||
import {debugPrintOutput, getCode} from "../util/test.js";
|
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||||
|
|
||||||
import html from "../../src/index.ts";
|
import html from "../../src/index.ts";
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ test.serial('simple', async (t) => {
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const code = await getCode(bundle, output, true);
|
const code = await getCode(bundle, output);
|
||||||
debugPrintOutput('simple',code);
|
debugPrintOutput('simple',code);
|
||||||
t.snapshot(code);
|
t.snapshot(code);
|
||||||
});
|
});
|
||||||
@@ -38,7 +38,7 @@ test.serial('inline-script', async (t) => {
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const code = await getCode(bundle, output, true);
|
const code = await getCode(bundle, output);
|
||||||
debugPrintOutput('inline-script',code);
|
debugPrintOutput('inline-script',code);
|
||||||
t.snapshot(code);
|
t.snapshot(code);
|
||||||
});
|
});
|
||||||
|
|||||||
13
test/evaluated-web-bundle/fixtures/app.mjs
Normal file
13
test/evaluated-web-bundle/fixtures/app.mjs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export async function app({root}){
|
||||||
|
|
||||||
|
const states = ['started', 'tick', 'ended'];
|
||||||
|
|
||||||
|
for(let state of states){
|
||||||
|
const text = `App ${state}`;
|
||||||
|
console.log(`Test my sourcemap: ${text}`);
|
||||||
|
root.innerHTML = `<div style="align-self: center"><b>${text}</b></div>`;
|
||||||
|
await new Promise((resolve,reject)=>
|
||||||
|
setTimeout(()=>resolve(), 10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
test/evaluated-web-bundle/fixtures/assets/assistant.ttf
Normal file
BIN
test/evaluated-web-bundle/fixtures/assets/assistant.ttf
Normal file
Binary file not shown.
8
test/evaluated-web-bundle/fixtures/assets/logo-sq.svg
Normal file
8
test/evaluated-web-bundle/fixtures/assets/logo-sq.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<title>HTML5 Logo</title>
|
||||||
|
<path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/>
|
||||||
|
<path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/>
|
||||||
|
<path fill="#f16529" d="M256 480.5V131H404.3L376 447"/>
|
||||||
|
<path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/>
|
||||||
|
<path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 693 B |
18
test/evaluated-web-bundle/fixtures/index.hbs
Normal file
18
test/evaluated-web-bundle/fixtures/index.hbs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>
|
||||||
|
Test bundle!
|
||||||
|
</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" href="./assets/logo-sq.svg">
|
||||||
|
|
||||||
|
{{{ head }}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root">Here the app should load!</div>
|
||||||
|
<script src="./index.mjs" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
test/evaluated-web-bundle/fixtures/index.mjs
Normal file
27
test/evaluated-web-bundle/fixtures/index.mjs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Dynamically loads libraries and bootstraps the application
|
||||||
|
(async ()=>{
|
||||||
|
// Add a loader here if any
|
||||||
|
const root = document.getElementById('root')
|
||||||
|
if(root) root.innerHTML= `<div style="align-self: center">My app has loaded!!</div>`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load app
|
||||||
|
const [
|
||||||
|
appModule,
|
||||||
|
] = await Promise.all([
|
||||||
|
import("./app.mjs"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log("Bootstrapped, ready to go!");
|
||||||
|
|
||||||
|
// Wait for DOM to be ready
|
||||||
|
if(document.readyState === 'loading') {
|
||||||
|
await new Promise((resolve)=>document.addEventListener('DOMContentLoaded', resolve));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the app!
|
||||||
|
await appModule.app({root});
|
||||||
|
}catch(err){
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
})()
|
||||||
42
test/evaluated-web-bundle/snapshots/test.js.md
Normal file
42
test/evaluated-web-bundle/snapshots/test.js.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Snapshot report for `test/evaluated-web-bundle/test.js`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `test.js.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://avajs.dev).
|
||||||
|
|
||||||
|
## web-bundle
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
{
|
||||||
|
console: [
|
||||||
|
'[log] Bootstrapped, ready to go!',
|
||||||
|
'[log] Test my sourcemap: App started',
|
||||||
|
'[log] Test my sourcemap: App tick',
|
||||||
|
'[log] Test my sourcemap: App ended',
|
||||||
|
],
|
||||||
|
errors: [],
|
||||||
|
html: `<html lang="en"><head>␊
|
||||||
|
<meta charset="UTF-8">␊
|
||||||
|
<title>␊
|
||||||
|
Test bundle!␊
|
||||||
|
</title>␊
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">␊
|
||||||
|
␊
|
||||||
|
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%20%20%20%20%3Ctitle%3EHTML5%20Logo%3C%2Ftitle%3E%20%20%20%20%3Cpath%20d%3D%22M108.4%200h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206%2023h-20.3V0h63.7v23H229v46h-23M259.5%200h24.1l14.8%2024.3L313.2%200h24.1v69h-23V34.8l-16.1%2024.8l-16.1-24.8v34.2h-22.6M348.7%200h23v46.2h32.6V69h-55.6%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23e44d26%22%20d%3D%22M107.6%20471l-33-370.4h362.8l-33%20370.2L255.7%20512%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23f16529%22%20d%3D%22M256%20480.5V131H404.3L376%20447%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23ebebeb%22%20d%3D%22M142%20176.3h114v45.4h-64.2l4.2%2046.5h60v45.3H154.4M156.4%20336.3H202l3.2%2036.3%2050.8%2013.6v47.4l-93.2-26%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M369.6%20176.3H255.8v45.4h109.6M361.3%20268.2H255.8v45.4h56l-5.3%2059-50.7%2013.6v47.2l93-25.8%22%2F%3E%3C%2Fsvg%3E">␊
|
||||||
|
␊
|
||||||
|
<title>I'm cool!</title>␊
|
||||||
|
</head>␊
|
||||||
|
<body>␊
|
||||||
|
<div id="root"><div style="align-self: center"><b>App ended</b></div></div>␊
|
||||||
|
<script src="index.js" type="module"></script>␊
|
||||||
|
␊
|
||||||
|
␊
|
||||||
|
</body></html>`,
|
||||||
|
requestsFailed: [],
|
||||||
|
responses: [
|
||||||
|
'200 http://localhost/index.html',
|
||||||
|
'200 http://localhost/index.js',
|
||||||
|
'200 http://localhost/app.js',
|
||||||
|
],
|
||||||
|
}
|
||||||
BIN
test/evaluated-web-bundle/snapshots/test.js.snap
Normal file
BIN
test/evaluated-web-bundle/snapshots/test.js.snap
Normal file
Binary file not shown.
53
test/evaluated-web-bundle/test.js
Normal file
53
test/evaluated-web-bundle/test.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import {join, dirname} from "node:path";
|
||||||
|
|
||||||
|
import test from "ava";
|
||||||
|
import { rollup } from "rollup";
|
||||||
|
import urlPlugin from "@rollup/plugin-url";
|
||||||
|
|
||||||
|
import html from "../../src/index.ts";
|
||||||
|
import {runBrowserTest} from "../util/browser-test.ts";
|
||||||
|
|
||||||
|
import {fileURLToPath} from "node:url";
|
||||||
|
import handlebars from "handlebars";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
process.chdir(join(__dirname, 'fixtures'));
|
||||||
|
|
||||||
|
|
||||||
|
const defaultAssetInclude = [
|
||||||
|
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||||
|
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||||
|
'**/*.(webm|mp4)',// video
|
||||||
|
];
|
||||||
|
|
||||||
|
test.serial('web-bundle', async (t) => {
|
||||||
|
const out = await runBrowserTest({
|
||||||
|
input: 'index.hbs',
|
||||||
|
treeshake: 'smallest',
|
||||||
|
plugins: [
|
||||||
|
html({
|
||||||
|
transform(src) {
|
||||||
|
return handlebars.compile(src)({
|
||||||
|
head: `<title>I'm cool!</title>`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
urlPlugin({
|
||||||
|
include: defaultAssetInclude,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
path: 'index.html',
|
||||||
|
log: t.log,
|
||||||
|
},{
|
||||||
|
dir: 'output', // Output all files
|
||||||
|
format: 'es', // iifi and cjs should be added to tests
|
||||||
|
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||||
|
chunkFileNames: '[name].js',
|
||||||
|
entryFileNames: '[name].[extname]',
|
||||||
|
assetFileNames: '[name].[extname]',
|
||||||
|
});
|
||||||
|
t.snapshot(out);
|
||||||
|
// await bundle.generate(output);
|
||||||
|
});
|
||||||
|
|
||||||
2
test/js-import/fixtures/batman.js
Normal file
2
test/js-import/fixtures/batman.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const b = ()=>'batman';
|
||||||
|
console.log(b());
|
||||||
3
test/js-import/fixtures/icon.svg
Normal file
3
test/js-import/fixtures/icon.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path style="fill:none;stroke:#00ff0d;stroke-width:5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M4.1 14.72 16 26.31 28.38 5.09"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 244 B |
11
test/js-import/fixtures/index.html
Normal file
11
test/js-import/fixtures/index.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="icon" href="./icon.svg">
|
||||||
|
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->
|
||||||
|
<!-- <link rel="stylesheet" href="./joker.css">-->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->
|
||||||
|
<!--<script src="./batman.js" type="module"></script>-->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
test/js-import/fixtures/index.js
Normal file
5
test/js-import/fixtures/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import html from "./index.html"
|
||||||
|
|
||||||
|
export function render(){
|
||||||
|
return html;
|
||||||
|
}
|
||||||
1
test/js-import/fixtures/joker.css
Normal file
1
test/js-import/fixtures/joker.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
* { width: 100%; }
|
||||||
75
test/js-import/snapshots/test.js.md
Normal file
75
test/js-import/snapshots/test.js.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Snapshot report for `test/js-import/test.js`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `test.js.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://avajs.dev).
|
||||||
|
|
||||||
|
## js-import
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
code: `var asset0 = "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E";␊
|
||||||
|
␊
|
||||||
|
const html = \`<html><head>␊
|
||||||
|
<link rel="icon" href="${asset0}">␊
|
||||||
|
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->␊
|
||||||
|
<!-- <link rel="stylesheet" href="./joker.css">-->␊
|
||||||
|
</head>␊
|
||||||
|
<body>␊
|
||||||
|
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->␊
|
||||||
|
<!--<script src="./batman.js" type="module"></script>-->␊
|
||||||
|
␊
|
||||||
|
␊
|
||||||
|
</body></html>\`;␊
|
||||||
|
␊
|
||||||
|
function render(){␊
|
||||||
|
return html;␊
|
||||||
|
}␊
|
||||||
|
␊
|
||||||
|
export { render };␊
|
||||||
|
//# sourceMappingURL=index-3d1ca61b.js.map␊
|
||||||
|
`,
|
||||||
|
fileName: 'index-3d1ca61b.js',
|
||||||
|
map: SourceMap {
|
||||||
|
file: 'index-3d1ca61b.js',
|
||||||
|
mappings: 'AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACRN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;',
|
||||||
|
names: [],
|
||||||
|
sources: [
|
||||||
|
'../icon.svg',
|
||||||
|
'../index.html',
|
||||||
|
'../index.js',
|
||||||
|
],
|
||||||
|
sourcesContent: [
|
||||||
|
'export default "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E"',
|
||||||
|
`<html>␊
|
||||||
|
<head>␊
|
||||||
|
<link rel="icon" href="./icon.svg">␊
|
||||||
|
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->␊
|
||||||
|
<!-- <link rel="stylesheet" href="./joker.css">-->␊
|
||||||
|
</head>␊
|
||||||
|
<body>␊
|
||||||
|
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->␊
|
||||||
|
<!--<script src="./batman.js" type="module"></script>-->␊
|
||||||
|
</body>␊
|
||||||
|
</html>␊
|
||||||
|
`,
|
||||||
|
`import html from "./index.html"␊
|
||||||
|
␊
|
||||||
|
export function render(){␊
|
||||||
|
return html;␊
|
||||||
|
}␊
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
version: 3,
|
||||||
|
},
|
||||||
|
source: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'index-3d1ca61b.js.map',
|
||||||
|
map: undefined,
|
||||||
|
source: '{"version":3,"file":"index-3d1ca61b.js","sources":["../icon.svg","../index.html","../index.js"],"sourcesContent":["export default \\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E\\"","<html>\\n <head>\\n <link rel=\\"icon\\" href=\\"./icon.svg\\">\\n <!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->\\n<!-- <link rel=\\"stylesheet\\" href=\\"./joker.css\\">-->\\n </head>\\n <body>\\n <!-- TODO: this shouldn\'t have been commented out, but our plugin fails if it is included (which shoudn\'t happen!!) -->\\n <!--<script src=\\"./batman.js\\" type=\\"module\\"></script>-->\\n </body>\\n</html>\\n","import html from \\"./index.html\\"\\n\\nexport function render(){\\n return html;\\n}\\n"],"names":[],"mappings":"AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACRN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;"}',
|
||||||
|
},
|
||||||
|
]
|
||||||
BIN
test/js-import/snapshots/test.js.snap
Normal file
BIN
test/js-import/snapshots/test.js.snap
Normal file
Binary file not shown.
51
test/js-import/test.js
Normal file
51
test/js-import/test.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import {join, dirname} from "node:path";
|
||||||
|
|
||||||
|
import test from "ava";
|
||||||
|
import { rollup } from "rollup";
|
||||||
|
|
||||||
|
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||||
|
|
||||||
|
import html from "../../src/index.ts";
|
||||||
|
import handlebars from "handlebars";
|
||||||
|
|
||||||
|
const output = {
|
||||||
|
dir: 'output', // Output all files
|
||||||
|
format: 'es', // iifi and cjs should be added to tests
|
||||||
|
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||||
|
};
|
||||||
|
|
||||||
|
import {fileURLToPath} from "node:url";
|
||||||
|
import urlPlugin from "@rollup/plugin-url";
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
process.chdir(join(__dirname, 'fixtures'));
|
||||||
|
|
||||||
|
const defaultAssetInclude = [
|
||||||
|
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||||
|
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||||
|
'**/*.(webm|mp4)',// video
|
||||||
|
];
|
||||||
|
|
||||||
|
test.serial('js-import', async (t) => {
|
||||||
|
const bundle = await rollup({
|
||||||
|
input: 'index.js',
|
||||||
|
plugins: [
|
||||||
|
html({
|
||||||
|
}),
|
||||||
|
// Test with assets
|
||||||
|
urlPlugin({
|
||||||
|
include: defaultAssetInclude,
|
||||||
|
limit: Number.MAX_SAFE_INTEGER,// Always inline things
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const code = await getCode(bundle, output);
|
||||||
|
debugPrintOutput('js-import',code);
|
||||||
|
t.snapshot(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// TODO various parameters
|
||||||
|
// - format: cjs, iifi, ...
|
||||||
|
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||||
|
// Watch mode tests would be its own dir
|
||||||
|
// ...
|
||||||
36
test/jsx-web-app/fixtures/app.tsx
Normal file
36
test/jsx-web-app/fixtures/app.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import {createRoot} from "react-dom/client";
|
||||||
|
import {StrictMode, useEffect, useState} from "react";
|
||||||
|
|
||||||
|
const states = ['started', 'tick', 'ended'];
|
||||||
|
export function App(){
|
||||||
|
const [state, setState] = useState(states[0])
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
let timeout: any;
|
||||||
|
let nextState = states[states.indexOf(state)+1];
|
||||||
|
if(nextState) {
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
console.log(`Test my sourcemap: ${nextState}`);
|
||||||
|
setState(nextState)
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ()=>{
|
||||||
|
if(timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [state])
|
||||||
|
|
||||||
|
return (<div style={{alignSelf: "center"}}>
|
||||||
|
<b>{state}</b>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function start({root: rootContainer}: {root: HTMLElement}){
|
||||||
|
if(!rootContainer) throw new Error("Missing root element");
|
||||||
|
const root = createRoot(rootContainer);
|
||||||
|
root.render(<StrictMode>
|
||||||
|
<App />
|
||||||
|
</StrictMode>);
|
||||||
|
}
|
||||||
8
test/jsx-web-app/fixtures/assets/logo-sq.svg
Normal file
8
test/jsx-web-app/fixtures/assets/logo-sq.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||||
|
<title>HTML5 Logo</title>
|
||||||
|
<path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/>
|
||||||
|
<path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/>
|
||||||
|
<path fill="#f16529" d="M256 480.5V131H404.3L376 447"/>
|
||||||
|
<path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/>
|
||||||
|
<path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 693 B |
14
test/jsx-web-app/fixtures/babel.config.js
Normal file
14
test/jsx-web-app/fixtures/babel.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export default {
|
||||||
|
presets: [
|
||||||
|
["@babel/preset-env", {
|
||||||
|
shippedProposals: true,
|
||||||
|
}],
|
||||||
|
["@babel/preset-typescript", {
|
||||||
|
|
||||||
|
}],
|
||||||
|
["@babel/preset-react", {
|
||||||
|
development: process.env.BABEL_ENV === "development",
|
||||||
|
runtime: "automatic"
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
}
|
||||||
18
test/jsx-web-app/fixtures/index.hbs
Normal file
18
test/jsx-web-app/fixtures/index.hbs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>
|
||||||
|
Test bundle!
|
||||||
|
</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="icon" href="./assets/logo-sq.svg">
|
||||||
|
|
||||||
|
{{{ head }}}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root">Here the app should load!</div>
|
||||||
|
<script src="./index.mjs" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
27
test/jsx-web-app/fixtures/index.mjs
Normal file
27
test/jsx-web-app/fixtures/index.mjs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Dynamically loads libraries and bootstraps the application
|
||||||
|
(async ()=>{
|
||||||
|
// Add a loader here if any
|
||||||
|
const root = document.getElementById('root')
|
||||||
|
if(root) root.innerHTML= `<div style="align-self: center">My app has loaded!!</div>`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load app
|
||||||
|
const [
|
||||||
|
appModule,
|
||||||
|
] = await Promise.all([
|
||||||
|
import("./app.tsx"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
console.log("Bootstrapped, ready to go!");
|
||||||
|
|
||||||
|
// Wait for DOM to be ready
|
||||||
|
if(document.readyState === 'loading') {
|
||||||
|
await new Promise((resolve)=>document.addEventListener('DOMContentLoaded', resolve));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the app!
|
||||||
|
await appModule.start({root});
|
||||||
|
}catch(err){
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
})()
|
||||||
16
test/jsx-web-app/fixtures/tsconfig.json
Normal file
16
test/jsx-web-app/fixtures/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"target": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"module": "ESNext",
|
||||||
|
"strict": true,
|
||||||
|
"paths":{
|
||||||
|
"react": ["./node_modules/@types/react"]
|
||||||
|
},
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowUnreachableCode": true,
|
||||||
|
"allowUnusedLabels": true,
|
||||||
|
"noUnusedLocals": false
|
||||||
|
},
|
||||||
|
}
|
||||||
85
test/jsx-web-app/test.js
Normal file
85
test/jsx-web-app/test.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import {join, dirname} from "node:path";
|
||||||
|
|
||||||
|
import test from "ava";
|
||||||
|
|
||||||
|
// Rollup * plugins
|
||||||
|
import { rollup } from "rollup";
|
||||||
|
import urlPlugin from "@rollup/plugin-url";
|
||||||
|
import nodeResolve from "@rollup/plugin-node-resolve";
|
||||||
|
import babelPlugin from "@rollup/plugin-babel";
|
||||||
|
import commonJsPlugin from "@rollup/plugin-commonjs";
|
||||||
|
import typescriptPlugin from "@rollup/plugin-typescript";
|
||||||
|
import replacePlugin from "@rollup/plugin-replace";
|
||||||
|
|
||||||
|
import html from "../../src/index.ts";
|
||||||
|
import {runBrowserTest} from "../util/browser-test.ts";
|
||||||
|
|
||||||
|
import {fileURLToPath} from "node:url";
|
||||||
|
import handlebars from "handlebars";
|
||||||
|
// import {debugPrintOutput, getCode, runBrowserTest} from "../util/index.ts";
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
process.chdir(join(__dirname, 'fixtures'));
|
||||||
|
|
||||||
|
|
||||||
|
const defaultAssetInclude = [
|
||||||
|
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||||
|
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||||
|
'**/*.(webm|mp4)',// video
|
||||||
|
];
|
||||||
|
|
||||||
|
test.serial('web-bundle', async (t) => {
|
||||||
|
const out = await runBrowserTest({
|
||||||
|
input: 'index.hbs',
|
||||||
|
treeshake: 'smallest',
|
||||||
|
plugins: [
|
||||||
|
html({
|
||||||
|
transform(src) {
|
||||||
|
return handlebars.compile(src)({
|
||||||
|
head: `<title>I'm cool!</title>`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
nodeResolve({
|
||||||
|
extensions: ['.js', '.mjs', '.jsx', '.ts', '.tsx'],
|
||||||
|
browser: true,
|
||||||
|
}),
|
||||||
|
commonJsPlugin({
|
||||||
|
}),
|
||||||
|
typescriptPlugin({
|
||||||
|
sourceMap: true,
|
||||||
|
// exclude: 'node_modules/**',
|
||||||
|
noEmitOnError: true,
|
||||||
|
outputToFilesystem: false,
|
||||||
|
noForceEmit: true,
|
||||||
|
jsx: "preserve",
|
||||||
|
}),
|
||||||
|
babelPlugin({
|
||||||
|
extensions: ['.js', '.mjs', '.jsx', '.ts', '.tsx'],
|
||||||
|
babelHelpers: "bundled",
|
||||||
|
}),
|
||||||
|
replacePlugin({
|
||||||
|
preventAssignment: false,
|
||||||
|
'process.env.NODE_ENV': process.env.NODE_ENV==='production'?`'${process.env.NODE_ENV}'` : '"development"'
|
||||||
|
}),
|
||||||
|
|
||||||
|
urlPlugin({
|
||||||
|
include: defaultAssetInclude,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}, {
|
||||||
|
path: 'index.html',
|
||||||
|
log: t.log,
|
||||||
|
},{
|
||||||
|
dir: 'output', // Output all files
|
||||||
|
format: 'es', // iifi and cjs should be added to tests
|
||||||
|
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||||
|
chunkFileNames: '[name].js',
|
||||||
|
entryFileNames: '[name].[extname]',
|
||||||
|
assetFileNames: '[name].[extname]',
|
||||||
|
});
|
||||||
|
t.snapshot(out);
|
||||||
|
|
||||||
|
// const code = await getCode(bundle, output);
|
||||||
|
// debugPrintOutput('jsx-web-app',code);
|
||||||
|
});
|
||||||
|
|
||||||
Binary file not shown.
@@ -3,7 +3,7 @@ import {join, dirname} from "node:path";
|
|||||||
import test from "ava";
|
import test from "ava";
|
||||||
import {rollup} from "rollup";
|
import {rollup} from "rollup";
|
||||||
import liveReload from "rollup-plugin-livereload";
|
import liveReload from "rollup-plugin-livereload";
|
||||||
import {debugPrintOutput, getCode} from "../util/test.js";
|
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||||
|
|
||||||
import html from "../../src/index.ts";
|
import html from "../../src/index.ts";
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ test.serial('live-reload', async (t) => {
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const code = await getCode(bundle, output, true);
|
const code = await getCode(bundle, output);
|
||||||
await bundle.close();// Make sure live-reload closes itself
|
await bundle.close();// Make sure live-reload closes itself
|
||||||
debugPrintOutput('live-reload',code);
|
debugPrintOutput('live-reload',code);
|
||||||
t.snapshot(code);
|
t.snapshot(code);
|
||||||
|
|||||||
2
test/multi-entry/fixtures/admin/batman.js
Normal file
2
test/multi-entry/fixtures/admin/batman.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const b = ()=>'batman';
|
||||||
|
console.log(b());
|
||||||
@@ -8,5 +8,6 @@
|
|||||||
import {adminDeps} from "../app/admin-deps.js";
|
import {adminDeps} from "../app/admin-deps.js";
|
||||||
bootstrap(document.getElementById('root'), adminDeps());
|
bootstrap(document.getElementById('root'), adminDeps());
|
||||||
</script>
|
</script>
|
||||||
|
<script src="./batman.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -9,6 +9,64 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
> Snapshot 1
|
> Snapshot 1
|
||||||
|
|
||||||
[
|
[
|
||||||
|
{
|
||||||
|
code: `const b = ()=>'batman';␊
|
||||||
|
console.log(b());␊
|
||||||
|
␊
|
||||||
|
export { b };␊
|
||||||
|
//# sourceMappingURL=batman-c7fa228c.js.map␊
|
||||||
|
`,
|
||||||
|
fileName: 'admin/batman-c7fa228c.js',
|
||||||
|
map: SourceMap {
|
||||||
|
file: 'batman-c7fa228c.js',
|
||||||
|
mappings: 'AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;',
|
||||||
|
names: [],
|
||||||
|
sources: [
|
||||||
|
'../../admin/batman.js',
|
||||||
|
],
|
||||||
|
sourcesContent: [
|
||||||
|
`export const b = ()=>'batman';␊
|
||||||
|
console.log(b());␊
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
version: 3,
|
||||||
|
},
|
||||||
|
source: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'admin/batman-c7fa228c.js.map',
|
||||||
|
map: undefined,
|
||||||
|
source: '{"version":3,"file":"batman-c7fa228c.js","sources":["../../admin/batman.js"],"sourcesContent":["export const b = ()=>\'batman\';\\nconsole.log(b());\\n"],"names":[],"mappings":"AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;"}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'admin/index.body.script0.js-15dfaff3.js.map',
|
||||||
|
map: undefined,
|
||||||
|
source: '{"version":3,"file":"index.body.script0.js-15dfaff3.js","sources":["../../app/admin-deps.js","../../admin/index.html.body.script0.js"],"sourcesContent":["export function adminDeps(){\\n return \\"robin!\\";\\n}\\n","\\n import {bootstrap} from \\"../app/app.js\\"\\n import {adminDeps} from \\"../app/admin-deps.js\\";\\n bootstrap(document.getElementById(\'root\'), adminDeps());\\n "],"names":[],"mappings":";;AAAO,SAAS,SAAS,EAAE;AAC3B,IAAI,OAAO,QAAQ,CAAC;AACpB;;ACCY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC"}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'admin/index.html',
|
||||||
|
map: undefined,
|
||||||
|
source: `<html><head>␊
|
||||||
|
</head>␊
|
||||||
|
<body>␊
|
||||||
|
<div id="root"></div>␊
|
||||||
|
<script type="module">import { b as bootstrap } from '../app-01141b67.js';␊
|
||||||
|
␊
|
||||||
|
function adminDeps(){␊
|
||||||
|
return "robin!";␊
|
||||||
|
}␊
|
||||||
|
␊
|
||||||
|
bootstrap(document.getElementById('root'), adminDeps());␊
|
||||||
|
//# sourceMappingURL=index.body.script0.js-15dfaff3.js.map␊
|
||||||
|
</script>␊
|
||||||
|
<script src="batman-c7fa228c.js" type="module"></script>␊
|
||||||
|
␊
|
||||||
|
␊
|
||||||
|
</body></html>`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
code: `const bootstrap = (el,deps = [])=>{␊
|
code: `const bootstrap = (el,deps = [])=>{␊
|
||||||
el.innerHtml = \`␊
|
el.innerHtml = \`␊
|
||||||
@@ -41,24 +99,18 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
},
|
},
|
||||||
source: undefined,
|
source: undefined,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
code: undefined,
|
|
||||||
fileName: 'index.html.body.script.js-45303f0f.js.map',
|
|
||||||
map: undefined,
|
|
||||||
source: '{"version":3,"file":"index.html.body.script.js-45303f0f.js","sources":["../index.html.body.script.js"],"sourcesContent":["\\n import {bootstrap} from \\"./app/app.js\\"\\n bootstrap(document.getElementById(\'root\'), \\"<none>\\");\\n "],"names":[],"mappings":";;AAEY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC"}',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: undefined,
|
|
||||||
fileName: 'admin/index.html.body.script.js-15dfaff3.js.map',
|
|
||||||
map: undefined,
|
|
||||||
source: '{"version":3,"file":"index.html.body.script.js-15dfaff3.js","sources":["../../app/admin-deps.js","../../admin/index.html.body.script.js"],"sourcesContent":["export function adminDeps(){\\n return \\"robin!\\";\\n}\\n","\\n import {bootstrap} from \\"../app/app.js\\"\\n import {adminDeps} from \\"../app/admin-deps.js\\";\\n bootstrap(document.getElementById(\'root\'), adminDeps());\\n "],"names":[],"mappings":";;AAAO,SAAS,SAAS,EAAE;AAC3B,IAAI,OAAO,QAAQ,CAAC;AACpB;;ACCY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC"}',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
code: undefined,
|
code: undefined,
|
||||||
fileName: 'app-01141b67.js.map',
|
fileName: 'app-01141b67.js.map',
|
||||||
map: undefined,
|
map: undefined,
|
||||||
source: '{"version":3,"file":"app-01141b67.js","sources":["../app/app.js"],"sourcesContent":["export const bootstrap = (el,deps = [])=>{\\n el.innerHtml = `\\n <div>I\'m \\"annoying\\" ${\\"in case we need to test \\\\`string\\\\` escaping.\\"}. Hence this file \\\\\'tries\\\\\' to include all allowed forms of \'it\'</div>\\n <div>Deps: ${deps}</div>\\n `;\\n}\\n"],"names":[],"mappings":"AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB,4BAA4B,EAAE,8CAA8C,CAAC;AAC7E,mBAAmB,EAAE,IAAI,CAAC;AAC1B,IAAI,CAAC,CAAC;AACN;;;;"}',
|
source: '{"version":3,"file":"app-01141b67.js","sources":["../app/app.js"],"sourcesContent":["export const bootstrap = (el,deps = [])=>{\\n el.innerHtml = `\\n <div>I\'m \\"annoying\\" ${\\"in case we need to test \\\\`string\\\\` escaping.\\"}. Hence this file \\\\\'tries\\\\\' to include all allowed forms of \'it\'</div>\\n <div>Deps: ${deps}</div>\\n `;\\n}\\n"],"names":[],"mappings":"AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB,4BAA4B,EAAE,8CAA8C,CAAC;AAC7E,mBAAmB,EAAE,IAAI,CAAC;AAC1B,IAAI,CAAC,CAAC;AACN;;;;"}',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'index.body.script.js-45303f0f.js.map',
|
||||||
|
map: undefined,
|
||||||
|
source: '{"version":3,"file":"index.body.script.js-45303f0f.js","sources":["../index.html.body.script.js"],"sourcesContent":["\\n import {bootstrap} from \\"./app/app.js\\"\\n bootstrap(document.getElementById(\'root\'), \\"<none>\\");\\n "],"names":[],"mappings":";;AAEY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC"}',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
code: undefined,
|
code: undefined,
|
||||||
fileName: 'index.html',
|
fileName: 'index.html',
|
||||||
@@ -70,28 +122,7 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
<script type="module">import { b as bootstrap } from './app-01141b67.js';␊
|
<script type="module">import { b as bootstrap } from './app-01141b67.js';␊
|
||||||
␊
|
␊
|
||||||
bootstrap(document.getElementById('root'), "<none>");␊
|
bootstrap(document.getElementById('root'), "<none>");␊
|
||||||
//# sourceMappingURL=index.html.body.script.js-45303f0f.js.map␊
|
//# sourceMappingURL=index.body.script.js-45303f0f.js.map␊
|
||||||
</script>␊
|
|
||||||
␊
|
|
||||||
␊
|
|
||||||
</body></html>`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: undefined,
|
|
||||||
fileName: 'admin/index.html',
|
|
||||||
map: undefined,
|
|
||||||
source: `<html><head>␊
|
|
||||||
</head>␊
|
|
||||||
<body>␊
|
|
||||||
<div id="root"></div>␊
|
|
||||||
<script type="module">import { b as bootstrap } from '../app-01141b67.js';␊
|
|
||||||
␊
|
|
||||||
function adminDeps(){␊
|
|
||||||
return "robin!";␊
|
|
||||||
}␊
|
|
||||||
␊
|
|
||||||
bootstrap(document.getElementById('root'), adminDeps());␊
|
|
||||||
//# sourceMappingURL=index.html.body.script.js-15dfaff3.js.map␊
|
|
||||||
</script>␊
|
</script>␊
|
||||||
␊
|
␊
|
||||||
␊
|
␊
|
||||||
|
|||||||
Binary file not shown.
@@ -1,8 +1,8 @@
|
|||||||
import {join, dirname} from "node:path";
|
import {resolve, join, dirname} from "node:path";
|
||||||
|
|
||||||
import test from "ava";
|
import test from "ava";
|
||||||
import { rollup } from "rollup";
|
import { rollup } from "rollup";
|
||||||
import {debugPrintOutput, getCode} from "../util/test.js";
|
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||||
|
|
||||||
import html from "../../src/index.ts";
|
import html from "../../src/index.ts";
|
||||||
|
|
||||||
@@ -21,14 +21,14 @@ test.serial('multi-entry', async (t) => {
|
|||||||
const bundle = await rollup({
|
const bundle = await rollup({
|
||||||
input: {
|
input: {
|
||||||
['index']: 'index.html',
|
['index']: 'index.html',
|
||||||
['admin/index']: 'admin/index.html'
|
['admin/index']: resolve(__dirname,'fixtures','admin/index.html'),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
html({
|
html({
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const code = await getCode(bundle, output, true);
|
const code = await getCode(bundle, output);
|
||||||
debugPrintOutput('multi-entry',code);
|
debugPrintOutput('multi-entry',code);
|
||||||
t.snapshot(code);
|
t.snapshot(code);
|
||||||
});
|
});
|
||||||
|
|||||||
5
test/rewrite-url/fixtures/admin/app.js
Normal file
5
test/rewrite-url/fixtures/admin/app.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const bootstrap = (el,deps = [])=>{
|
||||||
|
el.innerHtml = `
|
||||||
|
<div>load the app</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
8
test/rewrite-url/fixtures/admin/index.html
Normal file
8
test/rewrite-url/fixtures/admin/index.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="./app.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
test/rewrite-url/fixtures/index.html
Normal file
8
test/rewrite-url/fixtures/index.html
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="./admin/app.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
74
test/rewrite-url/snapshots/test.js.md
Normal file
74
test/rewrite-url/snapshots/test.js.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Snapshot report for `test/rewrite-url/test.js`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `test.js.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://avajs.dev).
|
||||||
|
|
||||||
|
## rewrite-url
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
code: `const bootstrap = (el,deps = [])=>{␊
|
||||||
|
el.innerHtml = \`␊
|
||||||
|
<div>load the app</div>␊
|
||||||
|
\`;␊
|
||||||
|
};␊
|
||||||
|
␊
|
||||||
|
export { bootstrap };␊
|
||||||
|
//# sourceMappingURL=app-88ed8fd6.js.map␊
|
||||||
|
`,
|
||||||
|
fileName: 'admin/app-88ed8fd6.js',
|
||||||
|
map: SourceMap {
|
||||||
|
file: 'app-88ed8fd6.js',
|
||||||
|
mappings: 'AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB;AACA,IAAI,CAAC,CAAC;AACN;;;;',
|
||||||
|
names: [],
|
||||||
|
sources: [
|
||||||
|
'../../admin/app.js',
|
||||||
|
],
|
||||||
|
sourcesContent: [
|
||||||
|
`export const bootstrap = (el,deps = [])=>{␊
|
||||||
|
el.innerHtml = \`␊
|
||||||
|
<div>load the app</div>␊
|
||||||
|
\`;␊
|
||||||
|
}␊
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
version: 3,
|
||||||
|
},
|
||||||
|
source: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'admin/app-88ed8fd6.js.map',
|
||||||
|
map: undefined,
|
||||||
|
source: '{"version":3,"file":"app-88ed8fd6.js","sources":["../../admin/app.js"],"sourcesContent":["export const bootstrap = (el,deps = [])=>{\\n el.innerHtml = `\\n <div>load the app</div>\\n `;\\n}\\n"],"names":[],"mappings":"AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB;AACA,IAAI,CAAC,CAAC;AACN;;;;"}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'admin/index.html',
|
||||||
|
map: undefined,
|
||||||
|
source: `<html><head>␊
|
||||||
|
</head>␊
|
||||||
|
<body>␊
|
||||||
|
<div id="root"></div>␊
|
||||||
|
<script src="/admin/app-88ed8fd6.js" type="module"></script>␊
|
||||||
|
␊
|
||||||
|
␊
|
||||||
|
</body></html>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'index.html',
|
||||||
|
map: undefined,
|
||||||
|
source: `<html><head>␊
|
||||||
|
</head>␊
|
||||||
|
<body>␊
|
||||||
|
<div id="root"></div>␊
|
||||||
|
<script src="/admin/app-88ed8fd6.js" type="module"></script>␊
|
||||||
|
␊
|
||||||
|
␊
|
||||||
|
</body></html>`,
|
||||||
|
},
|
||||||
|
]
|
||||||
BIN
test/rewrite-url/snapshots/test.js.snap
Normal file
BIN
test/rewrite-url/snapshots/test.js.snap
Normal file
Binary file not shown.
64
test/rewrite-url/test.js
Normal file
64
test/rewrite-url/test.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import {resolve, join, dirname} from "node:path";
|
||||||
|
import test from "ava";
|
||||||
|
import {runBrowserTest} from "../util/index.ts";
|
||||||
|
|
||||||
|
import html from "../../src/index.ts";
|
||||||
|
|
||||||
|
import {fileURLToPath} from "node:url";
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
process.chdir(join(__dirname, 'fixtures'));
|
||||||
|
|
||||||
|
|
||||||
|
test.serial('rewrite-url', async (t) => {
|
||||||
|
|
||||||
|
const out = await runBrowserTest({
|
||||||
|
input: {
|
||||||
|
['index']: 'index.html',
|
||||||
|
['admin/index']: resolve(__dirname,'fixtures','admin/index.html'),
|
||||||
|
['admin/app']: resolve(__dirname,'fixtures','admin/app.js'),
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
html({
|
||||||
|
rewriteUrl(relative, {rootPath, from}){
|
||||||
|
return `/${rootPath}`;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},{
|
||||||
|
log: t.log,
|
||||||
|
filterOutput:{
|
||||||
|
// TODO: Currently only need the "await getCode(bundle, output);" as output
|
||||||
|
},
|
||||||
|
path: '/admin'
|
||||||
|
}, {
|
||||||
|
dir: 'output', // Output all files
|
||||||
|
format: 'es', // iifi and cjs should be added to tests
|
||||||
|
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||||
|
});
|
||||||
|
|
||||||
|
t.snapshot(out.code); // Snapshot the result code
|
||||||
|
|
||||||
|
// const bundle = await rollup({
|
||||||
|
// input: {
|
||||||
|
// ['index']: 'index.html',
|
||||||
|
// ['admin/index']: resolve(__dirname,'fixtures','admin/index.html'),
|
||||||
|
// ['admin/app']: resolve(__dirname,'fixtures','admin/app.js'),
|
||||||
|
// },
|
||||||
|
// plugins: [
|
||||||
|
// html({
|
||||||
|
// rewriteUrl(relative, {rootPath, from}){
|
||||||
|
// return `/${rootPath}`;
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// ]
|
||||||
|
// });
|
||||||
|
// const code = await getCode(bundle, output);
|
||||||
|
// debugPrintOutput('rewrite-url',code);
|
||||||
|
// t.snapshot(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO various parameters
|
||||||
|
// - format: cjs, iifi, ...
|
||||||
|
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||||
|
// Watch mode tests would be its own dir
|
||||||
|
// ...
|
||||||
Binary file not shown.
@@ -3,7 +3,7 @@ import {join, dirname} from "node:path";
|
|||||||
import test from "ava";
|
import test from "ava";
|
||||||
import { rollup } from "rollup";
|
import { rollup } from "rollup";
|
||||||
|
|
||||||
import {debugPrintOutput, getCode} from "../util/test.js";
|
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||||
|
|
||||||
import html from "../../src/index.ts";
|
import html from "../../src/index.ts";
|
||||||
import handlebars from "handlebars";
|
import handlebars from "handlebars";
|
||||||
@@ -30,7 +30,7 @@ test.serial('handlebars', async (t) => {
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const code = await getCode(bundle, output, true);
|
const code = await getCode(bundle, output);
|
||||||
debugPrintOutput('handlebars',code);
|
debugPrintOutput('handlebars',code);
|
||||||
t.snapshot(code);
|
t.snapshot(code);
|
||||||
});
|
});
|
||||||
|
|||||||
3
test/url-plugin/fixtures/output/fb585fdb6db313c9.svg
Normal file
3
test/url-plugin/fixtures/output/fb585fdb6db313c9.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path style="fill:none;stroke:#00ff0d;stroke-width:5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M4.1 14.72 16 26.31 28.38 5.09"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 244 B |
Binary file not shown.
@@ -4,7 +4,7 @@ import test from "ava";
|
|||||||
import { rollup } from "rollup";
|
import { rollup } from "rollup";
|
||||||
import urlPlugin from "@rollup/plugin-url";
|
import urlPlugin from "@rollup/plugin-url";
|
||||||
|
|
||||||
import {debugPrintOutput, getCode} from "../util/test.js";
|
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||||
|
|
||||||
import html from "../../src/index.ts";
|
import html from "../../src/index.ts";
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ test.serial('copied-assets', async (t) => {
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const code = await getCode(bundle, output, true);
|
const code = await getCode(bundle, output);
|
||||||
debugPrintOutput('copied-assets',code);
|
debugPrintOutput('copied-assets',code);
|
||||||
t.snapshot(code);
|
t.snapshot(code);
|
||||||
});
|
});
|
||||||
@@ -55,7 +55,7 @@ test.serial('inlined-assets', async (t) => {
|
|||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
const code = await getCode(bundle, output, true);
|
const code = await getCode(bundle, output);
|
||||||
debugPrintOutput('inlined-assets',code);
|
debugPrintOutput('inlined-assets',code);
|
||||||
t.snapshot(code);
|
t.snapshot(code);
|
||||||
});
|
});
|
||||||
|
|||||||
129
test/util/browser-test.ts
Normal file
129
test/util/browser-test.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import {Plugin, InputPluginOption, RollupOptions, OutputOptions, RollupOutput} from "rollup";
|
||||||
|
import {TestOptions as BrowserTestOptions, TestOutput as PuppeteerTestOutput} from "./puppeteer-run-test.js";
|
||||||
|
import { rollup } from "rollup";
|
||||||
|
import serveTest, {LogCallback} from "./serve-test.js";
|
||||||
|
import type {ExecutionContext} from "ava";
|
||||||
|
import {getCode, TestOutput} from "./code-output.ts";
|
||||||
|
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * The AVA context used to test (ie t.snapshot(..) )
|
||||||
|
// */
|
||||||
|
// t: ExecutionContext
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// filterOutput:{
|
||||||
|
// html: true,
|
||||||
|
// console: ['log','error','warn'],// TODO: or warning? need to check what possible values are
|
||||||
|
// errors: true, // again don't know possible values
|
||||||
|
// responses: true, // interesting to see what other values were requested
|
||||||
|
// requestsFailed: true, // will probably also be replicated into console errors, but helpful to have if imports werent found
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// try{
|
||||||
|
// // Track requests, errors and console
|
||||||
|
// page.on('console', message => {
|
||||||
|
// let [type, text] = [message.type(), message.text()];
|
||||||
|
// if(replaceHost){
|
||||||
|
// text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
// }
|
||||||
|
// if((<any>filterOutput.console)?.includes?.(<any>type) ?? (filterOutput.console === true)){// TODO: add callback option
|
||||||
|
// output.console?.push(`[${type}] ${text}`);
|
||||||
|
// }
|
||||||
|
// }).on('pageerror', ({ message }) => {
|
||||||
|
// let text = message;
|
||||||
|
// if(replaceHost){
|
||||||
|
// text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
// }
|
||||||
|
// if(filterOutput.errors === true) {// TODO add callback option
|
||||||
|
// output.errors?.push(text)
|
||||||
|
// }
|
||||||
|
// }).on('response', response => {
|
||||||
|
// let [status, url] = [response.status(), response.url()]
|
||||||
|
// if(replaceHost){
|
||||||
|
// url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
// }
|
||||||
|
// if(filterOutput.responses === true) {// TODO add callback option
|
||||||
|
// output.responses?.push(`${status} ${url}`)
|
||||||
|
// }
|
||||||
|
// }).on('requestfailed', request => {
|
||||||
|
// let [failure, url] = [request.failure()?.errorText, request.url()];
|
||||||
|
// if(replaceHost){
|
||||||
|
// failure = failure?.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
// url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
// }
|
||||||
|
// if(filterOutput.requestsFailed === true) {// TODO add callback option
|
||||||
|
// output.requestsFailed?.push(`${failure} ${url}`)
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// testOptions.t?.snapshot?.(testOutput);
|
||||||
|
|
||||||
|
export interface OutputFilterOptions {
|
||||||
|
html?: boolean
|
||||||
|
console?: ('log'|'error'|'warn')[] | true
|
||||||
|
errors?: boolean, // again don't know possible values
|
||||||
|
responses?: boolean, // interesting to see what other values were requested
|
||||||
|
requestsFailed?: boolean, // will probably also be replicated into console errors, but helpful to have if imports werent found
|
||||||
|
}
|
||||||
|
export interface BrowserTestInput extends BrowserTestOptions{
|
||||||
|
log?: LogCallback;
|
||||||
|
/**
|
||||||
|
* Optionally specify what to filter from the output
|
||||||
|
*/
|
||||||
|
filterOutput?: OutputFilterOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface BrowserTestOutput extends PuppeteerTestOutput{
|
||||||
|
code: TestOutput[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runBrowserTest(
|
||||||
|
build: RollupOptions,
|
||||||
|
test?: BrowserTestInput | false,
|
||||||
|
output?: OutputOptions
|
||||||
|
) : Promise<Partial<BrowserTestOutput>>{
|
||||||
|
const resolvedPlugins = await Promise.resolve(build.plugins||null);
|
||||||
|
let pluginsArray : InputPluginOption[] = [];
|
||||||
|
if(resolvedPlugins && resolvedPlugins instanceof Array){
|
||||||
|
pluginsArray = resolvedPlugins
|
||||||
|
}else if(resolvedPlugins){
|
||||||
|
pluginsArray = [resolvedPlugins];
|
||||||
|
}
|
||||||
|
|
||||||
|
let testOutput: Partial<BrowserTestOutput> = {};
|
||||||
|
const bundle = await rollup({
|
||||||
|
...build,
|
||||||
|
plugins: [
|
||||||
|
...pluginsArray,
|
||||||
|
// TODO check if browser output is requested (either for snapshot or for testing)
|
||||||
|
...(test? [serveTest({
|
||||||
|
// TODO: intercept output from the serveTest? (and include as one bit in output options below, for snapshotting)
|
||||||
|
...test,
|
||||||
|
log: test.log ?? console.log,
|
||||||
|
onResult: (output)=>{
|
||||||
|
testOutput = {...testOutput, ...output};
|
||||||
|
}
|
||||||
|
})]: [])
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO make configurable?
|
||||||
|
const generated = await bundle.generate({
|
||||||
|
dir: 'output', // Output all files
|
||||||
|
format: 'es', // iifi and cjs should be added to tests
|
||||||
|
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||||
|
chunkFileNames: '[name].js',
|
||||||
|
entryFileNames: '[name].mjs',
|
||||||
|
assetFileNames: '[name].[extname]',
|
||||||
|
});
|
||||||
|
|
||||||
|
if(output){
|
||||||
|
testOutput.code = await getCode(bundle, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return testOutput
|
||||||
|
|
||||||
|
}
|
||||||
26
test/util/code-output.ts
Normal file
26
test/util/code-output.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import type {RollupBuild, OutputOptions, OutputAsset, OutputChunk, SourceMap} from "rollup";
|
||||||
|
|
||||||
|
export interface TestOutput{
|
||||||
|
code: string,
|
||||||
|
fileName: string,
|
||||||
|
source: any,
|
||||||
|
map: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCode = async (bundle: RollupBuild, outputOptions: OutputOptions): Promise<TestOutput[]> => {
|
||||||
|
const { output } = await bundle.generate(outputOptions || { format: 'cjs', exports: 'auto' });
|
||||||
|
|
||||||
|
return output.sort((a,b)=> {
|
||||||
|
if(a.fileName === b.fileName && (<OutputAsset>a).source !== (<OutputAsset>b).source){ return (<OutputAsset>a).source<(<OutputAsset>b).source?-1:1}
|
||||||
|
return a.fileName < b.fileName ? -1 : (a.fileName > b.fileName? 1 : 0);
|
||||||
|
}).map(chunk=> {
|
||||||
|
const { code, map } = (<OutputChunk>chunk);
|
||||||
|
const { fileName, source } = (<OutputAsset>chunk);
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
fileName,
|
||||||
|
source,
|
||||||
|
map
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
5
test/util/debug-mode.ts
Normal file
5
test/util/debug-mode.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import inspector from 'node:inspector';
|
||||||
|
|
||||||
|
export function isInDebugMode() {
|
||||||
|
return (inspector.url() !== undefined) || process.env.DEBUG;
|
||||||
|
}
|
||||||
8
test/util/index.ts
Normal file
8
test/util/index.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// TODO: this should be the main module used, other should be imported manually if exceptions are needed?
|
||||||
|
export * from "./browser-test.ts";
|
||||||
|
|
||||||
|
export * from "./code-output.ts";
|
||||||
|
export * from "./print-code-output.ts";
|
||||||
|
export * from "./serve-test.ts";
|
||||||
|
|
||||||
|
// export * from './misc.js';
|
||||||
@@ -1,56 +1,7 @@
|
|||||||
|
// This is still from the old rollup plugin we forked from. For now not used.
|
||||||
|
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
import chalk from "chalk";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import('rollup').RollupBuild} bundle
|
|
||||||
* @param {import('rollup').OutputOptions} [outputOptions]
|
|
||||||
*/
|
|
||||||
export const getCode = async (bundle, outputOptions, allFiles = false) => {
|
|
||||||
const { output } = await bundle.generate(outputOptions || { format: 'cjs', exports: 'auto' });
|
|
||||||
|
|
||||||
if (allFiles) {
|
|
||||||
return output.map(({ code, fileName, source, map }) => {
|
|
||||||
return {
|
|
||||||
code,
|
|
||||||
fileName,
|
|
||||||
source,
|
|
||||||
map
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const [{ code }] = output;
|
|
||||||
return code;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const debugPrintOutput = async (header, files) => {
|
|
||||||
const out = [];
|
|
||||||
|
|
||||||
const headFn = chalk.bgCyan;
|
|
||||||
const headPadding = header.split('').map(x=>'#').join('');
|
|
||||||
out.push(...[
|
|
||||||
headFn(`##${headPadding}##`),
|
|
||||||
headFn(`# ${header} #`),
|
|
||||||
headFn(`##${headPadding}##`),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const fileHeadFn = chalk.blue;
|
|
||||||
const fileContentFn = chalk.blackBright;
|
|
||||||
out.push(...(files.map(file=>{
|
|
||||||
return [
|
|
||||||
fileHeadFn(`${file.fileName}:`),
|
|
||||||
fileContentFn(`${file.code??file.source}`),
|
|
||||||
'',
|
|
||||||
]
|
|
||||||
}).flat()));
|
|
||||||
|
|
||||||
out.push(...[
|
|
||||||
headFn(`##${headPadding}##`),
|
|
||||||
]);
|
|
||||||
|
|
||||||
process.env.DEBUG? console.log(out.join('\n')) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import('rollup').RollupBuild} bundle
|
* @param {import('rollup').RollupBuild} bundle
|
||||||
32
test/util/print-code-output.ts
Normal file
32
test/util/print-code-output.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import process from "node:process";
|
||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
import {TestOutput} from "./code-output.ts";
|
||||||
|
|
||||||
|
export const debugPrintOutput = async (header: string, files: TestOutput[]) => {
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
const headFn = chalk.bgCyan;
|
||||||
|
const headPadding = header.split('').map(x=>'#').join('');
|
||||||
|
out.push(...[
|
||||||
|
headFn(`##${headPadding}##`),
|
||||||
|
headFn(`# ${header} #`),
|
||||||
|
headFn(`##${headPadding}##`),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const fileHeadFn = chalk.blue;
|
||||||
|
const fileContentFn = chalk.blackBright;
|
||||||
|
out.push(...(files.map(file=>{
|
||||||
|
return [
|
||||||
|
fileHeadFn(`${file.fileName}:`),
|
||||||
|
fileContentFn(`${file.code??file.source}`),
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
}).flat()));
|
||||||
|
|
||||||
|
out.push(...[
|
||||||
|
headFn(`##${headPadding}##`),
|
||||||
|
]);
|
||||||
|
|
||||||
|
process.env.DEBUG? console.log(out.join('\n')) : null;
|
||||||
|
};
|
||||||
133
test/util/puppeteer-run-test.ts
Normal file
133
test/util/puppeteer-run-test.ts
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* Puppeteer + from-memory devServer rollup plugin to open the result in a webpage en output the result
|
||||||
|
* (after an optional series of commands to the puppeteer Page)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import puppeteer, {Page} from "puppeteer";
|
||||||
|
import {fileURLToPath, URL} from "node:url";
|
||||||
|
import {isInDebugMode} from "./debug-mode.ts";
|
||||||
|
|
||||||
|
|
||||||
|
export type PageTestCallback = (page: Page)=>Promise<void>;
|
||||||
|
|
||||||
|
export interface TestOptions {
|
||||||
|
path: string
|
||||||
|
cb: PageTestCallback
|
||||||
|
replaceHost: boolean
|
||||||
|
replaceHostWith?: string
|
||||||
|
}
|
||||||
|
const defaultOptions: Partial<TestOptions> = {
|
||||||
|
path: 'index.html',
|
||||||
|
cb: async (page: Page)=>{
|
||||||
|
await page.waitForNetworkIdle({});
|
||||||
|
},
|
||||||
|
replaceHost: true,
|
||||||
|
replaceHostWith: `http://localhost`,
|
||||||
|
}
|
||||||
|
export interface TestOutput{
|
||||||
|
html: string,
|
||||||
|
console: string[],
|
||||||
|
errors: string[],
|
||||||
|
responses: string[],
|
||||||
|
requestsFailed: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a page in a puppeteer browser and return the resulting HTML and logmessages produced.
|
||||||
|
* Optionally a callback can be provided to simulate user interactions on the page before returning the HTML
|
||||||
|
* When DEBUG mode is detected, puppeteer headless mode will be disabled allowing you to inspect the page if you set a breakpoint
|
||||||
|
*
|
||||||
|
* @param opts
|
||||||
|
* @param hostUrl
|
||||||
|
*/
|
||||||
|
export async function puppeteerRunTest(opts: Partial<TestOptions>, hostUrl: string){
|
||||||
|
const options : TestOptions = (<TestOptions>{
|
||||||
|
...defaultOptions,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
path,
|
||||||
|
cb,
|
||||||
|
replaceHost,
|
||||||
|
replaceHostWith,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: isInDebugMode()? false : 'new',
|
||||||
|
});
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
let output : TestOutput = {
|
||||||
|
html: '',
|
||||||
|
console: [],
|
||||||
|
errors: [],
|
||||||
|
responses: [],
|
||||||
|
requestsFailed: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let errored = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Track requests, errors and console
|
||||||
|
page.on('console', message => {
|
||||||
|
let [type, text] = [message.type(), message.text()];
|
||||||
|
if (replaceHost) {
|
||||||
|
text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
output.console?.push(`[${type}] ${text}`);
|
||||||
|
}).on('pageerror', ({message}) => {
|
||||||
|
let text = message;
|
||||||
|
if (replaceHost) {
|
||||||
|
text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
output.errors?.push(text);
|
||||||
|
}).on('response', response => {
|
||||||
|
let [status, url] = [response.status(), response.url()]
|
||||||
|
if (replaceHost) {
|
||||||
|
url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
output.responses?.push(`${status} ${url}`);
|
||||||
|
}).on('requestfailed', request => {
|
||||||
|
let [failure, url] = [request.failure()?.errorText, request.url()];
|
||||||
|
if (replaceHost) {
|
||||||
|
failure = failure?.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
output.requestsFailed?.push(`${failure} ${url}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = new URL(path??'', hostUrl);
|
||||||
|
await page.goto(url.href);
|
||||||
|
|
||||||
|
if (!cb) {
|
||||||
|
await page.waitForNetworkIdle({});
|
||||||
|
} else {
|
||||||
|
await cb(page);
|
||||||
|
}
|
||||||
|
const htmlHandle = await page.$('html');
|
||||||
|
const html = await page.evaluate(html => html?.outerHTML ?? html?.innerHTML, htmlHandle);
|
||||||
|
|
||||||
|
// Add the final html
|
||||||
|
output.html = html || '';
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}catch(err){
|
||||||
|
errored = true;
|
||||||
|
throw err;
|
||||||
|
}finally{
|
||||||
|
if(isInDebugMode() && !errored){
|
||||||
|
console.log(`DEBUG MODE ENABLED, Close the puppeteer browsertab to continue!\n${import.meta.url}:144`);
|
||||||
|
await new Promise((resolve)=>{
|
||||||
|
page.on('close', ()=>{
|
||||||
|
console.log("Page closed");
|
||||||
|
resolve(null);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
await page.close();
|
||||||
|
}
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
289
test/util/serve-test.ts
Normal file
289
test/util/serve-test.ts
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
/**
|
||||||
|
* Puppeteer + from-memory devServer rollup plugin to open the result in a webpage en output the result
|
||||||
|
* (after an optional series of commands to the puppeteer Page)
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import {puppeteerRunTest, PageTestCallback, TestOutput} from "./puppeteer-run-test.ts";
|
||||||
|
import {isInDebugMode} from "./debug-mode.ts";
|
||||||
|
|
||||||
|
import {resolve, posix} from "node:path";
|
||||||
|
import fs from "node:fs/promises";
|
||||||
|
import type {Stats} from "node:fs";
|
||||||
|
|
||||||
|
import { createServer as createHttpsServer } from 'https'
|
||||||
|
import { createServer} from 'http'
|
||||||
|
|
||||||
|
import { Mime } from 'mime/lite'
|
||||||
|
import standardTypes from 'mime/types/standard.js'
|
||||||
|
import otherTypes from 'mime/types/other.js'
|
||||||
|
|
||||||
|
|
||||||
|
import type {NormalizedOutputOptions, OutputAsset, OutputBundle, OutputChunk, Plugin} from 'rollup';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
IncomingHttpHeaders, OutgoingHttpHeaders,
|
||||||
|
IncomingMessage, ServerResponse,
|
||||||
|
Server
|
||||||
|
} from 'http'
|
||||||
|
import type { ServerOptions } from 'https'
|
||||||
|
|
||||||
|
import test, {ExecutionContext} from "ava";
|
||||||
|
import {createReadStream} from "fs";
|
||||||
|
|
||||||
|
|
||||||
|
type TypeMap = {
|
||||||
|
[key: string]: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type ErrorCodeException = Error & {code: string};
|
||||||
|
export type TestResultCallback = (output: TestOutput)=>void;
|
||||||
|
export type LogCallback = (...args: string[])=>void;
|
||||||
|
|
||||||
|
|
||||||
|
export interface ServeTestOptions {
|
||||||
|
/**
|
||||||
|
* Change the path to be opened when the test is started
|
||||||
|
* Remember to start with a slash, e.g. `'/different/page'`
|
||||||
|
*/
|
||||||
|
path?: string
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fallback to serving from a specified srcDir, this allows setting breakpoints on sourcecode and test the sourcemaps
|
||||||
|
*/
|
||||||
|
srcDir?: string|boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to manually take control of the page and simulate user interactions
|
||||||
|
*/
|
||||||
|
cb?: PageTestCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to `true` to return index.html (200) instead of error page (404)
|
||||||
|
* or path to fallback page
|
||||||
|
*/
|
||||||
|
historyApiFallback?: boolean | string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the host of the server (default: `'localhost'`)
|
||||||
|
*/
|
||||||
|
host?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the port that the server will listen on (default: `10001`)
|
||||||
|
*/
|
||||||
|
port?: number | string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default server will be served over HTTP (https: `false`). It can optionally be served over HTTPS.
|
||||||
|
*/
|
||||||
|
https?: ServerOptions | false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom response headers
|
||||||
|
*/
|
||||||
|
headers?:
|
||||||
|
| IncomingHttpHeaders
|
||||||
|
| OutgoingHttpHeaders
|
||||||
|
| {
|
||||||
|
// i.e. Parameters<OutgoingMessage["setHeader"]>
|
||||||
|
[name: string]: number | string | ReadonlyArray<string>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set custom mime types, usage https://github.com/broofa/mime#mimedefinetypemap-force--false
|
||||||
|
*/
|
||||||
|
mimeTypes?: TypeMap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute function after server has begun listening
|
||||||
|
*/
|
||||||
|
onListening?: (server: Server) => void
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RollupServeTestOptions extends ServeTestOptions{
|
||||||
|
/**
|
||||||
|
* A callback to run when a test has been run
|
||||||
|
*/
|
||||||
|
onResult?: TestResultCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to log messages
|
||||||
|
*/
|
||||||
|
log?: LogCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serve your rolled up bundle like webpack-dev-server
|
||||||
|
* @param {import('..').RollupServeOptions} options
|
||||||
|
*/
|
||||||
|
export default function serveTest (options: RollupServeTestOptions ): Plugin {
|
||||||
|
const mime = new Mime(standardTypes, otherTypes)
|
||||||
|
const testOptions = {
|
||||||
|
port: 0,
|
||||||
|
headers: {},
|
||||||
|
historyApiFallback: true,
|
||||||
|
srcDir: '', // Serve source dir as fallback (for sourcemaps / debugging)
|
||||||
|
onListening: function noop (){},
|
||||||
|
...options||{},
|
||||||
|
https: options.https??false,
|
||||||
|
mimeTypes: options.mimeTypes? mime.define(options.mimeTypes, true): false
|
||||||
|
}
|
||||||
|
|
||||||
|
let server : Server;
|
||||||
|
let bundle : OutputBundle = {};
|
||||||
|
|
||||||
|
const logTest = (msg: string, mode: 'info'|'warn' = 'info')=>{
|
||||||
|
if(isInDebugMode()){
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
const modeColor = {
|
||||||
|
green: 32,
|
||||||
|
info: 34,
|
||||||
|
warn: 33,
|
||||||
|
}[mode];
|
||||||
|
testOptions.log?.(`\u001b[${modeColor}m${msg}\u001b[0m`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestListener = async (request: IncomingMessage, response: ServerResponse) => {
|
||||||
|
// Remove querystring
|
||||||
|
const unsafePath = decodeURI(request.url!.split('?')[0])
|
||||||
|
|
||||||
|
// Don't allow path traversal
|
||||||
|
const urlPath = posix.normalize(unsafePath)
|
||||||
|
|
||||||
|
for(const [key, value] of Object.entries((<OutgoingHttpHeaders>testOptions.headers))){
|
||||||
|
response.setHeader(key, value!);
|
||||||
|
}
|
||||||
|
|
||||||
|
function urlToFilePath(url:string){
|
||||||
|
return url[0]==='/'?url.slice(1):url;
|
||||||
|
}
|
||||||
|
let filePath = urlToFilePath(urlPath);
|
||||||
|
let absPath: string | undefined = undefined;
|
||||||
|
let stats: Stats | undefined = undefined;
|
||||||
|
|
||||||
|
if(!bundle[filePath]){
|
||||||
|
if(testOptions.srcDir || testOptions.srcDir===''){
|
||||||
|
try{
|
||||||
|
absPath = resolve(<string>testOptions.srcDir||'',filePath);
|
||||||
|
stats = await fs.stat(absPath);
|
||||||
|
}catch(err){
|
||||||
|
// File not found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!(stats?.isFile()) && testOptions.historyApiFallback) {
|
||||||
|
const fallbackPath = typeof testOptions.historyApiFallback === 'string'
|
||||||
|
? testOptions.historyApiFallback
|
||||||
|
: '/index.html';
|
||||||
|
if(bundle[urlToFilePath(fallbackPath)]){
|
||||||
|
filePath = urlToFilePath(fallbackPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mimeType = mime.getType(filePath!);
|
||||||
|
if(bundle[filePath]) {
|
||||||
|
let file: OutputChunk | OutputAsset = bundle[filePath];
|
||||||
|
const content = (<OutputChunk>file).code || (<OutputAsset>file).source; // Todo might need to read a source file;
|
||||||
|
response.writeHead(200, {'Content-Type': mimeType || 'text/plain'});
|
||||||
|
response.end(content, 'utf-8');
|
||||||
|
logTest(`[200] ${request.url}`);
|
||||||
|
return;
|
||||||
|
}else if(stats?.isFile()){
|
||||||
|
response.writeHead(200, {
|
||||||
|
'Content-Type': mimeType || 'text/plain',
|
||||||
|
'Content-Length': stats.size,
|
||||||
|
'Last-Modified': stats.mtime.toUTCString()
|
||||||
|
});
|
||||||
|
const content = await fs.readFile(absPath!);
|
||||||
|
response.end(content);
|
||||||
|
response.end();
|
||||||
|
logTest(`[200] ${request.url} (src)`);
|
||||||
|
}else{
|
||||||
|
response.writeHead(404)
|
||||||
|
response.end(
|
||||||
|
'404 Not Found' + '\n\n' + filePath,
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
|
||||||
|
logTest(`[404] ${request.url}`, "warn");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function closeServerOnTermination () {
|
||||||
|
const terminationSignals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP']
|
||||||
|
terminationSignals.forEach(signal => {
|
||||||
|
process.on(signal, () => {
|
||||||
|
if (server) {
|
||||||
|
server.close()
|
||||||
|
process.exit()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// release previous server instance if rollup is reloading configuration in watch mode
|
||||||
|
// @ts-ignore
|
||||||
|
if (server) {
|
||||||
|
server.close()
|
||||||
|
} else {
|
||||||
|
closeServerOnTermination()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If HTTPS options are available, create an HTTPS server
|
||||||
|
server = testOptions.https
|
||||||
|
? createHttpsServer(testOptions.https, requestListener)
|
||||||
|
: createServer(requestListener)
|
||||||
|
server.listen(
|
||||||
|
typeof(testOptions.port)==='string'? Number.parseInt(testOptions.port):testOptions.port,
|
||||||
|
testOptions.host,
|
||||||
|
undefined,
|
||||||
|
() => testOptions.onListening?.(server)
|
||||||
|
)
|
||||||
|
|
||||||
|
testOptions.port = (<any>server.address())?.port ?? testOptions.port;
|
||||||
|
|
||||||
|
// Assemble url for error and info messages
|
||||||
|
const url = (testOptions.https ? 'https' : 'http') + '://' + (testOptions.host || 'localhost') + ':' + testOptions.port
|
||||||
|
|
||||||
|
// Handle common server errors
|
||||||
|
server.on('error', e => {
|
||||||
|
if ((<ErrorCodeException>e).code === 'EADDRINUSE') {
|
||||||
|
console.error(url + ' is in use, either stop the other server or use a different port.')
|
||||||
|
process.exit()
|
||||||
|
} else {
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let first = true
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'serve',
|
||||||
|
generateBundle: {
|
||||||
|
order: 'post',
|
||||||
|
async handler(options, output){
|
||||||
|
bundle = output;
|
||||||
|
if (first) {
|
||||||
|
first = false
|
||||||
|
|
||||||
|
const testOutput = await puppeteerRunTest({
|
||||||
|
...testOptions
|
||||||
|
}, url);
|
||||||
|
|
||||||
|
testOptions.onResult?.(testOutput);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeBundle (){
|
||||||
|
// Done with the bundle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
test/util/test.d.ts
vendored
45
test/util/test.d.ts
vendored
@@ -1,45 +0,0 @@
|
|||||||
/* eslint-disable import/no-extraneous-dependencies */
|
|
||||||
import type { RollupBuild, OutputOptions, OutputChunk, OutputAsset } from 'rollup';
|
|
||||||
import type { Assertions } from 'ava';
|
|
||||||
|
|
||||||
interface GetCode {
|
|
||||||
(bundle: RollupBuild, outputOptions?: OutputOptions | null, allFiles?: false): Promise<string>;
|
|
||||||
(bundle: RollupBuild, outputOptions: OutputOptions | null | undefined, allFiles: true): Promise<
|
|
||||||
Array<{
|
|
||||||
code: OutputChunk['code'] | undefined;
|
|
||||||
fileName: OutputChunk['fileName'] | OutputAsset['fileName'];
|
|
||||||
source: OutputAsset['source'] | undefined;
|
|
||||||
}>
|
|
||||||
>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getCode: GetCode;
|
|
||||||
|
|
||||||
export function getFiles(
|
|
||||||
bundle: RollupBuild,
|
|
||||||
outputOptions?: OutputOptions
|
|
||||||
): Promise<
|
|
||||||
{
|
|
||||||
fileName: string;
|
|
||||||
content: any;
|
|
||||||
}[]
|
|
||||||
>;
|
|
||||||
|
|
||||||
export function evaluateBundle(bundle: RollupBuild): Promise<Pick<NodeModule, 'exports'>>;
|
|
||||||
|
|
||||||
export function getImports(bundle: RollupBuild): Promise<string[]>;
|
|
||||||
|
|
||||||
export function getResolvedModules(bundle: RollupBuild): Promise<Record<string, string>>;
|
|
||||||
|
|
||||||
export function onwarn(warning: string | any): void;
|
|
||||||
|
|
||||||
export function testBundle(
|
|
||||||
t: Assertions,
|
|
||||||
bundle: RollupBuild,
|
|
||||||
options: { inject: Record<string, any>; options: Record<string, any> }
|
|
||||||
): Promise<{
|
|
||||||
code: string;
|
|
||||||
error?: any;
|
|
||||||
result?: any;
|
|
||||||
module: Pick<NodeModule, 'exports'>;
|
|
||||||
}>;
|
|
||||||
7
test/watch/fixtures/index.html
Normal file
7
test/watch/fixtures/index.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="./watched-file.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
3
test/watch/fixtures/watched-file.js
Normal file
3
test/watch/fixtures/watched-file.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
export const a = 1; // DO NOT CHANGE ME HERE, but in ../test.js
|
||||||
|
|
||||||
53
test/watch/snapshots/test.js.md
Normal file
53
test/watch/snapshots/test.js.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Snapshot report for `test/watch/test.js`
|
||||||
|
|
||||||
|
The actual snapshot is saved in `test.js.snap`.
|
||||||
|
|
||||||
|
Generated by [AVA](https://avajs.dev).
|
||||||
|
|
||||||
|
## watch
|
||||||
|
|
||||||
|
> Snapshot 1
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'index.html',
|
||||||
|
map: undefined,
|
||||||
|
source: `<html><head>␊
|
||||||
|
</head>␊
|
||||||
|
<body>␊
|
||||||
|
<script src="watched-file-8c4729c5.js" type="module"></script>␊
|
||||||
|
␊
|
||||||
|
␊
|
||||||
|
</body></html>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: `const a = 2; // If i show up as a changed file, then the watch test has gone wrong!␊
|
||||||
|
␊
|
||||||
|
export { a };␊
|
||||||
|
//# sourceMappingURL=watched-file-8c4729c5.js.map␊
|
||||||
|
`,
|
||||||
|
fileName: 'watched-file-8c4729c5.js',
|
||||||
|
map: SourceMap {
|
||||||
|
file: 'watched-file-8c4729c5.js',
|
||||||
|
mappings: 'AACgB,MAAC,CAAC,GAAG,EAAE;;;;',
|
||||||
|
names: [],
|
||||||
|
sources: [
|
||||||
|
'../watched-file.js',
|
||||||
|
],
|
||||||
|
sourcesContent: [
|
||||||
|
`␊
|
||||||
|
export const a = 2; // If i show up as a changed file, then the watch test has gone wrong!␊
|
||||||
|
`,
|
||||||
|
],
|
||||||
|
version: 3,
|
||||||
|
},
|
||||||
|
source: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: undefined,
|
||||||
|
fileName: 'watched-file-8c4729c5.js.map',
|
||||||
|
map: undefined,
|
||||||
|
source: '{"version":3,"file":"watched-file-8c4729c5.js","sources":["../watched-file.js"],"sourcesContent":["\\n export const a = 2; // If i show up as a changed file, then the watch test has gone wrong!\\n "],"names":[],"mappings":"AACgB,MAAC,CAAC,GAAG,EAAE;;;;"}',
|
||||||
|
},
|
||||||
|
]
|
||||||
BIN
test/watch/snapshots/test.js.snap
Normal file
BIN
test/watch/snapshots/test.js.snap
Normal file
Binary file not shown.
97
test/watch/test.js
Normal file
97
test/watch/test.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import {join, dirname} from "node:path";
|
||||||
|
|
||||||
|
import test from "ava";
|
||||||
|
import * as rollup from "rollup";
|
||||||
|
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||||
|
import {resolve} from "node:path";
|
||||||
|
import {writeFile} from "node:fs/promises";
|
||||||
|
|
||||||
|
import html from "../../src/index.ts";
|
||||||
|
|
||||||
|
const output = {
|
||||||
|
dir: 'output', // Output all files
|
||||||
|
format: 'es', // iifi and cjs should be added to tests
|
||||||
|
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||||
|
};
|
||||||
|
|
||||||
|
import {fileURLToPath} from "node:url";
|
||||||
|
import {pathToFileURL} from "url";
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
process.chdir(join(__dirname, 'fixtures'));
|
||||||
|
|
||||||
|
|
||||||
|
test.serial('watch', async (t) => {
|
||||||
|
const origContent = `
|
||||||
|
export const a = 1; // DO NOT CHANGE ME HERE, but in ../test.js
|
||||||
|
`;
|
||||||
|
const changeContent = `
|
||||||
|
export const a = 2; // If i show up as a changed file, then the watch test has gone wrong!
|
||||||
|
`
|
||||||
|
|
||||||
|
const path = resolve(__dirname, 'fixtures/watched-file.js');
|
||||||
|
await writeFile(path, origContent, {encoding: 'utf-8'});
|
||||||
|
|
||||||
|
const watcher = rollup.watch({
|
||||||
|
input: 'index.html',
|
||||||
|
output,
|
||||||
|
plugins: [
|
||||||
|
html({
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
skipWrite: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
async (bundle)=>{
|
||||||
|
await writeFile(path, changeContent, {encoding: 'utf-8'});
|
||||||
|
// Just wait on the watch mode to pick up on the changes
|
||||||
|
},
|
||||||
|
async (bundle)=>{
|
||||||
|
const code = await getCode(bundle, output);
|
||||||
|
debugPrintOutput('watch',code);
|
||||||
|
|
||||||
|
// Reset the source file
|
||||||
|
await writeFile(path, origContent, {encoding: 'utf-8'});
|
||||||
|
|
||||||
|
// Assert the output is what we exapect;
|
||||||
|
t.snapshot(code);
|
||||||
|
|
||||||
|
watcher
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await new Promise((resolve, reject)=>{
|
||||||
|
watcher.on('event', async (event) => {
|
||||||
|
const {result} = event;
|
||||||
|
switch (event.code) {
|
||||||
|
case "START":
|
||||||
|
t.log(`WATCH STARTED`);
|
||||||
|
break;
|
||||||
|
case "BUNDLE_START":
|
||||||
|
t.log(`REBUILDING...`);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "BUNDLE_END":
|
||||||
|
t.log(`Rebuilt...`);
|
||||||
|
const cb = steps.shift();
|
||||||
|
|
||||||
|
const generated = await result.generate(output);
|
||||||
|
const cbResult = await cb(result);
|
||||||
|
if(steps.length===0){
|
||||||
|
watcher.close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "ERROR":
|
||||||
|
reject(event.error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result) {
|
||||||
|
result.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -13,7 +13,8 @@
|
|||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"allowJs": true
|
"allowJs": true,
|
||||||
|
"allowImportingTsExtensions": true
|
||||||
},
|
},
|
||||||
"exclude": ["dist", "node_modules", "test/types"],
|
"exclude": ["dist", "node_modules", "test/types"],
|
||||||
"include": ["src/**/*", "types/**/*"],
|
"include": ["src/**/*", "types/**/*"],
|
||||||
|
|||||||
24
types/index.d.ts
vendored
24
types/index.d.ts
vendored
@@ -15,6 +15,11 @@ export interface RollupHtmlTransformContext {
|
|||||||
// files: Record<string, (OutputChunk | OutputAsset)[]>;
|
// files: Record<string, (OutputChunk | OutputAsset)[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RewriteUrlCallbackContext {
|
||||||
|
from: string;
|
||||||
|
rootPath: string;
|
||||||
|
}
|
||||||
|
export type RewriteUrlCallback = (relative: string, context: RewriteUrlCallbackContext) => string|Promise<string>;
|
||||||
export type TransformCallback = (source: string, transformContext: RollupHtmlTransformContext) => string|Promise<string>;
|
export type TransformCallback = (source: string, transformContext: RollupHtmlTransformContext) => string|Promise<string>;
|
||||||
|
|
||||||
export interface RollupHtmlOptions {
|
export interface RollupHtmlOptions {
|
||||||
@@ -23,6 +28,7 @@ export interface RollupHtmlOptions {
|
|||||||
* Follows the same logic as rollup's [entryFileNames](https://rollupjs.org/configuration-options/#output-entryfilenames).
|
* Follows the same logic as rollup's [entryFileNames](https://rollupjs.org/configuration-options/#output-entryfilenames).
|
||||||
*/
|
*/
|
||||||
htmlFileNames?: string|((chunkInfo: PreRenderedChunk) => string);
|
htmlFileNames?: string|((chunkInfo: PreRenderedChunk) => string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transform a source file passed into this plugin to HTML. For example: a handlebars transform
|
* Transform a source file passed into this plugin to HTML. For example: a handlebars transform
|
||||||
* ```
|
* ```
|
||||||
@@ -33,6 +39,17 @@ export interface RollupHtmlOptions {
|
|||||||
*/
|
*/
|
||||||
transform?: TransformCallback;
|
transform?: TransformCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional callback to rewrite how resources are referenced in the output HTML.
|
||||||
|
* For example to rewrite urls to as paths from the root of your website:
|
||||||
|
* ```
|
||||||
|
* rewriteUrl(relative, {rootPath, from}){
|
||||||
|
* return `/${rootPath}`;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
rewriteUrl?: RewriteUrlCallback;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detect which references (<a href="...">, <img src="...">) to resolve from a HTML node.
|
* Detect which references (<a href="...">, <img src="...">) to resolve from a HTML node.
|
||||||
* This rarely needs to be overloaded, but can be used to support non-native attributes used by custom-elements.
|
* This rarely needs to be overloaded, but can be used to support non-native attributes used by custom-elements.
|
||||||
@@ -49,7 +66,14 @@ export interface RollupHtmlOptions {
|
|||||||
* Return a falsey value to skip this reference. Return true to resolve as is. (or string to transform the id)
|
* Return a falsey value to skip this reference. Return true to resolve as is. (or string to transform the id)
|
||||||
*/
|
*/
|
||||||
resolve?: ResolveCallback;
|
resolve?: ResolveCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Pattern](https://github.com/micromatch/picomatch#globbing-features) to include
|
||||||
|
*/
|
||||||
include?: FilterPattern;
|
include?: FilterPattern;
|
||||||
|
/**
|
||||||
|
* [Pattern](https://github.com/micromatch/picomatch#globbing-features) to exclude
|
||||||
|
*/
|
||||||
exclude?: FilterPattern
|
exclude?: FilterPattern
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user