Compare commits
20 Commits
develop
...
feature/je
| Author | SHA1 | Date | |
|---|---|---|---|
| c4878caef3 | |||
| e46f668ac8 | |||
| 3b540d0c48 | |||
| 1c55b894c9 | |||
| 3e46055845 | |||
| e96c2248ee | |||
| 2adfbee74b | |||
| 71a377417d | |||
| afd4a3c9ae | |||
| 980d33c48e | |||
| 5c1e528304 | |||
| 5d2a45ef81 | |||
| 6e50208557 | |||
| 48dcdefee1 | |||
| ba09aaf915 | |||
| 9bf026f0c3 | |||
| a784abc1b0 | |||
| 52c104f781 | |||
| b18ac5c361 | |||
| ba07649981 |
10
.run/Template Jest.run.xml
Normal file
10
.run/Template Jest.run.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="true" type="JavaScriptTestRunnerJest">
|
||||
<node-interpreter value="/usr/bin/node" />
|
||||
<node-options value="--experimental-vm-modules" />
|
||||
<jest-options value="--detectOpenHandles" />
|
||||
<envs />
|
||||
<scope-kind value="ALL" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
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
|
||||
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
|
||||
|
||||
30
README.md
30
README.md
@@ -10,10 +10,13 @@
|
||||
[](https://liberamanifesto.com)
|
||||
|
||||
# rollup-plugin-html-entry2
|
||||
| :warning: WARNING |
|
||||
|:-------------------------------------------------------------------|
|
||||
| **Experimental-stage** plugin. Expect bugs and missing features... |
|
||||
|
||||
| :warning: WARNING |
|
||||
|:----------------------------------------------------------------------------------------------------------------------|
|
||||
| **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.
|
||||
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`
|
||||
|
||||
Type: `Function`<br>
|
||||
Type: `Function`\
|
||||
Default: `undefined`\
|
||||
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)
|
||||
|
||||
## Status
|
||||
|
||||
This plugin is in an early state. As such not everything that is supported yet, and the options may change.
|
||||
|
||||
### (Rudimentarily) supported
|
||||
- Importing JS via `<script src="..." type="module">` tags
|
||||
- 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
|
||||
- Inline scripts (i.e `<script>...</script>`)
|
||||
|
||||
|
||||
### Not (yet) supported
|
||||
- Inline scripts (i.e `<script>...</script>`)
|
||||
### Not (yet/properly) supported
|
||||
- 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
|
||||
- CommonJS (cjs) and IIFI output formats. (Is UMD actually ever used?)
|
||||
- Overriding which tags to ignore/include
|
||||
- Other (various) plugins such as those for HMR etc
|
||||
- Overriding which DOM-nodes and resulting URLS to ignore/include (in a clean way)
|
||||
- Other (various) plugins such as typescript, or those for HMR etc
|
||||
- ...
|
||||
|
||||
# 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...\
|
||||
[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
|
||||
## git.cerxes.net
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
const babelConfig ={
|
||||
presets: [
|
||||
["@babel/preset-typescript", {
|
||||
|
||||
}],
|
||||
],
|
||||
plugins: [
|
||||
|
||||
87
package.json
87
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rollup-plugin-html-entry2",
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.7",
|
||||
"description": "Teaches rollup how to deal with HTML, allows to use HTML-files as entry-points.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
@@ -12,7 +12,7 @@
|
||||
"bugs": "https://git.cerxes.net/rollup-apps/plugin-html/issues",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"main": "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:lint": "pnpm build && pnpm lint-staged",
|
||||
"ci:test": "pnpm test -- --verbose",
|
||||
"test": "ava",
|
||||
"save-test": "ava --update-snapshots"
|
||||
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
||||
"save-test": "NODE_OPTIONS='--experimental-vm-modules' jest -u"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -47,7 +47,7 @@
|
||||
"template"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"rollup": "^3.0.0"
|
||||
"rollup": "^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
@@ -55,50 +55,49 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.1",
|
||||
"@rollup/pluginutils": "^5.0.5",
|
||||
"magic-string": "^0.30.5",
|
||||
"parse5": "^7.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.15.11",
|
||||
"@rollup/plugin-typescript": "^11.1.0",
|
||||
"postcss": "^8.4.22",
|
||||
"rollup": "^3.20.3",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"typescript": "^5.0.4",
|
||||
"del-cli": "^5.0.0",
|
||||
"tslib": "^2.5.0",
|
||||
"ava": "^5.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@babel/core": "^7.23.3",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@babel/preset-env": "^7.23.6",
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.5",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-url": "^8.0.2",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@types/node": "^18.18.12",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"chalk": "^5.3.0",
|
||||
"del-cli": "^5.1.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"lint-staged": "^13.3.0",
|
||||
"nyc": "^15.1.0",
|
||||
"lint-staged": "^13.2.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"@rollup/plugin-url": "^8.0.1",
|
||||
"chalk": "^5.2.0",
|
||||
"rollup-plugin-livereload": "^2.0.5"
|
||||
"postcss": "^8.4.31",
|
||||
"rollup": "^4.12.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-livereload": "^2.0.5",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"typescript": "^5.3.2",
|
||||
"puppeteer": "^21.5.2",
|
||||
"mime": "^4.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"ts-jest": "^29.1.2",
|
||||
"@jest/globals": "^29.7.0"
|
||||
},
|
||||
"types": "./types/index.d.ts",
|
||||
"ava": {
|
||||
"workerThreads": false,
|
||||
"files": [
|
||||
"!**/fixtures/**",
|
||||
"!**/util/**",
|
||||
"!**/helpers/**",
|
||||
"!**/recipes/**",
|
||||
"!**/types.ts"
|
||||
],
|
||||
"extensions": {
|
||||
"ts": "module",
|
||||
"js": true
|
||||
},
|
||||
"nodeArguments": [
|
||||
"--loader=ts-node/esm",
|
||||
"--experimental-vm-modules"
|
||||
]
|
||||
"jest": {
|
||||
"preset":"ts-jest/presets/default-esm",
|
||||
"setupFiles": ["./test/setup.js"]
|
||||
}
|
||||
}
|
||||
|
||||
4615
pnpm-lock.yaml
generated
4615
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]}"`), '');
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
import type {
|
||||
LoadedReference
|
||||
} from "../types/load.d.ts";
|
||||
import {DefaultTreeAdapterMap} from "parse5";
|
||||
import type {DefaultTreeAdapterMap} from "parse5";
|
||||
|
||||
// Internal type
|
||||
export type HtmlImport = LoadedReference & {
|
||||
|
||||
378
src/index.ts
378
src/index.ts
@@ -1,4 +1,4 @@
|
||||
import { extname } from "node:path";
|
||||
|
||||
|
||||
import type {
|
||||
Plugin,
|
||||
@@ -19,18 +19,25 @@ import type {
|
||||
LoadReference, BodyReference, AttributeReference, LoadFunction
|
||||
} from '../types/index.d.ts';
|
||||
|
||||
// createFilter function is a utility that constructs a filter function from include/exclude patterns.
|
||||
import {createFilter} from '@rollup/pluginutils';
|
||||
// parse5 package is used for parsing HTML.
|
||||
import {parse as parseHtml, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5";
|
||||
// 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 {makeLoader, makeInlineId} from "./loader.js";
|
||||
import {HtmlImport, HtmlModule} from "./html-module.js";
|
||||
|
||||
import {dirname} from "node:path";
|
||||
import posix from "node:path/posix";
|
||||
|
||||
import crypto from "node:crypto";
|
||||
|
||||
// utilities
|
||||
import {makeLoader, makeInlineId} from "./loader.ts";
|
||||
import {HtmlImport, HtmlModule} from "./html-module.ts";
|
||||
|
||||
|
||||
const defaults: RollupHtmlOptions = {
|
||||
transform: (source: string)=>source,// NO-OP
|
||||
load: makeLoader(),
|
||||
@@ -44,10 +51,17 @@ const defaults: RollupHtmlOptions = {
|
||||
const modulePrefix = `// <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 {
|
||||
const {
|
||||
publicPath,
|
||||
transform,
|
||||
rewriteUrl,
|
||||
load,
|
||||
htmlFileNames,
|
||||
resolve,
|
||||
@@ -61,17 +75,48 @@ 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")}
|
||||
|
||||
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 {
|
||||
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: {
|
||||
async handler(specifier: string,
|
||||
importer: string | undefined,
|
||||
options: { assertions: Record<string, string> }){
|
||||
async handler(specifier,
|
||||
importer,
|
||||
options){
|
||||
if(virtualSources.has(specifier)) return specifier;
|
||||
if(!filter(specifier)) return;
|
||||
|
||||
@@ -82,25 +127,21 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
||||
});
|
||||
|
||||
if(resolved){
|
||||
const moduleId = resolved.id;
|
||||
const moduleExt = extname(resolved.id);
|
||||
const moduleName = specifier.replace(new RegExp(`${moduleExt}\$`),''); // strip extension of the name if any
|
||||
const htmlModule : HtmlModule = htmlModules.get(moduleId) ?? {
|
||||
id: resolved.id,
|
||||
name: moduleName,
|
||||
imports: [],
|
||||
assetId: null,
|
||||
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}
|
||||
[pluginName]: {
|
||||
specifier: specifier,
|
||||
id: resolved.id,
|
||||
name: moduleName,
|
||||
imports: [],
|
||||
assetId: null,
|
||||
importers: new Set(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,127 +149,180 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
||||
},
|
||||
load: {
|
||||
async handler(id: string) {
|
||||
if(virtualSources.has(id)) return virtualSources.get(id);
|
||||
if(!filter(id)) return;
|
||||
if (virtualSources.has(id)) return virtualSources.get(id);
|
||||
// if (!filter(id)) return;
|
||||
}
|
||||
},
|
||||
transform: {
|
||||
order: 'pre',
|
||||
async handler(...args){
|
||||
const [code, id] = args;
|
||||
if (!filter(id)) return;
|
||||
|
||||
// Load
|
||||
const htmlModule = htmlModules.get(id);
|
||||
if(htmlModule) {
|
||||
const contents = await readFile(id, {encoding: "utf-8"});
|
||||
|
||||
const htmlSrc = transform ? await transform(contents, {
|
||||
id,
|
||||
}) : contents;
|
||||
|
||||
// Parse document and store it (TODO: check for watch mode, we should check if it needs reparsing or not)
|
||||
const document = htmlModule.document = htmlModule.document ?? parseHtml(htmlSrc);
|
||||
|
||||
// Figure out which references to load from this HTML by iterating all nodes (looking for src or href attributes)
|
||||
let htmlImports: HtmlImport[] = htmlModule.imports = [];
|
||||
if (document.childNodes) {
|
||||
let nodeQueue = document.childNodes;
|
||||
do {
|
||||
const nextQueue: DefaultTreeAdapterMap['childNode'][][] = [];
|
||||
await Promise.all(nodeQueue.map(async (node) => {
|
||||
const el = (<DefaultTreeAdapterMap['element']>node);
|
||||
const loadFunction: LoadFunction = async ({
|
||||
id: sourceId,
|
||||
source,
|
||||
type
|
||||
})=>{
|
||||
if(!sourceId){
|
||||
sourceId = makeInlineId(id, node, 'js');
|
||||
}
|
||||
if(source){
|
||||
virtualSources.set(sourceId, source);
|
||||
}
|
||||
|
||||
const resolved = await this.resolve(sourceId, id, {
|
||||
isEntry: type==='entryChunk',
|
||||
});
|
||||
if(!resolved){
|
||||
throw new Error(`Could not resolve ${sourceId} from ${id}`);
|
||||
}
|
||||
|
||||
const selfInfo = this.getModuleInfo(id);
|
||||
const importName = (source && selfInfo?.meta[pluginName].name) ? makeInlineId(selfInfo?.meta[pluginName].name, node, extname(sourceId)) : undefined;
|
||||
|
||||
const htmlImport: HtmlImport = {
|
||||
id: <string>sourceId,
|
||||
resolved: resolved,
|
||||
// loaded: loaded,
|
||||
node: el,
|
||||
type,
|
||||
source,
|
||||
referenceId:
|
||||
(resolved && (['chunk','entryChunk'].includes(type!))) ? this.emitFile({
|
||||
type: 'chunk', // Might want to adapt, or make configurable (see LoadType)
|
||||
id: resolved.id,
|
||||
name: importName,
|
||||
importer: id,
|
||||
}) : null,
|
||||
placeholder: `html-import-${crypto.randomBytes(32).toString('base64')}`,
|
||||
index: htmlImports.length,
|
||||
}
|
||||
htmlImports.push(htmlImport);
|
||||
return htmlImport.placeholder;
|
||||
}
|
||||
|
||||
let toLoad: LoadResult | undefined = load? await Promise.resolve(load({
|
||||
node: el,
|
||||
sourceId: id
|
||||
}, loadFunction)) : undefined;
|
||||
|
||||
if (toLoad !== false) {
|
||||
let asParent = (<DefaultTreeAdapterMap['parentNode']>node);
|
||||
if (asParent.childNodes) {
|
||||
nextQueue.push(asParent.childNodes);
|
||||
}
|
||||
}
|
||||
}));
|
||||
nodeQueue = nextQueue.flat();
|
||||
} while (nodeQueue.length > 0);
|
||||
// parse
|
||||
const moduleInfo = this.getModuleInfo(id);
|
||||
const moduleMeta = moduleInfo!.meta ?? {};
|
||||
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(),
|
||||
}
|
||||
|
||||
let html = serializeHtml(htmlModule.document).replaceAll(/`/g,'\\\`').replaceAll(/\$\{/g,'\\${');
|
||||
const moduleImports = [];
|
||||
for(const htmlImport of htmlImports){
|
||||
if(htmlImport.type === 'default') {
|
||||
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..
|
||||
html = html.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{
|
||||
// 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||'');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO when importing html from .js this will not do. (
|
||||
const htmlJSModule = [
|
||||
...moduleImports,
|
||||
``,
|
||||
`export const html = \`${html}\`;`,
|
||||
`export default html;`,
|
||||
].join('\n');
|
||||
return {
|
||||
code: htmlJSModule,
|
||||
};
|
||||
}
|
||||
const contents = code;
|
||||
|
||||
const htmlSrc = transform ? await transform(contents, {
|
||||
id,
|
||||
}) : contents;
|
||||
|
||||
// Parse document and store it
|
||||
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)
|
||||
let htmlImports: HtmlImport[] = htmlModule.imports = [];
|
||||
if (document.childNodes) {
|
||||
let nodeQueue = document.childNodes;
|
||||
do {
|
||||
const nextQueue: DefaultTreeAdapterMap['childNode'][][] = [];
|
||||
await Promise.all(nodeQueue.map(async (node) => {
|
||||
const el = (<DefaultTreeAdapterMap['element']>node);
|
||||
const loadFunction: LoadFunction = async ({
|
||||
id: sourceId,
|
||||
source,
|
||||
type
|
||||
})=>{
|
||||
if(!sourceId){
|
||||
sourceId = makeInlineId(id, node, 'js');
|
||||
}
|
||||
if(source){
|
||||
virtualSources.set(sourceId, source);
|
||||
}
|
||||
const resolved = await this.resolve(sourceId, id, {
|
||||
skipSelf: false, // defaults to true since rollup 4, and for virtual files this is problematic
|
||||
isEntry: type==='entryChunk',
|
||||
});
|
||||
if(!resolved){
|
||||
throw new Error(`Could not resolve ${sourceId} from ${id}`);
|
||||
}
|
||||
|
||||
const selfInfo = this.getModuleInfo(id);
|
||||
|
||||
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 = {
|
||||
id: <string>sourceId,
|
||||
resolved: resolved,
|
||||
// loaded: loaded,
|
||||
node: el,
|
||||
type,
|
||||
source,
|
||||
referenceId:
|
||||
(resolved && (['chunk','entryChunk'].includes(type!))) ? this.emitFile({
|
||||
type: 'chunk', // Might want to adapt, or make configurable (see LoadType)
|
||||
id: resolved.id,
|
||||
name: importName,
|
||||
importer: id,
|
||||
}) : null,
|
||||
placeholder: `html-import-${crypto.randomBytes(32).toString('base64')}`,
|
||||
index: htmlImports.length,
|
||||
}
|
||||
// if(entryName){
|
||||
// addedEntries.set(resolved.id, entryName);// (we could do this using meta?)
|
||||
// }
|
||||
htmlImports.push(htmlImport);
|
||||
return htmlImport.placeholder;
|
||||
}
|
||||
|
||||
let toLoad: LoadResult | undefined = load? await Promise.resolve(load({
|
||||
node: el,
|
||||
sourceId: id
|
||||
}, loadFunction)) : undefined;
|
||||
|
||||
if (toLoad !== false) {
|
||||
let asParent = (<DefaultTreeAdapterMap['parentNode']>node);
|
||||
if (asParent.childNodes) {
|
||||
nextQueue.push(asParent.childNodes);
|
||||
}
|
||||
}
|
||||
}));
|
||||
nodeQueue = nextQueue.flat();
|
||||
} while (nodeQueue.length > 0);
|
||||
}
|
||||
|
||||
// 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)
|
||||
// @ts-ignore
|
||||
let htmlJS = new MagicString(serializeHtml(htmlModule.document));
|
||||
htmlJS.replaceAll(/`/g,'\\\`').replaceAll(/\$\{/g,'\\${');
|
||||
|
||||
const moduleImports = [];
|
||||
for(const htmlImport of htmlImports){
|
||||
if(htmlImport.type === 'default') {
|
||||
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..
|
||||
htmlJS = htmlJS.replace(htmlImport.placeholder, `\${${assetId}}`);// TODO: Should we be worried about windows absolute URLs here?
|
||||
}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)
|
||||
// html = html.replace(htmlImport.placeholder, htmlImport.loaded?.code||htmlImport.source||'');
|
||||
}
|
||||
}
|
||||
|
||||
// Import all dependencies and wrap the HTML in a `...`, assign to a var and export (escaping any ` characters in the HTML)
|
||||
htmlJS.prepend([
|
||||
...moduleImports,
|
||||
`export const html = \``
|
||||
].join('\n')).append([
|
||||
`\`;`,
|
||||
`export default html;`,
|
||||
].join('\n'));
|
||||
|
||||
const map = htmlJS.generateMap({
|
||||
source: id,
|
||||
file: `${id}.map`,
|
||||
includeContent: true,
|
||||
hires: 'boundary'
|
||||
});
|
||||
|
||||
return {
|
||||
code: htmlJS.toString(),
|
||||
map: map.toString(),
|
||||
meta: moduleMeta,
|
||||
};
|
||||
}
|
||||
},
|
||||
outputOptions(options){
|
||||
return {
|
||||
...options,
|
||||
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?
|
||||
if(htmlModule){
|
||||
let fileName = typeof (htmlFileNames) === 'string' ? htmlFileNames : (<(chunkInfo:PreRenderedChunk)=>string>htmlFileNames)(chunkInfo);
|
||||
if(fileName) {
|
||||
return fileName;
|
||||
}
|
||||
}else if(addedEntry){
|
||||
return addedEntry;
|
||||
}
|
||||
return typeof (defaultOption) === 'string' ? defaultOption : (<(chunkInfo:PreRenderedChunk)=>string>defaultOption)(chunkInfo);
|
||||
},
|
||||
@@ -236,7 +330,8 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
||||
}
|
||||
},
|
||||
resolveFileUrl(options){
|
||||
const htmlModule = htmlModules.get(options.moduleId);
|
||||
const moduleInfo = this.getModuleInfo(options.moduleId);
|
||||
const htmlModule = moduleInfo?.meta?.[pluginName];
|
||||
if(htmlModule){
|
||||
// Simply use the relative path in our HTML-fileURLs instead of the default `new URL('${fileName}', document.baseURI).href`)
|
||||
return `"${options.relativePath}"`;
|
||||
@@ -247,7 +342,9 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
||||
order:'post',
|
||||
handler(chunk: RenderedChunk){
|
||||
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) {
|
||||
return modulePrefix; // Overwrite any added banner with our own
|
||||
}
|
||||
@@ -265,7 +362,11 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
||||
const chunk = (<OutputChunk>bundle);
|
||||
if(chunk.facadeModuleId) {
|
||||
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})}
|
||||
else if(virtualSources.has(chunk.facadeModuleId)){
|
||||
virtualBundles.add(bundleName);
|
||||
@@ -328,7 +429,12 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
||||
htmlContents = htmlContents.replace(htmlImport.placeholder, importResult.code);
|
||||
}else if(htmlImport.type === 'entryChunk'){
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import type {
|
||||
} from '../types/index.d.ts';
|
||||
import {parseFragment as parseHtmlFragment, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5";
|
||||
|
||||
import {KnownMappings, defaultMapping} from "./loader-mappings.js";
|
||||
import {KnownMappings, defaultMapping} from "./loader-mappings.ts";
|
||||
|
||||
/**
|
||||
* Makes a unique but human-readable name from a path within a HTML file.
|
||||
@@ -46,9 +46,17 @@ export function makeInlineId(sourceId: string, node: DefaultTreeAdapterMap['chil
|
||||
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){
|
||||
const fn : LoadNodeCallback = async function ({node, sourceId}, load){
|
||||
for(const mapping of mappings){
|
||||
|
||||
// Test the mapping for a match
|
||||
if (mapping.tagName && mapping.tagName !== node.tagName) continue; // No match, skip
|
||||
if (mapping.match){
|
||||
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){
|
||||
// Mapped on attribute, resolve its src or href (or whatever was returned)
|
||||
const attr = node.attrs.find(attr=>attr.name === (<AttributeReference>mapping).attr);
|
||||
if(!attr) continue ;// No match, skip
|
||||
const placeholder = await load({
|
||||
@@ -76,6 +87,7 @@ export function makeLoader(mappings: NodeMapping[] = defaultMapping){
|
||||
});
|
||||
attr.value = placeholder;
|
||||
}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.
|
||||
if(!body) continue; // Empty body, skip
|
||||
const placeholder = await load({
|
||||
|
||||
78
test/basic/__snapshots__/test.js.snap
Normal file
78
test/basic/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,78 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`basic inline-script 1`] = `
|
||||
[
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "script.body.script.js.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"script.body.script.js.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,
|
||||
"fileName": "script.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
document.body.appendChild(
|
||||
document.createTextNode(\`Inline script including \${b()}\`)
|
||||
);
|
||||
//# sourceMappingURL=script.body.script.js.js.map
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`basic simple 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../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": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -1,86 +0,0 @@
|
||||
# Snapshot report for `test/basic/test.js`
|
||||
|
||||
The actual snapshot is saved in `test.js.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## simple
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
␊
|
||||
export { b };␊
|
||||
//# sourceMappingURL=batman-c7fa228c.js.map␊
|
||||
`,
|
||||
fileName: '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: [
|
||||
'../batman.js',
|
||||
],
|
||||
sourcesContent: [
|
||||
`export const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
`,
|
||||
],
|
||||
version: 3,
|
||||
},
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'batman-c7fa228c.js.map',
|
||||
map: undefined,
|
||||
source: '{"version":3,"file":"batman-c7fa228c.js","sources":["../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: 'index.html',
|
||||
map: undefined,
|
||||
source: `<html><head>␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman-c7fa228c.js" type="module"></script>␊
|
||||
␊
|
||||
␊
|
||||
</body></html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## inline-script
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'script.html.body.script.js-e3b82208.js.map',
|
||||
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"}',
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'script.html',
|
||||
map: undefined,
|
||||
source: `<html><head>␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script type="module">const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
␊
|
||||
document.body.appendChild(␊
|
||||
document.createTextNode(\`Inline script including ${b()}\`)␊
|
||||
);␊
|
||||
//# sourceMappingURL=script.html.body.script.js-e3b82208.js.map␊
|
||||
</script>␊
|
||||
␊
|
||||
␊
|
||||
</body></html>`,
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@@ -1,50 +1,47 @@
|
||||
import {join, dirname} from "node:path";
|
||||
|
||||
import test from "ava";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import { rollup } from "rollup";
|
||||
import {debugPrintOutput, getCode} from "../util/test.js";
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
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";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test.serial('simple', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
]
|
||||
describe("basic", ()=> {
|
||||
test('simple', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
await bundle.close();
|
||||
debugPrintOutput('simple', code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
debugPrintOutput('simple',code);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('inline-script', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'script.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
]
|
||||
test('inline-script', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'script.html',
|
||||
plugins: [
|
||||
html({}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
await bundle.close();
|
||||
debugPrintOutput('inline-script', code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
debugPrintOutput('inline-script',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/evaluated-web-bundle/__snapshots__/test.js.snap
Normal file
36
test/evaluated-web-bundle/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,36 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`evaluated-web-bundle 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",
|
||||
],
|
||||
}
|
||||
`;
|
||||
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);
|
||||
}
|
||||
})()
|
||||
49
test/evaluated-web-bundle/test.js
Normal file
49
test/evaluated-web-bundle/test.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
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('evaluated-web-bundle', async ()=>{
|
||||
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',
|
||||
},{
|
||||
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]',
|
||||
});
|
||||
expect(out).toMatchSnapshot();
|
||||
});
|
||||
69
test/js-import/__snapshots__/test.js.snap
Normal file
69
test/js-import/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,69 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`js-import 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.js.map
|
||||
",
|
||||
"fileName": "index.js",
|
||||
"map": SourceMap {
|
||||
"file": "index.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.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"index.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;;;;"}",
|
||||
},
|
||||
]
|
||||
`;
|
||||
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%; }
|
||||
47
test/js-import/test.js
Normal file
47
test/js-import/test.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import { rollup } from "rollup";
|
||||
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
import handlebars from "handlebars";
|
||||
|
||||
|
||||
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('js-import', async () => {
|
||||
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);
|
||||
debugPrintOutput('js-import',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
// 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/__snapshots__/test.js.snap
Normal file
36
test/jsx-web-app/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,36 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`web-bundle 1`] = `
|
||||
{
|
||||
"console": [
|
||||
"[info] %cDownload the React DevTools for a better development experience: https://reactjs.org/link/react-devtools font-weight:bold",
|
||||
"[log] Bootstrapped, ready to go!",
|
||||
"[log] Test my sourcemap: tick",
|
||||
"[log] Test my sourcemap: 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>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",
|
||||
],
|
||||
}
|
||||
`;
|
||||
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, expect, jest} from "@jest/globals";
|
||||
|
||||
// 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
|
||||
];
|
||||
|
||||
|
||||
jest.setTimeout(30*1000);// Bundling react + typescript is getting heavy
|
||||
test('web-bundle', async () => {
|
||||
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'
|
||||
},{
|
||||
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]',
|
||||
});
|
||||
expect(out).toMatchSnapshot();
|
||||
|
||||
// const code = await getCode(bundle, output);
|
||||
// debugPrintOutput('jsx-web-app',code);
|
||||
});
|
||||
|
||||
54
test/live-reload/__snapshots__/test.js.snap
Normal file
54
test/live-reload/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,54 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`live-reload 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "
|
||||
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
|
||||
const test = ()=>{
|
||||
return \`I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this files \\'tries\\' to include all allowed forms of 'it'\`;
|
||||
};
|
||||
console.log(test());
|
||||
|
||||
export { test };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": ";;AAAY,MAAC,IAAI,GAAG,IAAI;AACxB,IAAI,OAAO,CAAC,eAAe,EAAE,8CAA8C,CAAC,iEAAiE,CAAC,CAAC;AAC/I,EAAC;AACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const test = ()=>{
|
||||
return \`I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this files \\'tries\\' to include all allowed forms of 'it'\`;
|
||||
}
|
||||
console.log(test());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../batman.js"],"sourcesContent":["export const test = ()=>{\\n return \`I'm \\"annoying\\" \${\\"in case we need to test \\\\\`string\\\\\` escaping.\\"}. Hence this files \\\\'tries\\\\' to include all allowed forms of 'it'\`;\\n}\\nconsole.log(test());\\n"],"names":[],"mappings":";;AAAY,MAAC,IAAI,GAAG,IAAI;AACxB,IAAI,OAAO,CAAC,eAAe,EAAE,8CAA8C,CAAC,iEAAiE,CAAC,CAAC;AAC/I,EAAC;AACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -1,60 +0,0 @@
|
||||
# Snapshot report for `test/live-reload/test.js`
|
||||
|
||||
The actual snapshot is saved in `test.js.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## live-reload
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `␊
|
||||
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);␊
|
||||
const test = ()=>{␊
|
||||
return \`I'm "annoying" ${"in case we need to test \\\`string\\\` escaping."}. Hence this files \\'tries\\' to include all allowed forms of 'it'\`;␊
|
||||
};␊
|
||||
console.log(test());␊
|
||||
␊
|
||||
export { test };␊
|
||||
//# sourceMappingURL=batman-1a5fc364.js.map␊
|
||||
`,
|
||||
fileName: 'batman-1a5fc364.js',
|
||||
map: SourceMap {
|
||||
file: 'batman-1a5fc364.js',
|
||||
mappings: ';;AAAY,MAAC,IAAI,GAAG,IAAI;AACxB,IAAI,OAAO,CAAC,eAAe,EAAE,8CAA8C,CAAC,iEAAiE,CAAC,CAAC;AAC/I,EAAC;AACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;;',
|
||||
names: [],
|
||||
sources: [
|
||||
'../batman.js',
|
||||
],
|
||||
sourcesContent: [
|
||||
`export const test = ()=>{␊
|
||||
return \`I'm "annoying" ${"in case we need to test \\\`string\\\` escaping."}. Hence this files \\'tries\\' to include all allowed forms of 'it'\`;␊
|
||||
}␊
|
||||
console.log(test());␊
|
||||
`,
|
||||
],
|
||||
version: 3,
|
||||
},
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'batman-1a5fc364.js.map',
|
||||
map: undefined,
|
||||
source: '{"version":3,"file":"batman-1a5fc364.js","sources":["../batman.js"],"sourcesContent":["export const test = ()=>{\\n return `I\'m \\"annoying\\" ${\\"in case we need to test \\\\`string\\\\` escaping.\\"}. Hence this files \\\\\'tries\\\\\' to include all allowed forms of \'it\'`;\\n}\\nconsole.log(test());\\n"],"names":[],"mappings":";;AAAY,MAAC,IAAI,GAAG,IAAI;AACxB,IAAI,OAAO,CAAC,eAAe,EAAE,8CAA8C,CAAC,iEAAiE,CAAC,CAAC;AAC/I,EAAC;AACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;;"}',
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `<html><head>␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman-1a5fc364.js" type="module"></script>␊
|
||||
␊
|
||||
␊
|
||||
</body></html>`,
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@@ -1,24 +1,18 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import test from "ava";
|
||||
import {rollup} from "rollup";
|
||||
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";
|
||||
|
||||
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";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test.serial('live-reload', async (t) => {
|
||||
test('live-reload', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
@@ -29,10 +23,10 @@ test.serial('live-reload', async (t) => {
|
||||
})
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
const code = await getCode(bundle);
|
||||
await bundle.close();// Make sure live-reload closes itself
|
||||
debugPrintOutput('live-reload',code);
|
||||
t.snapshot(code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO various parameters
|
||||
|
||||
125
test/multi-entry/__snapshots__/test.js.snap
Normal file
125
test/multi-entry/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,125 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`multi-entry 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "admin/batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.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.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.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.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"index.body.script0.js.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.js';
|
||||
|
||||
function adminDeps(){
|
||||
return "robin!";
|
||||
}
|
||||
|
||||
bootstrap(document.getElementById('root'), adminDeps());
|
||||
//# sourceMappingURL=index.body.script0.js.js.map
|
||||
</script>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
{
|
||||
"code": "const bootstrap = (el,deps = [])=>{
|
||||
el.innerHtml = \`
|
||||
<div>I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this file \\'tries\\' to include all allowed forms of 'it'</div>
|
||||
<div>Deps: \${deps}</div>
|
||||
\`;
|
||||
};
|
||||
|
||||
export { bootstrap as b };
|
||||
//# sourceMappingURL=app.js.map
|
||||
",
|
||||
"fileName": "app.js",
|
||||
"map": SourceMap {
|
||||
"file": "app.js",
|
||||
"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;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../app/app.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const bootstrap = (el,deps = [])=>{
|
||||
el.innerHtml = \`
|
||||
<div>I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this file \\'tries\\' to include all allowed forms of 'it'</div>
|
||||
<div>Deps: \${deps}</div>
|
||||
\`;
|
||||
}
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "app.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"app.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.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"index.body.script.js.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": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">import { b as bootstrap } from './app.js';
|
||||
|
||||
bootstrap(document.getElementById('root'), "<none>");
|
||||
//# sourceMappingURL=index.body.script.js.js.map
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
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";
|
||||
bootstrap(document.getElementById('root'), adminDeps());
|
||||
</script>
|
||||
<script src="./batman.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
# Snapshot report for `test/multi-entry/test.js`
|
||||
|
||||
The actual snapshot is saved in `test.js.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## multi-entry
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `const bootstrap = (el,deps = [])=>{␊
|
||||
el.innerHtml = \`␊
|
||||
<div>I'm "annoying" ${"in case we need to test \\\`string\\\` escaping."}. Hence this file \\'tries\\' to include all allowed forms of 'it'</div>␊
|
||||
<div>Deps: ${deps}</div>␊
|
||||
\`;␊
|
||||
};␊
|
||||
␊
|
||||
export { bootstrap as b };␊
|
||||
//# sourceMappingURL=app-01141b67.js.map␊
|
||||
`,
|
||||
fileName: 'app-01141b67.js',
|
||||
map: SourceMap {
|
||||
file: 'app-01141b67.js',
|
||||
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;;;;',
|
||||
names: [],
|
||||
sources: [
|
||||
'../app/app.js',
|
||||
],
|
||||
sourcesContent: [
|
||||
`export const bootstrap = (el,deps = [])=>{␊
|
||||
el.innerHtml = \`␊
|
||||
<div>I'm "annoying" ${"in case we need to test \\\`string\\\` escaping."}. Hence this file \\'tries\\' to include all allowed forms of 'it'</div>␊
|
||||
<div>Deps: ${deps}</div>␊
|
||||
\`;␊
|
||||
}␊
|
||||
`,
|
||||
],
|
||||
version: 3,
|
||||
},
|
||||
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,
|
||||
fileName: 'app-01141b67.js.map',
|
||||
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;;;;"}',
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `<html><head>␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<div id="root"></div>␊
|
||||
<script type="module">import { b as bootstrap } from './app-01141b67.js';␊
|
||||
␊
|
||||
bootstrap(document.getElementById('root'), "<none>");␊
|
||||
//# sourceMappingURL=index.html.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>␊
|
||||
␊
|
||||
␊
|
||||
</body></html>`,
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@@ -1,36 +1,30 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {resolve, join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import test from "ava";
|
||||
import { rollup } from "rollup";
|
||||
import {debugPrintOutput, getCode} from "../util/test.js";
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
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";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test.serial('multi-entry', async (t) => {
|
||||
test('multi-entry', async () => {
|
||||
const bundle = await rollup({
|
||||
input: {
|
||||
['index']: 'index.html',
|
||||
['admin/index']: 'admin/index.html'
|
||||
['admin/index']: resolve(__dirname,'fixtures','admin/index.html'),
|
||||
},
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('multi-entry',code);
|
||||
t.snapshot(code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO various parameters
|
||||
|
||||
3
test/rewrite-url/__snapshots__/test.js.snap
Normal file
3
test/rewrite-url/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rewrite-url 1`] = `undefined`;
|
||||
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>
|
||||
62
test/rewrite-url/test.js
Normal file
62
test/rewrite-url/test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import {resolve, join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
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('rewrite-url', async () => {
|
||||
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}`;
|
||||
}
|
||||
}),
|
||||
],
|
||||
},{
|
||||
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
|
||||
});
|
||||
expect(out.code).toMatchSnapshot(); // 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
|
||||
// ...
|
||||
2
test/setup.js
Normal file
2
test/setup.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Replace the jest console with the normal one (jest makes console.log too verbose)
|
||||
global.console = await import("node:console");
|
||||
49
test/templating/__snapshots__/test.js.snap
Normal file
49
test/templating/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`handlebars 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../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": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
<meta data-test="a">
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -1,55 +0,0 @@
|
||||
# Snapshot report for `test/templating/test.js`
|
||||
|
||||
The actual snapshot is saved in `test.js.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## handlebars
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
␊
|
||||
export { b };␊
|
||||
//# sourceMappingURL=batman-c7fa228c.js.map␊
|
||||
`,
|
||||
fileName: '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: [
|
||||
'../batman.js',
|
||||
],
|
||||
sourcesContent: [
|
||||
`export const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
`,
|
||||
],
|
||||
version: 3,
|
||||
},
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'batman-c7fa228c.js.map',
|
||||
map: undefined,
|
||||
source: '{"version":3,"file":"batman-c7fa228c.js","sources":["../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: 'index.html',
|
||||
map: undefined,
|
||||
source: `<html><head>␊
|
||||
<meta data-test="a">␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman-c7fa228c.js" type="module"></script>␊
|
||||
␊
|
||||
␊
|
||||
</body></html>`,
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@@ -1,25 +1,18 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import test from "ava";
|
||||
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 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";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test.serial('handlebars', async (t) => {
|
||||
test('handlebars', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.hbs',
|
||||
plugins: [
|
||||
@@ -30,9 +23,9 @@ test.serial('handlebars', async (t) => {
|
||||
})
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('handlebars',code);
|
||||
t.snapshot(code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
|
||||
97
test/url-plugin/__snapshots__/test.js.snap
Normal file
97
test/url-plugin/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,97 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`url-plugin copied-assets 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../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": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
<link rel="icon" href="fb585fdb6db313c9.svg">
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`url-plugin inlined-assets 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../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": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
<link rel="icon" href="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">
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
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 |
@@ -1,105 +0,0 @@
|
||||
# Snapshot report for `test/url-plugin/test.js`
|
||||
|
||||
The actual snapshot is saved in `test.js.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## copied-assets
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
␊
|
||||
export { b };␊
|
||||
//# sourceMappingURL=batman-c7fa228c.js.map␊
|
||||
`,
|
||||
fileName: '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: [
|
||||
'../batman.js',
|
||||
],
|
||||
sourcesContent: [
|
||||
`export const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
`,
|
||||
],
|
||||
version: 3,
|
||||
},
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'batman-c7fa228c.js.map',
|
||||
map: undefined,
|
||||
source: '{"version":3,"file":"batman-c7fa228c.js","sources":["../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: 'index.html',
|
||||
map: undefined,
|
||||
source: `<html><head>␊
|
||||
<link rel="icon" href="fb585fdb6db313c9.svg">␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman-c7fa228c.js" type="module"></script>␊
|
||||
␊
|
||||
␊
|
||||
</body></html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## inlined-assets
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
␊
|
||||
export { b };␊
|
||||
//# sourceMappingURL=batman-c7fa228c.js.map␊
|
||||
`,
|
||||
fileName: '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: [
|
||||
'../batman.js',
|
||||
],
|
||||
sourcesContent: [
|
||||
`export const b = ()=>'batman';␊
|
||||
console.log(b());␊
|
||||
`,
|
||||
],
|
||||
version: 3,
|
||||
},
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'batman-c7fa228c.js.map',
|
||||
map: undefined,
|
||||
source: '{"version":3,"file":"batman-c7fa228c.js","sources":["../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: 'index.html',
|
||||
map: undefined,
|
||||
source: `<html><head>␊
|
||||
<link rel="icon" href="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">␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman-c7fa228c.js" type="module"></script>␊
|
||||
␊
|
||||
␊
|
||||
</body></html>`,
|
||||
},
|
||||
]
|
||||
Binary file not shown.
@@ -1,18 +1,13 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import test from "ava";
|
||||
import { rollup } from "rollup";
|
||||
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";
|
||||
|
||||
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";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -25,40 +20,43 @@ const defaultAssetInclude = [
|
||||
'**/*.(webm|mp4)',// video
|
||||
];
|
||||
|
||||
test.serial('copied-assets', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
limit: 0,// Never inline something
|
||||
}),
|
||||
],
|
||||
describe("url-plugin", ()=>{
|
||||
test('copied-assets', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
limit: 0,// Never inline something
|
||||
}),
|
||||
],
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('copied-assets',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
debugPrintOutput('copied-assets',code);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
|
||||
test.serial('inlined-assets', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
limit: Number.MAX_SAFE_INTEGER,// Always inline things
|
||||
}),
|
||||
]
|
||||
test('inlined-assets', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
limit: Number.MAX_SAFE_INTEGER,// Always inline things
|
||||
}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('inlined-assets',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
debugPrintOutput('inlined-assets',code);
|
||||
t.snapshot(code);
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
|
||||
69
test/util/browser-test.ts
Normal file
69
test/util/browser-test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
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.ts";
|
||||
import {getCode, TestOutput} from "./code-output.ts";
|
||||
import {defaultOutput} from "./default-output.ts";
|
||||
|
||||
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?
|
||||
|
||||
if(output){
|
||||
testOutput.code = await getCode(bundle);
|
||||
}else{
|
||||
const generated = await bundle.generate(defaultOutput);
|
||||
}
|
||||
await bundle.close();
|
||||
|
||||
return testOutput
|
||||
|
||||
}
|
||||
27
test/util/code-output.ts
Normal file
27
test/util/code-output.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type {RollupBuild, OutputOptions, OutputAsset, OutputChunk, SourceMap} from "rollup";
|
||||
import {defaultOutput} from "./default-output.ts";
|
||||
|
||||
export interface TestOutput{
|
||||
code: string,
|
||||
fileName: string,
|
||||
source: any,
|
||||
map: any
|
||||
}
|
||||
|
||||
export const getCode = async (bundle: RollupBuild, outputOptions: OutputOptions = defaultOutput): 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;
|
||||
}
|
||||
11
test/util/default-output.ts
Normal file
11
test/util/default-output.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type {OutputOptions} from "rollup";
|
||||
|
||||
export const defaultOutput : OutputOptions = {
|
||||
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
|
||||
// Prevent hashes from being added (and screw up the snapshots)
|
||||
chunkFileNames: '[name].js',
|
||||
entryFileNames: '[name].js',
|
||||
assetFileNames: '[name].[extname]',
|
||||
};
|
||||
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 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
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
286
test/util/serve-test.ts
Normal file
286
test/util/serve-test.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* 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'
|
||||
|
||||
|
||||
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'>;
|
||||
}>;
|
||||
47
test/watch/__snapshots__/test.js.snap
Normal file
47
test/watch/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,47 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`watch 1`] = `
|
||||
[
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="watched-file.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.js.map
|
||||
",
|
||||
"fileName": "watched-file.js",
|
||||
"map": SourceMap {
|
||||
"file": "watched-file.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.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"watched-file.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;;;;"}",
|
||||
},
|
||||
]
|
||||
`;
|
||||
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
|
||||
|
||||
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, expect} from "@jest/globals";
|
||||
|
||||
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";
|
||||
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
import {pathToFileURL} from "url";
|
||||
import {defaultOutput} from "../util/default-output.ts";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test('watch', async () => {
|
||||
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 output = defaultOutput;
|
||||
const watcher = rollup.watch({
|
||||
input: 'index.html',
|
||||
output: 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);
|
||||
debugPrintOutput('watch',code);
|
||||
|
||||
// Reset the source file
|
||||
await writeFile(path, origContent, {encoding: 'utf-8'});
|
||||
|
||||
// Assert the output is what we exapect;
|
||||
expect(code).toMatchSnapshot();
|
||||
|
||||
watcher
|
||||
},
|
||||
];
|
||||
const log = console.log;
|
||||
|
||||
await new Promise((resolve, reject)=>{
|
||||
watcher.on('event', async (event) => {
|
||||
const {result} = event;
|
||||
switch (event.code) {
|
||||
case "START":
|
||||
log(`WATCH STARTED`);
|
||||
break;
|
||||
case "BUNDLE_START":
|
||||
log(`REBUILDING...`);
|
||||
|
||||
break;
|
||||
case "BUNDLE_END":
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await watcher.close();
|
||||
});
|
||||
@@ -13,7 +13,8 @@
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"allowJs": true
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"exclude": ["dist", "node_modules", "test/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)[]>;
|
||||
}
|
||||
|
||||
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 interface RollupHtmlOptions {
|
||||
@@ -23,6 +28,7 @@ export interface RollupHtmlOptions {
|
||||
* Follows the same logic as rollup's [entryFileNames](https://rollupjs.org/configuration-options/#output-entryfilenames).
|
||||
*/
|
||||
htmlFileNames?: string|((chunkInfo: PreRenderedChunk) => string);
|
||||
|
||||
/**
|
||||
* Transform a source file passed into this plugin to HTML. For example: a handlebars transform
|
||||
* ```
|
||||
@@ -33,6 +39,17 @@ export interface RollupHtmlOptions {
|
||||
*/
|
||||
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.
|
||||
* 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)
|
||||
*/
|
||||
resolve?: ResolveCallback;
|
||||
|
||||
/**
|
||||
* [Pattern](https://github.com/micromatch/picomatch#globbing-features) to include
|
||||
*/
|
||||
include?: FilterPattern;
|
||||
/**
|
||||
* [Pattern](https://github.com/micromatch/picomatch#globbing-features) to exclude
|
||||
*/
|
||||
exclude?: FilterPattern
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user