Compare commits

..

7 Commits

Author SHA1 Message Date
68eb2e3ea1 chore: fix puppeteer/chromium issues in CI during testing
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
2024-03-29 17:52:40 +01:00
141c48730a chore: small update to docs
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-29 16:22:16 +01:00
7f991b2b09 chore: reorganize complicated hooks into their own files
Some checks failed
continuous-integration/drone Build is failing
2024-03-28 23:48:40 +01:00
2d4d097560 wip: reorganize plugin into its hooks 2024-03-27 17:23:02 +01:00
93f99c732f chore: more readable snapshots 2024-03-26 01:21:23 +01:00
4cb8daf908 chore: calculate code coverage 2024-03-23 22:20:11 +01:00
c4878caef3 chore: update dependencies to rollup 4 and remove unneeded dependencies 2024-02-21 17:30:44 +01:00
47 changed files with 2639 additions and 51394 deletions

View File

@ -8,17 +8,19 @@ trigger:
- main - main
steps: steps:
- name: build - name: build
image: node:18-alpine image: node:20-alpine
environment: environment:
NPM_TOKEN: NPM_TOKEN:
from_secret: NPM_TOKEN from_secret: NPM_TOKEN
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true
PUPPETEER_CHROME_ARGS: --disable-software-rasterizer --disable-dev-shm-usage --no-sandbox
commands: commands:
- apk add --no-cache chromium chromium-swiftshader
- npm install -g pnpm - npm install -g pnpm
- apk add --no-cache jq
- echo -e "\n//npm.cerxes.net/:_authToken=$NPM_TOKEN" >> ~/.npmrc - echo -e "\n//npm.cerxes.net/:_authToken=$NPM_TOKEN" >> ~/.npmrc
- cat ~/.npmrc - cat ~/.npmrc
- npm profile get name --registry https://npm.cerxes.net - npm profile get name --registry https://npm.cerxes.net
- pnpm install - pnpm install
- pnpm run build - pnpm run build
- pnpm run ci:test - PUPPETEER_EXECUTABLE_PATH=$(which chromium) pnpm run ci:test
- pnpm publish --tag latest - pnpm publish --tag latest

View File

@ -10,20 +10,23 @@
[![libera manifesto](https://img.shields.io/badge/libera-manifesto-lightgrey.svg)](https://liberamanifesto.com) [![libera manifesto](https://img.shields.io/badge/libera-manifesto-lightgrey.svg)](https://liberamanifesto.com)
# rollup-plugin-html-entry2 # rollup-plugin-html-entry2
| :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. > ⚠️ WARNING: **Experimental-stage** plugin ⚠️
> ---
> Expect bugs and missing features...
> ⚠️ **Renaming** ⚠️\
> Name might change in the future. Considering 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](https://rollupjs.org) plugin that tries to teach Rollup to start from an HTML entry, and the use of (multiple) HTML files in general.
The goal is to include assets referenced by the HTML file into the build-process as to copy/inline where appropriate and The goal is to include assets referenced by the HTML file into the build-process as to copy/inline where appropriate and
optionally optimize them. Without having to seperatly copy them to the output directory. optionally optimize them. Without having to seperatly copy them to the output directory.
When building web-applications a HTML-file is simply the logical entry point into your application. \ When building web-applications a HTML-file is simply the logical entry point into your application. \
Inspired (and forked) by the original [@rollup/plugin-html](https://www.npmjs.com/package/@rollup/plugin-html), Inspired by (and forked from) the original [@rollup/plugin-html](https://www.npmjs.com/package/@rollup/plugin-html),
this plugin will also allow you to transform the source files by any HTML-templating engine such as [handlebars]. this plugin will also allow you to transform the source files by any HTML-templating engine such as [handlebars].
@ -31,7 +34,7 @@ Please see [Supported Output Formats](#supported-output-formats) for information
## Requirements ## Requirements
This plugin requires an [LTS](https://github.com/nodejs/Release) Node version (v18.0.0+) and Rollup v3.?.?+. This plugin aims to support active [LTS](https://github.com/nodejs/Release) versions Node versions v18, v20 + Rollup v3 and up
## Install ## Install
@ -59,7 +62,7 @@ export default {
!! To use 'import x from y' syntax you might need to set `"type": "module"` in your `package.json`. !! To use 'import x from y' syntax you might need to set `"type": "module"` in your `package.json`.
[Javascript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) are the _preferred_ way of writing modern Javascript. [Javascript modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) are the _preferred_ way of writing modern Javascript.
Due to the early stage development of this plugin, old-style CommonJS modules are completely ignored for now. Due to the early stage development of this plugin, old-style CommonJS modules are completely ignored and unsupported for now.
Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#command-line-reference) or the [API](https://www.rollupjs.org/guide/en/#javascript-api). Then call `rollup` either via the [CLI](https://www.rollupjs.org/guide/en/#command-line-reference) or the [API](https://www.rollupjs.org/guide/en/#javascript-api).
@ -83,6 +86,9 @@ async function build() {
input: 'index.hbs', input: 'index.hbs',
plugins: [ plugins: [
html({ html({
include: [
'**/*.(html|hbs)',// html or handlebars
],
transform(src) { transform(src) {
return handlebars.compile(src)({a: 'a'}) return handlebars.compile(src)({a: 'a'})
} }
@ -93,23 +99,29 @@ async function build() {
``` ```
## Supported Output Formats ## Supported Output Formats
By default, this plugin supports the `esm` (`es`). Any other format is currently untested as this plugin is in an early state, see [#status](#status) By default, this plugin supports the `esm` (`es`). Any other format is currently untested as this plugin is in an early state, see [#status](#status)
## Status ## Status
### (Rudimentarily) supported ### (Rudimentarily) supported
- Importing JS via `<script src="..." type="module">` tags - Importing JS via `<script src="..." type="module">` tags
- Importing assets using @rollup/plugin-url (which could use an update TBH)
- 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>`) - Inline scripts (i.e `<script>...</script>`)
- Compatibility with other plugins such as :
- [@rollup/plugin-url](https://www.npmjs.com/package/@rollup/plugin-url) including using it for `<img src"..."/>` references
- [@rollup/plugin-node-resolve](https://www.npmjs.com/package/@rollup/plugin-node-resolve)
- [@rollup/plugin-babel](https://www.npmjs.com/package/@rollup/plugin-babel)
- [@rollup/plugin-commonjs](https://www.npmjs.com/package/@rollup/plugin-commonjs)
- [@rollup/plugin-typescript](https://www.npmjs.com/package/@rollup/plugin-typescript)
- [@rollup/plugin-terser](https://www.npmjs.com/package/@rollup/plugin-terser)
- [rollup-plugin-livereload](https://www.npmjs.com/package/rollup-plugin-livereload)
### Not (yet/properly) supported ### 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) - Sourcemaps (inlined script) (dev-note: we're already including magic-string for this, but do not fully using it yet, neeeds refactoring)
- Plugins importing CSS files - Plugins importing CSS files
- CommonJS (cjs) and IIFI output formats. (Is UMD actually ever used?) - CommonJS (cjs) and IIFI output formats. (Is UMD actually ever used?)
- Overriding which DOM-nodes and resulting URLS to ignore/include (in a clean way) - 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 - Other (various) plugins such as those for HMR etc
- ... - ...
# Contibuting # Contibuting
@ -125,7 +137,7 @@ See also the ToDo-list at the end of the [changelog](./CHANGELOG.md)
Once publicly released, the intent is to move the GIT-repository to github. Until that day though, it exists privately on this gitea server and corresponding npm-registry [npm.cerxes.net](https://npm.cerxes.net).\ Once publicly released, the intent is to move the GIT-repository to github. Until that day though, it exists privately on this gitea server and corresponding npm-registry [npm.cerxes.net](https://npm.cerxes.net).\
TODO: change the links once this happens TODO: change the links once this happens
## Prior work ## Prior work
- [Vite](https://vitejs.dev) seems to have already [done work])(https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/html.ts) to handle HTML in rollup. - [Vite](https://vitejs.dev) seems to have already [done work](https://github.com/vitejs/vite/blob/main/packages/vite/src/node/plugins/html.ts) to handle HTML in rollup.
- [rollup-plugin-html-entry](https://www.npmjs.com/package/rollup-plugin-html-entry) seems to be **dead**. Last version from 2020, there have been many changes to rollup`s plugin capabilities since then - [rollup-plugin-html-entry](https://www.npmjs.com/package/rollup-plugin-html-entry) seems to be **dead**. Last version from 2020, there have been many changes to rollup`s plugin capabilities since then
- [@rollup/plugin-html](https://www.npmjs.com/package/@rollup/plugin-html) is where this project was originally forked from. Its main focus was to generate an HTML to serve the resulting bundle. Which is different from supporting HTML as entry point since it did not resolve assets used in the HTML. Besides the project setup, not much of the original has been kept... - [@rollup/plugin-html](https://www.npmjs.com/package/@rollup/plugin-html) is where this project was originally forked from. Its main focus was to generate an HTML to serve the resulting bundle. Which is different from supporting HTML as entry point since it did not resolve assets used in the HTML. Besides the project setup, not much of the original has been kept...
- [@web/rollup-plugin-html](https://www.npmjs.com/package/@web/rollup-plugin-html) a plugin with similar intentions as this one (in active development anno 2023). - [@web/rollup-plugin-html](https://www.npmjs.com/package/@web/rollup-plugin-html) a plugin with similar intentions as this one (in active development anno 2023).

View File

@ -2,9 +2,14 @@
const babelConfig ={ const babelConfig ={
presets: [ presets: [
["@babel/preset-typescript", { ["@babel/preset-typescript", {
}], }],
], ],
plugins: [ plugins: [
], ],
ignore: [
// Not sure why our babel config is even triggered on this one
// jest testing was running into alot of: `[BABEL] Note: The code generator has deoptimised the styling of node_modules/.pnpm/rollup@4.13.2/node_modules/rollup/dist/es/shared/node-entry.js as it exceeds the max of 500KB.`
"./node_modules/.pnpm/rollup"
]
}; };
export default babelConfig; export default babelConfig;

View File

@ -1,6 +1,6 @@
{ {
"name": "rollup-plugin-html-entry2", "name": "rollup-plugin-html-entry2",
"version": "0.0.6", "version": "0.0.7",
"description": "Teaches rollup how to deal with HTML, allows to use HTML-files as entry-points.", "description": "Teaches rollup how to deal with HTML, allows to use HTML-files as entry-points.",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
@ -24,15 +24,6 @@
"registry": "https://npm.cerxes.net", "registry": "https://npm.cerxes.net",
"access": "public" "access": "public"
}, },
"scripts": {
"build": "rollup -c",
"prerelease": "pnpm build",
"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": "NODE_OPTIONS='--experimental-vm-modules' jest",
"save-test": "NODE_OPTIONS='--experimental-vm-modules' jest -u"
},
"files": [ "files": [
"dist", "dist",
"!dist/**/*.map", "!dist/**/*.map",
@ -47,7 +38,7 @@
"template" "template"
], ],
"peerDependencies": { "peerDependencies": {
"rollup": "^3.0.0" "rollup": "^4.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"rollup": { "rollup": {
@ -55,68 +46,67 @@
} }
}, },
"dependencies": { "dependencies": {
"@rollup/pluginutils": "^5.0.5", "@rollup/pluginutils": "^5.1.0",
"magic-string": "^0.30.5", "magic-string": "^0.30.8",
"parse5": "^7.1.2" "parse5": "^7.1.2"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.23.3", "@babel/core": "^7.24.3",
"@babel/plugin-syntax-import-assertions": "^7.23.3", "@babel/plugin-syntax-import-assertions": "^7.24.1",
"@babel/preset-typescript": "^7.23.3", "@babel/preset-env": "^7.24.3",
"@babel/preset-env": "^7.23.6", "@babel/preset-react": "^7.24.1",
"@babel/preset-react": "^7.23.3", "@babel/preset-typescript": "^7.24.1",
"@jest/globals": "^29.7.0",
"@rollup/plugin-babel": "^6.0.4", "@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-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5", "@rollup/plugin-replace": "^5.0.5",
"@types/node": "^18.18.12", "@rollup/plugin-terser": "^0.4.4",
"@types/react": "^18.2.0", "@rollup/plugin-typescript": "^11.1.6",
"@types/react-dom": "^18.2.0", "@rollup/plugin-url": "^8.0.2",
"@types/jest": "^29.5.12",
"@types/node": "^18.19.26",
"@types/react": "^18.2.69",
"@types/react-dom": "^18.2.22",
"chalk": "^5.3.0", "chalk": "^5.3.0",
"del-cli": "^5.1.0", "del-cli": "^5.1.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"jest": "^29.7.0",
"lint-staged": "^13.3.0", "lint-staged": "^13.3.0",
"mime": "^4.0.1",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"postcss": "^8.4.31", "postcss": "^8.4.38",
"rollup": "^3.29.4", "puppeteer": "^21.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup": "^4.13.2",
"rollup-plugin-delete": "^2.0.0", "rollup-plugin-delete": "^2.0.0",
"rollup-plugin-livereload": "^2.0.5", "rollup-plugin-livereload": "^2.0.5",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"tslib": "^2.6.2",
"tsx": "^4.4.0",
"typescript": "^5.3.2",
"puppeteer": "^21.5.2",
"mime": "^4.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"jest": "^29.7.0",
"@types/jest": "^29.5.12",
"ts-jest": "^29.1.2", "ts-jest": "^29.1.2",
"@jest/globals": "^29.7.0" "typescript": "^5.4.3",
"typedoc": "^0.25.12",
"pretty-format": "^29.7.0"
}, },
"types": "./types/index.d.ts", "types": "./types/index.d.ts",
"jest": { "jest": {
"preset":"ts-jest/presets/default-esm", "preset": "ts-jest/presets/default-esm",
"setupFiles": ["./test/setup.js"] "setupFiles": [
}, "./test/setup.js"
"ava": {
"workerThreads": false,
"files": [
"!**/fixtures/**",
"!**/util/**",
"!**/helpers/**",
"!**/recipes/**",
"!**/types.ts"
], ],
"extensions": { "collectCoverage": true,
"ts": "module", "coveragePathIgnorePatterns": [
"js": true "test/*"
},
"nodeArguments": [
"--experimental-vm-modules"
] ]
},
"scripts": {
"build": "rollup -c",
"prerelease": "pnpm build",
"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": "NODE_OPTIONS='--experimental-vm-modules' jest",
"save-test": "NODE_OPTIONS='--experimental-vm-modules' jest -u",
"docs": "typedoc src/index.ts --sort visibility --sort required-first --sort source-order"
} }
} }

2946
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,462 +1,19 @@
export * from "./plugin/index.ts";
import {html} from "./plugin/index.ts";
export default html;
import type { export type {
Plugin, RewriteUrlCallback, RewriteUrlCallbackContext,
OutputBundle, TransformCallback, RollupHtmlTransformContext,
OutputChunk, } from "./plugin/html.ts"
OutputAsset,
NormalizedOutputOptions,
// ModuleInfo,
ResolvedId,
PreRenderedChunk,
RenderedChunk,
} from 'rollup';
import type { export type {
LoadResult, LoadNodeCallback, LoadResult, LoadReference, LoadedReference, LoadFunction,
RollupHtmlOptions, LoadType,
LoadNodeCallback, RollupHtmlLoadContext, BodyReference, AttributeReference,
LoadReference, BodyReference, AttributeReference, LoadFunction
} from '../types/index.d.ts';
// createFilter function is a utility that constructs a filter function from include/exclude patterns. ResolveCallback, ResolveResult, RollupHtmlResolveContext
import {createFilter} from '@rollup/pluginutils'; } from "./types/index.ts";
// 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";
// TODO should export loader and types (but rework it first)
// nodejs imports (io, path)
import path, { extname, dirname } from "node:path";
import {readFile} from "node:fs/promises"
import posix from "node:path/posix";
import crypto from "node:crypto";
// utilities
import {makeLoader, makeInlineId} from "./loader.ts";
import {HtmlImport, HtmlModule} from "./html-module.ts";
const defaults: RollupHtmlOptions = {
transform: (source: string)=>source,// NO-OP
load: makeLoader(),
resolve: ()=>true,
htmlFileNames: "[name].html",
include: [
'**/*.(html|hbs)',// html or handlebars
]
};
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,
include,
exclude,
} = Object.assign(
{},
defaults,
opts
);
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, {});
// 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,
// 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> }){
if(virtualSources.has(specifier)) return specifier;
if(!filter(specifier)) return;
// Let it be resolved like others (node_modules, project aliases, ..)
const resolved = await this.resolve(specifier, importer, {
skipSelf: true,
...options,
});
if(resolved){
const moduleExt = extname(resolved.id);
const moduleName = specifier.replace(new RegExp(`${moduleExt}\$`),''); // strip extension of the name if any
return {
...resolved,
meta: {
...resolved.meta,
[pluginName]: {
specifier: specifier,
id: resolved.id,
name: moduleName,
imports: [],
assetId: null,
importers: new Set(),
}
}
}
}
}
},
load: {
async handler(id: string) {
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;
// 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(),
}
}
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, {
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 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);
},
// TODO do we need to do the same for chunks?? (what if they're dynamically imported?)
}
},
resolveFileUrl(options){
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}"`;
}
},
banner: {
// Injects a tag so we know where our bundle starts so we can safely ignore other stuff addded via a banner (ie. live-reload)
order:'post',
handler(chunk: RenderedChunk){
if(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
}
}
return '';
}
},
async generateBundle(outputOptions, bundles){
const bundleItems = Object.entries(bundles);
const virtualBundles = new Set<string>();
const facadeToChunk = new Map<string,OutputChunk>();
const htmlResults = new Map<string, {chunk: OutputChunk, htmlModule: HtmlModule}>();
for(const [bundleName, bundle] of bundleItems) {
const chunk = (<OutputChunk>bundle);
if(chunk.facadeModuleId) {
facadeToChunk.set(chunk.facadeModuleId, chunk);
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);
}
}
}
for(const [bundleName, {chunk, htmlModule}] of htmlResults.entries()){
if(htmlModule. document) {
// Delete the placeholder chunk from the bundle and emit an asset file for the HTML instead.
deleteFromBundle(bundleName, bundles);
// Interpret the module and take its default export (TODO: if [NodeJS vm SourceTextModule](https://nodejs.org/api/vm.html#class-vmsourcetextmodule) ever lands, it would be cleaner to use that one instead of directly importing it)
let htmlContents: string;
// Take out the sourceMapUrl if any (it will not have been written yet and tends to cause a crash, we don't need it anyway))
let sanitizedCode = chunk.code;
// Use the modulePrefix to filter out prepended code that is not relevant for us (like live-reload)
const moduleStart = sanitizedCode.indexOf(modulePrefix);
if(moduleStart>=0){
sanitizedCode = sanitizedCode.slice(moduleStart+modulePrefix.length);
}
// Filter out any sourceMapping url that may have been added
const sourceMapRE = /\/\/# sourceMappingURL=(.+)/.exec(sanitizedCode);
if(sourceMapRE){
sanitizedCode = sanitizedCode.slice(0,sourceMapRE.index)+sanitizedCode.slice(sourceMapRE.index+sourceMapRE[0].length);
}
// Encode into a url that we can import(...)
// const importUrl = `data:text/javascript;base64,${Buffer.from(sanitizedCode).toString('base64')}`; // Safer, but unrecognizable if this throws an error
const importUrl = `data:text/javascript,${encodeURIComponent(sanitizedCode)}`; // Due to needed encoding still hard to read, but it might at least be recognizable by the user if it throws an error
try{
let htmlJsModule = await import(importUrl);
htmlContents = htmlJsModule.default;
}catch(err){
throw new Error([
`Failed to parse resulting HTML-module. Most likely this is due to a plugin that has altered the module in such a way that we cannot easely evaluate it in NodeJS.`,
`The code we tried to evaluate:`,
sanitizedCode.split('\n').map(x=>` ${x}`).join('\n'),
`The error we got:`,
err
].join('\n'))
// TODO: We could try to fallback as follows, but the issues are likely to persist in the end result
// for(const htmlImport of htmlModule.imports){
// if(htmlImport.referenceId) {
// const fileName = this.getFileName(htmlImport.referenceId);
// htmlImport.reference.set(fileName);
// }
// }
// serialized = serializeHtml(htmlModule.document);
}
// Inject the inlined chunks (TODO cleanup)
for(const htmlImport of htmlModule.imports){
const importResult = facadeToChunk.get(htmlImport.resolved?.id!);
if(importResult){
if(htmlImport.type === 'chunk') {
htmlContents = htmlContents.replace(htmlImport.placeholder, importResult.code);
}else if(htmlImport.type === 'entryChunk'){
const relPath = posix.relative(dirname(chunk.fileName), importResult.fileName);
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);
}
}
}
this.emitFile({
type: 'asset',
name: htmlModule.name,
fileName: chunk.fileName,
source: htmlContents,
});
}else{
throw new Error('something went wrong...');
}
}
for( const bundleName of virtualBundles.keys()){
deleteFromBundle(bundleName, bundles, false);
}
}
};
}
function deleteFromBundle(bundlename: string, bundle: OutputBundle, deleteMap: boolean = true){
delete bundle[bundlename];
if(deleteMap) {
delete bundle[`${bundlename}.map`];// Also delete any generated map files because they don't make any sense. (TODO: there seems to be no better way to detect this?)
}
}

View File

@ -6,10 +6,10 @@ import type {
LoadReference, LoadReference,
NodeMapping, NodeMapping,
AttributeReference, BodyReference, LoadedReference AttributeReference, BodyReference, LoadedReference
} from '../types/index.d.ts'; } from '../types/loader.d.ts';
import {parseFragment as parseHtmlFragment, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5"; import {parseFragment as parseHtmlFragment, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5";
import {KnownMappings, defaultMapping} from "./loader-mappings.ts"; import {KnownMappings, defaultMapping} from "./mapping.ts";
/** /**
* Makes a unique but human-readable name from a path within a HTML file. * Makes a unique but human-readable name from a path within a HTML file.

View File

@ -2,7 +2,7 @@
import type { import type {
NodeMapping, NodeMapping,
} from '../types/load.d.ts'; } from '../types/loader.ts';
// TODO: specifying ext makes sense for inlined script to convey as what kind of content this should be treated as (i.e. is the inlined script JSX/Typescript/..., or the inlined style CSS/PCSS/SASS. Might be prerrable to support a 'compile-time' ext-attribute on the node) // TODO: specifying ext makes sense for inlined script to convey as what kind of content this should be treated as (i.e. is the inlined script JSX/Typescript/..., or the inlined style CSS/PCSS/SASS. Might be prerrable to support a 'compile-time' ext-attribute on the node)
// but in the case of href/src references, it makes more sense to add it as a meta-data property (conveying how we expect it to be loaded) and the existing filename left as is. // but in the case of href/src references, it makes more sense to add it as a meta-data property (conveying how we expect it to be loaded) and the existing filename left as is.

View File

@ -0,0 +1,142 @@
import type {
OutputBundle,
OutputChunk,
Plugin,
} from 'rollup';
import {dirname} from "node:path";
// nodejs imports (io, path)
import path from "node:path";
import posix from "node:path/posix";
// utilities
import {HtmlModule} from "../../types/html-module.ts";
export interface RewriteUrlCallbackContext {
from: string;
rootPath: string;
}
export type RewriteUrlCallback = (relative: string, context: RewriteUrlCallbackContext) => string|Promise<string>;
export function generateBundle({
virtualSources,
pluginName, modulePrefix, rewriteUrl
}: {
virtualSources: Map<string, string>,
pluginName: string,
modulePrefix: string,
rewriteUrl?: RewriteUrlCallback
}): Plugin['generateBundle']{
return {
async handler(outputOptions, bundles){
const bundleItems = Object.entries(bundles);
const virtualBundles = new Set<string>();
const facadeToChunk = new Map<string,OutputChunk>();
const htmlResults = new Map<string, {chunk: OutputChunk, htmlModule: HtmlModule}>();
for(const [bundleName, bundle] of bundleItems) {
const chunk = (<OutputChunk>bundle);
if(chunk.facadeModuleId) {
facadeToChunk.set(chunk.facadeModuleId, chunk);
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);
}
}
}
for(const [bundleName, {chunk, htmlModule}] of htmlResults.entries()){
if(htmlModule. document) {
// Delete the placeholder chunk from the bundle and emit an asset file for the HTML instead.
deleteFromBundle(bundleName, bundles);
// Interpret the module and take its default export (TODO: if [NodeJS vm SourceTextModule](https://nodejs.org/api/vm.html#class-vmsourcetextmodule) ever lands, it would be cleaner to use that one instead of directly importing it)
let htmlContents: string;
// Take out the sourceMapUrl if any (it will not have been written yet and tends to cause a crash, we don't need it anyway))
let sanitizedCode = chunk.code;
// Use the modulePrefix to filter out prepended code that is not relevant for us (like live-reload)
const moduleStart = sanitizedCode.indexOf(modulePrefix);
if(moduleStart>=0){
sanitizedCode = sanitizedCode.slice(moduleStart+modulePrefix.length);
}
// Filter out any sourceMapping url that may have been added
const sourceMapRE = /\/\/# sourceMappingURL=(.+)/.exec(sanitizedCode);
if(sourceMapRE){
sanitizedCode = sanitizedCode.slice(0,sourceMapRE.index)+sanitizedCode.slice(sourceMapRE.index+sourceMapRE[0].length);
}
// Encode into a url that we can import(...)
// const importUrl = `data:text/javascript;base64,${Buffer.from(sanitizedCode).toString('base64')}`; // Safer, but unrecognizable if this throws an error
const importUrl = `data:text/javascript,${encodeURIComponent(sanitizedCode)}`; // Due to needed encoding still hard to read, but it might at least be recognizable by the user if it throws an error
try{
let htmlJsModule = await import(importUrl);
htmlContents = htmlJsModule.default;
}catch(err){
throw new Error([
`Failed to parse resulting HTML-module. Most likely this is due to a plugin that has altered the module in such a way that we cannot easely evaluate it in NodeJS.`,
`The code we tried to evaluate:`,
sanitizedCode.split('\n').map(x=>` ${x}`).join('\n'),
`The error we got:`,
err
].join('\n'))
// TODO: We could try to fallback as follows, but the issues are likely to persist in the end result
// for(const htmlImport of htmlModule.imports){
// if(htmlImport.referenceId) {
// const fileName = this.getFileName(htmlImport.referenceId);
// htmlImport.reference.set(fileName);
// }
// }
// serialized = serializeHtml(htmlModule.document);
}
// Inject the inlined chunks (TODO cleanup)
for(const htmlImport of htmlModule.imports){
const importResult = facadeToChunk.get(htmlImport.resolved?.id!);
if(importResult){
if(htmlImport.type === 'chunk') {
htmlContents = htmlContents.replace(htmlImport.placeholder, importResult.code);
}else if(htmlImport.type === 'entryChunk'){
const relPath = posix.relative(dirname(chunk.fileName), importResult.fileName);
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);
}
}
}
this.emitFile({
type: 'asset',
name: htmlModule.name,
fileName: chunk.fileName,
source: htmlContents,
});
}else{
throw new Error('something went wrong...');
}
}
for( const bundleName of virtualBundles.keys()){
deleteFromBundle(bundleName, bundles, false);
}
}
}
}
function deleteFromBundle(bundlename: string, bundle: OutputBundle, deleteMap: boolean = true){
delete bundle[bundlename];
if(deleteMap) {
delete bundle[`${bundlename}.map`];// Also delete any generated map files because they don't make any sense. (TODO: there seems to be no better way to detect this?)
}
}

View File

@ -0,0 +1,3 @@
export * from "./resolve-id.ts";
export * from "./transform.ts";
export * from "./generate-bundle.ts";

View File

@ -0,0 +1,51 @@
import type {
Plugin,
} from 'rollup';
import {createFilter} from '@rollup/pluginutils';
import {extname} from "node:path";
export function resolveId({
virtualSources,
filter,
pluginName
}: {
virtualSources: Map<string, string>,
filter: ReturnType<typeof createFilter>,
pluginName: string
}): Plugin['resolveId']{
return {
handler: async function (specifier,
importer,
options){
if(virtualSources.has(specifier)) return specifier; // Resolve virtual sources we added
if(!filter(specifier)) return;
// Let it be resolved like others (node_modules, project aliases, ..)
const resolved = await this.resolve(specifier, importer, {
skipSelf: true,
...options,
});
if(resolved){
const moduleExt = extname(resolved.id);
const moduleName = specifier.replace(new RegExp(`${moduleExt}\$`),''); // strip extension of the name if any
return {
...resolved,
meta: {
...resolved.meta,
[pluginName]: {
specifier: specifier,
id: resolved.id,
name: moduleName,
imports: [],
assetId: null,
importers: new Set(),
}
}
}
}
}
}
}

View File

@ -0,0 +1,200 @@
import type {
Plugin,
} from 'rollup';
import {createFilter} from '@rollup/pluginutils';
import {extname} from "node:path";
// 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 posix from "node:path/posix";
import crypto from "node:crypto";
import type {LoadNodeCallback, LoadFunction, LoadResult} from "../../types/loader.ts";
// utilities
import {makeInlineId} from "../../loader/loader.ts";
import {HtmlImport} from "../../types/html-module.ts";
export interface RollupHtmlTransformContext {
id?: string;
// bundle: OutputBundle;
// files: Record<string, (OutputChunk | OutputAsset)[]>;
}
export type TransformCallback = (source: string, transformContext: RollupHtmlTransformContext) => string|Promise<string>;
export function transform({
virtualSources,
filter,
entryNames,
pluginName,
transform,
load,
}: {
virtualSources: Map<string, string>,
filter: ReturnType<typeof createFilter>,
entryNames: Map<string,string>,
pluginName: string,
transform: TransformCallback,
load: LoadNodeCallback
}): Plugin['transform']{
return {
order: 'pre',
async handler(...args){
const [code, id] = args;
if (!filter(id)) return;
// 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(),
}
}
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,
};
}
}
}

222
src/plugin/html.ts Normal file
View File

@ -0,0 +1,222 @@
import type {
Plugin,
OutputBundle,
PreRenderedChunk,
RenderedChunk,
} from 'rollup';
// createFilter function is a utility that constructs a filter function from include/exclude patterns.
import {createFilter} from '@rollup/pluginutils';
import type {FilterPattern} from "@rollup/pluginutils";
// utilities
import {makeLoader} from "../loader/loader.ts";
import type {LoadNodeCallback} from "../types/loader.ts";
import type {ResolveCallback} from "../types/resolve.d.ts";
import * as hooks from "./hooks/index.ts";
import type {TransformCallback} from "./hooks/transform.ts";
import type {RewriteUrlCallback} from "./hooks/generate-bundle.ts";
export type {TransformCallback,RollupHtmlTransformContext} from "./hooks/transform.ts";
export type {RewriteUrlCallback, RewriteUrlCallbackContext} from "./hooks/generate-bundle.ts";
const modulePrefix = `// <html-module>`;
const moduleSuffix = `// </html-module>`;
/**
* Intantiates a Rollup plugin to transform HTML files.,
*
*/
export function html(opts: {
publicPath?: string;
/**
* 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
* ```javascript
* transform(source){
* return handlebars.compile(source)({myVar:'example'})
* }
* ```
*/
transform?: TransformCallback;
/**
* Optional callback to rewrite how resources are referenced in the output HTML.
* For example to rewrite urls to paths from the root of your website:
* ```javascript
* rewriteUrl(relative, {rootPath, from}){
* return `/${rootPath}`;
* }
* ```
*/
rewriteUrl?: RewriteUrlCallback;
/**
* Detect which references (`<a href="...">`, `<img src="...">`, `<script>...</script>`,...) to resolve from
* an HTML node.
* This rarely needs to be overloaded, but can be used to support non-native attributes used by custom-elements.
*
* Return false to skip any further processing on this node. \
* Use the load function to add any resources from this node, and replace the import with a placeholder so the plugin
* knows where to inject the end result
*/
load?: LoadNodeCallback;
/**
* Callback to filter which references actually need to be resolved. Here you can filter out:
* - Links to extensions that don't need to be handled through rollup
* - Resources that are external to the app (for example non-relative paths)
* - Page navigation within the app
*
* 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.
* For example: `['**\/*.(html|hbs)']` to include html and handblebars files.
*
* Includes only .html by default
*/
include?: FilterPattern;
/**
* [Pattern](https://github.com/micromatch/picomatch#globbing-features) to exclude
*/
exclude?: FilterPattern
} = {}): Plugin {
const {
publicPath,
transform = (source: string)=>source, // NO-OP default
rewriteUrl,
load = makeLoader(),
htmlFileNames = "[name].html",
resolve = ()=>true,
include = [
'**/*.(html)',// html
],
exclude,
} = opts;
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, {});
// 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,
// Track html entrypoints
buildStart(options){
entryNames.clear();
const entries = Object.entries(typeof(options.input)==='object'?options.input:{[options.input]:[options.input]})
.map(([k,v])=>[v,k]);
for(const [k,v] of entries){
entryNames.set(k,v);
}
},
resolveId: hooks.resolveId(({
virtualSources,
filter,
pluginName
})),
load: {
async handler(id: string) {
if (virtualSources.has(id)) return virtualSources.get(id);
}
},
transform: hooks.transform({
virtualSources,
filter,
entryNames,
pluginName,
transform,
load
}),
outputOptions(options){
return {
...options,
entryFileNames: (chunkInfo)=>{
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);
},
// TODO do we need to do the same for chunks?? (what if they're dynamically imported?)
}
},
resolveFileUrl(options){
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}"`;
}
},
banner: {
// Injects a tag so we know where our bundle starts so we can safely ignore other stuff addded via a banner (ie. live-reload)
order:'post',
handler(chunk: RenderedChunk){
if(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
}
}
return '';
}
},
generateBundle: hooks.generateBundle({
virtualSources,
pluginName,
modulePrefix,
rewriteUrl,
})
};
}

1
src/plugin/index.ts Normal file
View File

@ -0,0 +1 @@
export {html} from "./html.ts";

View File

@ -9,7 +9,7 @@ import type {
import type { import type {
LoadedReference LoadedReference
} from "../types/load.d.ts"; } from "./loader.d.ts";
import type {DefaultTreeAdapterMap} from "parse5"; import type {DefaultTreeAdapterMap} from "parse5";
// Internal type // Internal type

3
src/types/index.ts Normal file
View File

@ -0,0 +1,3 @@
export type * from "./html-module.ts";
export type * from "./loader.ts";
export type * from "./resolve.ts";

View File

@ -15,7 +15,7 @@ export type AttributeReference = {
}; };
export type BodyReference = { export type BodyReference = {
/** /**
* Indiciate this is an inlined reference (node body) * Indicate this is an inlined reference (node body)
*/ */
body: boolean; body: boolean;
/** /**

View File

@ -1,78 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`basic inline-script 1`] = ` exports[`basic inline-script 1`] = `
[ ###############
{ # script.html #
"code": undefined, ###############
"fileName": "script.body.script.js-e3b82208.js.map", <html><head>
"map": undefined, <!-- TODO:
"source": "{"version":3,"file":"script.body.script.js-e3b82208.js","sources":["../batman.js","../script.html.body.script.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n","\\n import {b} from \\"./batman.js\\";\\n document.body.appendChild(\\n document.createTextNode(\`Inline script including \${b()}\`)\\n );\\n "],"names":[],"mappings":"AAAO,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;ACCJ,QAAQ,CAAC,IAAI,CAAC,WAAW;AACrC,gBAAgB,QAAQ,CAAC,cAAc,CAAC,CAAC,wBAAwB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,aAAa"}", EMPTY FAVICON, PREVENTS THE EXTRA REQUEST, but should be ignored by the rollup plugin:
}, <link rel="icon" href="data:,">
{ ONCE ADDED: change the snapshot to include full output (of browser test + code, instead of just .code)
"code": undefined, -->
"fileName": "script.html",
"map": undefined,
"source": "<html><head>
</head> </head>
<body> <body>
<script type="module">const b = ()=>'batman'; <script type="module">const b = ()=>'batman';
console.log(b()); console.log(b());
document.body.appendChild( document.body.appendChild(
document.createTextNode(\`Inline script including \${b()}\`) document.createTextNode(\`Inline script including \${b()}\`)
); );
//# sourceMappingURL=script.body.script.js-e3b82208.js.map //# sourceMappingURL=script.body.script.js.js.map
</script> </script>
</body></html>", </body></html>
}, ################################
] # script.body.script.js.js.map #
################################
{"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"}
#################
# RENDERED HTML #
#################
<html><head>
<!-- TODO:
EMPTY FAVICON, PREVENTS THE EXTRA REQUEST, but should be ignored by the rollup plugin:
<link rel="icon" href="data:,">
ONCE ADDED: change the snapshot to include full output (of browser test + code, instead of just .code)
-->
</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>
Inline script including batman</body></html>
###########
# CONSOLE #
###########
[log] batman
[error] Failed to load resource: the server responded with a status of 404 (Not Found)
#############
# RESPONSES #
#############
200 http://localhost/script.html
404 http://localhost/favicon.ico
`; `;
exports[`basic simple 1`] = ` exports[`basic simple 1`] = `
[ ##############
{ # index.html #
"code": "const b = ()=>'batman'; ##############
console.log(b()); <html><head>
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> </head>
<body> <body>
<script src="batman-c7fa228c.js" type="module"></script> <script src="batman.js" type="module"></script>
</body></html>", </body></html>
}, #############
] # batman.js #
#############
const b = ()=>'batman';
console.log(b());
export { b };
//# sourceMappingURL=batman.js.map
#################
# batman.js.map #
#################
{"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;;;;"}
#################
# RENDERED HTML #
#################
<html><head>
</head>
<body>
<script src="batman.js" type="module"></script>
</body></html>
###########
# CONSOLE #
###########
[log] batman
#############
# RESPONSES #
#############
200 http://localhost/index.html
200 http://localhost/batman.js
200 http://localhost/favicon.ico
`; `;

View File

@ -1,5 +1,10 @@
<html> <html>
<head> <head>
<!-- TODO:
EMPTY FAVICON, PREVENTS THE EXTRA REQUEST, but should be ignored by the rollup plugin:
<link rel="icon" href="data:,">
ONCE ADDED: change the snapshot to include full output (of browser test + code, instead of just .code)
-->
</head> </head>
<body> <body>
<script type="module"> <script type="module">

View File

@ -2,46 +2,38 @@ import {join, dirname} from "node:path";
import {test, expect} from "@jest/globals"; import {test, expect} from "@jest/globals";
import { rollup } from "rollup"; import {runBrowserTest, defaultOutput, serializer} from "../util/index.ts";
import {debugPrintOutput, getCode} from "../util/index.ts";
import html from "../../src/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"; import {fileURLToPath} from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(join(__dirname, 'fixtures')); process.chdir(join(__dirname, 'fixtures'));
describe("basic", ()=> { describe("basic", ()=> {
expect.addSnapshotSerializer(serializer);
test('simple', async () => { test('simple', async () => {
const bundle = await rollup({ const out = await runBrowserTest({
input: 'index.html', input: 'index.html',
plugins: [ plugins: [
html({}), html({}),
] ]
}); }, {
const code = await getCode(bundle, output); }, defaultOutput);
await bundle.close(); expect(out).toMatchSnapshot();
debugPrintOutput('simple', code);
expect(code).toMatchSnapshot();
}); });
test('inline-script', async () => { test('inline-script', async () => {
const bundle = await rollup({ const out = await runBrowserTest({
input: 'script.html', input: 'script.html',
plugins: [ plugins: [
html({}), html({}),
] ]
}); }, {
const code = await getCode(bundle, output); path: 'script.html',
await bundle.close(); }, defaultOutput);
debugPrintOutput('inline-script', code); expect(out).toMatchSnapshot();
expect(code).toMatchSnapshot();
}); });
// TODO various parameters // TODO various parameters
@ -49,5 +41,4 @@ describe("basic", ()=> {
// - sourcemap: inline, false, (and the various exotic sourcemap options) // - sourcemap: inline, false, (and the various exotic sourcemap options)
// Watch mode tests would be its own dir // Watch mode tests would be its own dir
// ... // ...
}); });

View File

@ -1,65 +1,10 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`evaluated-web-bundle 1`] = ` exports[`evaluated-web-bundle 1`] = `
{ ##############
"code": [ # index.html #
{ ##############
"code": "async function app({root}){ <!DOCTYPE html><html lang="en"><head>
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)
);
}
}
export { app };
//# sourceMappingURL=app.js.map
",
"fileName": "app.js",
"map": SourceMap {
"file": "app.js",
"mappings": "AAAO,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AACjC;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD;AACA,IAAI,IAAI,IAAI,KAAK,IAAI,MAAM,CAAC;AAC5B,QAAQ,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACpC,QAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAClD,QAAQ,IAAI,CAAC,SAAS,GAAG,CAAC,mCAAmC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChF,QAAQ,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM;AACzC,YAAY,UAAU,CAAC,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;AACzC,SAAS,CAAC;AACV,KAAK;AACL;;;;",
"names": [],
"sources": [
"../app.mjs",
],
"sourcesContent": [
"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)
);
}
}
",
],
"version": 3,
},
"source": undefined,
},
{
"code": undefined,
"fileName": "app.js.map",
"map": undefined,
"source": "{"version":3,"file":"app.js","sources":["../app.mjs"],"sourcesContent":["export async function app({root}){\\n\\n const states = ['started', 'tick', 'ended'];\\n\\n for(let state of states){\\n const text = \`App \${state}\`;\\n console.log(\`Test my sourcemap: \${text}\`);\\n root.innerHTML = \`<div style=\\"align-self: center\\"><b>\${text}</b></div>\`;\\n await new Promise((resolve,reject)=>\\n setTimeout(()=>resolve(), 10)\\n );\\n }\\n}\\n"],"names":[],"mappings":"AAAO,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AACjC;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD;AACA,IAAI,IAAI,IAAI,KAAK,IAAI,MAAM,CAAC;AAC5B,QAAQ,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACpC,QAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAClD,QAAQ,IAAI,CAAC,SAAS,GAAG,CAAC,mCAAmC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChF,QAAQ,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM;AACzC,YAAY,UAAU,CAAC,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;AACzC,SAAS,CAAC;AACV,KAAK;AACL;;;;"}",
},
{
"code": undefined,
"fileName": "index.html",
"map": undefined,
"source": "<!DOCTYPE html><html lang="en"><head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title> <title>
Test bundle! Test bundle!
@ -69,17 +14,42 @@ export { app };
<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"> <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> <title>I'm cool!</title>
</head> </head>
<body> <body>
<div id="root">Here the app should load!</div> <div id="root">Here the app should load!</div>
<script src="index.js" type="module"></script> <script src="index.js" type="module"></script>
</body></html>", </body></html>
}, ##########
{ # app.js #
"code": "// Dynamically loads libraries and bootstraps the application ##########
(async ()=>{ 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)
);
}
}
export { app };
//# sourceMappingURL=app.js.map
##############
# app.js.map #
##############
{"version":3,"file":"app.js","sources":["../app.mjs"],"sourcesContent":["export async function app({root}){\\n\\n const states = ['started', 'tick', 'ended'];\\n\\n for(let state of states){\\n const text = \`App \${state}\`;\\n console.log(\`Test my sourcemap: \${text}\`);\\n root.innerHTML = \`<div style=\\"align-self: center\\"><b>\${text}</b></div>\`;\\n await new Promise((resolve,reject)=>\\n setTimeout(()=>resolve(), 10)\\n );\\n }\\n}\\n"],"names":[],"mappings":"AAAO,eAAe,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;AACjC;AACA,IAAI,MAAM,MAAM,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD;AACA,IAAI,IAAI,IAAI,KAAK,IAAI,MAAM,CAAC;AAC5B,QAAQ,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AACpC,QAAQ,OAAO,CAAC,GAAG,CAAC,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAClD,QAAQ,IAAI,CAAC,SAAS,GAAG,CAAC,mCAAmC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChF,QAAQ,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM;AACzC,YAAY,UAAU,CAAC,IAAI,OAAO,EAAE,EAAE,EAAE,CAAC;AACzC,SAAS,CAAC;AACV,KAAK;AACL;;;;"}
############
# index.js #
############
// Dynamically loads libraries and bootstraps the application
(async ()=>{
// Add a loader here if any // Add a loader here if any
const root = document.getElementById('root'); const root = document.getElementById('root');
if(root) root.innerHTML= \`<div style="align-self: center">My app has loaded!!</div>\`; if(root) root.innerHTML= \`<div style="align-self: center">My app has loaded!!</div>\`;
@ -104,66 +74,17 @@ export { app };
}catch(err){ }catch(err){
console.error(err); console.error(err);
} }
})(); })();
//# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map
",
"fileName": "index.js",
"map": SourceMap {
"file": "index.js",
"mappings": "AAAA;AACA,CAAC,UAAU;AACX;AACA,IAAI,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAC;AAChD,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,yDAAyD,CAAC,CAAC;AACzF;AACA,IAAI,IAAI;AACR;AACA,QAAQ,MAAM;AACd,YAAY,SAAS;AACrB,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AAC9B,YAAY,OAAO,UAAW,CAAC;AAC/B,SAAS,CAAC,CAAC;AACX;AACA,QAAQ,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAClD;AACA;AACA,QAAQ,GAAG,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE;AAC9C,YAAY,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;AACjG,SAAS;AACT;AACA;AACA,QAAQ,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,KAAK,MAAM,GAAG,CAAC;AACf,QAAQ,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC3B,KAAK;AACL,CAAC",
"names": [],
"sources": [
"../index.mjs",
],
"sourcesContent": [
"// 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 # index.js.map #
const [ ################
appModule, {"version":3,"file":"index.js","sources":["../index.mjs"],"sourcesContent":["// Dynamically loads libraries and bootstraps the application\\n(async ()=>{\\n // Add a loader here if any\\n const root = document.getElementById('root')\\n if(root) root.innerHTML= \`<div style=\\"align-self: center\\">My app has loaded!!</div>\`;\\n\\n try {\\n // Load app\\n const [\\n appModule,\\n ] = await Promise.all([\\n import(\\"./app.mjs\\"),\\n ]);\\n\\n console.log(\\"Bootstrapped, ready to go!\\");\\n\\n // Wait for DOM to be ready\\n if(document.readyState === 'loading') {\\n await new Promise((resolve)=>document.addEventListener('DOMContentLoaded', resolve));\\n }\\n\\n // Start the app!\\n await appModule.app({root});\\n }catch(err){\\n console.error(err);\\n }\\n})()\\n"],"names":[],"mappings":"AAAA;AACA,CAAC,UAAU;AACX;AACA,IAAI,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAC;AAChD,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,yDAAyD,CAAC,CAAC;AACzF;AACA,IAAI,IAAI;AACR;AACA,QAAQ,MAAM;AACd,YAAY,SAAS;AACrB,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AAC9B,YAAY,OAAO,UAAW,CAAC;AAC/B,SAAS,CAAC,CAAC;AACX;AACA,QAAQ,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAClD;AACA;AACA,QAAQ,GAAG,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE;AAC9C,YAAY,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;AACjG,SAAS;AACT;AACA;AACA,QAAQ,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,KAAK,MAAM,GAAG,CAAC;AACf,QAAQ,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC3B,KAAK;AACL,CAAC"}
] = await Promise.all([ #################
import("./app.mjs"), # RENDERED HTML #
]); #################
<html lang="en"><head>
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);
}
})()
",
],
"version": 3,
},
"source": undefined,
},
{
"code": undefined,
"fileName": "index.js.map",
"map": undefined,
"source": "{"version":3,"file":"index.js","sources":["../index.mjs"],"sourcesContent":["// Dynamically loads libraries and bootstraps the application\\n(async ()=>{\\n // Add a loader here if any\\n const root = document.getElementById('root')\\n if(root) root.innerHTML= \`<div style=\\"align-self: center\\">My app has loaded!!</div>\`;\\n\\n try {\\n // Load app\\n const [\\n appModule,\\n ] = await Promise.all([\\n import(\\"./app.mjs\\"),\\n ]);\\n\\n console.log(\\"Bootstrapped, ready to go!\\");\\n\\n // Wait for DOM to be ready\\n if(document.readyState === 'loading') {\\n await new Promise((resolve)=>document.addEventListener('DOMContentLoaded', resolve));\\n }\\n\\n // Start the app!\\n await appModule.app({root});\\n }catch(err){\\n console.error(err);\\n }\\n})()\\n"],"names":[],"mappings":"AAAA;AACA,CAAC,UAAU;AACX;AACA,IAAI,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,MAAM,EAAC;AAChD,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,yDAAyD,CAAC,CAAC;AACzF;AACA,IAAI,IAAI;AACR;AACA,QAAQ,MAAM;AACd,YAAY,SAAS;AACrB,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;AAC9B,YAAY,OAAO,UAAW,CAAC;AAC/B,SAAS,CAAC,CAAC;AACX;AACA,QAAQ,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAClD;AACA;AACA,QAAQ,GAAG,QAAQ,CAAC,UAAU,KAAK,SAAS,EAAE;AAC9C,YAAY,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,GAAG,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,OAAO,CAAC,CAAC,CAAC;AACjG,SAAS;AACT;AACA;AACA,QAAQ,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACpC,KAAK,MAAM,GAAG,CAAC;AACf,QAAQ,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AAC3B,KAAK;AACL,CAAC"}",
},
],
"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"> <meta charset="UTF-8">
<title> <title>
Test bundle! Test bundle!
@ -173,18 +94,24 @@ export { app };
<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"> <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> <title>I'm cool!</title>
</head> </head>
<body> <body>
<div id="root"><div style="align-self: center"><b>App ended</b></div></div> <div id="root"><div style="align-self: center"><b>App ended</b></div></div>
<script src="index.js" type="module"></script> <script src="index.js" type="module"></script>
</body></html>", </body></html>
"requestsFailed": [], ###########
"responses": [ # CONSOLE #
"200 http://localhost/index.html", ###########
"200 http://localhost/index.js", [log] Bootstrapped, ready to go!
"200 http://localhost/app.js", [log] Test my sourcemap: App started
], [log] Test my sourcemap: App tick
} [log] Test my sourcemap: App ended
#############
# RESPONSES #
#############
200 http://localhost/index.html
200 http://localhost/index.js
200 http://localhost/app.js
`; `;

View File

@ -8,6 +8,7 @@ import {runBrowserTest} from "../util/browser-test.ts";
import {fileURLToPath} from "node:url"; import {fileURLToPath} from "node:url";
import handlebars from "handlebars"; import handlebars from "handlebars";
import {serializer} from "../util/index.ts";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(join(__dirname, 'fixtures')); process.chdir(join(__dirname, 'fixtures'));
@ -20,11 +21,15 @@ const defaultAssetInclude = [
]; ];
test('evaluated-web-bundle', async ()=>{ test('evaluated-web-bundle', async ()=>{
expect.addSnapshotSerializer(serializer);
const out = await runBrowserTest({ const out = await runBrowserTest({
input: 'index.hbs', input: 'index.hbs',
treeshake: 'smallest', treeshake: 'smallest',
plugins: [ plugins: [
html({ html({
include: [
'**/*.(html|hbs)',// html or handlebars
],
transform(src) { transform(src) {
return handlebars.compile(src)({ return handlebars.compile(src)({
head: `<title>I'm cool!</title>` head: `<title>I'm cool!</title>`

View File

@ -1,69 +1,32 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`js-import 1`] = ` exports[`js-import 1`] = `
[ ############
{ # index.js #
"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"; ############
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> const html = \`<html><head>
<link rel="icon" href="\${asset0}"> <link rel="icon" href="\${asset0}">
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor--> <!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->
<!-- <link rel="stylesheet" href="./joker.css">--> <!-- <link rel="stylesheet" href="./joker.css">-->
</head> </head>
<body> <body>
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) --> <!-- 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>--> <!--<script src="./batman.js" type="module"></script>-->
</body></html>\`; </body></html>\`;
function render(){ function render(){
return html; return html;
} }
export { render }; export { render };
//# sourceMappingURL=index-3d1ca61b.js.map //# sourceMappingURL=index.js.map
",
"fileName": "index-3d1ca61b.js",
"map": SourceMap {
"file": "index-3d1ca61b.js",
"mappings": "AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACRN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;",
"names": [],
"sources": [
"../icon.svg",
"../index.html",
"../index.js",
],
"sourcesContent": [
"export default "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E"",
"<html>
<head>
<link rel="icon" href="./icon.svg">
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->
<!-- <link rel="stylesheet" href="./joker.css">-->
</head>
<body>
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->
<!--<script src="./batman.js" type="module"></script>-->
</body>
</html>
",
"import html from "./index.html"
export function render(){ ################
return html; # index.js.map #
} ################
", {"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;;;;"}
],
"version": 3,
},
"source": undefined,
},
{
"code": undefined,
"fileName": "index-3d1ca61b.js.map",
"map": undefined,
"source": "{"version":3,"file":"index-3d1ca61b.js","sources":["../icon.svg","../index.html","../index.js"],"sourcesContent":["export default \\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E\\"","<html>\\n <head>\\n <link rel=\\"icon\\" href=\\"./icon.svg\\">\\n <!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->\\n<!-- <link rel=\\"stylesheet\\" href=\\"./joker.css\\">-->\\n </head>\\n <body>\\n <!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->\\n <!--<script src=\\"./batman.js\\" type=\\"module\\"></script>-->\\n </body>\\n</html>\\n","import html from \\"./index.html\\"\\n\\nexport function render(){\\n return html;\\n}\\n"],"names":[],"mappings":"AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACRN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;"}",
},
]
`; `;

View File

@ -3,16 +3,11 @@ import {test, expect} from "@jest/globals";
import { rollup } from "rollup"; import { rollup } from "rollup";
import {debugPrintOutput, getCode} from "../util/index.ts"; import {debugPrintOutput, getCode, serializer} from "../util/index.ts";
import html from "../../src/index.ts"; import html from "../../src/index.ts";
import handlebars from "handlebars"; import handlebars from "handlebars";
const output = {
dir: 'output', // Output all files
format: 'es', // iifi and cjs should be added to tests
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
};
import {fileURLToPath} from "node:url"; import {fileURLToPath} from "node:url";
import urlPlugin from "@rollup/plugin-url"; import urlPlugin from "@rollup/plugin-url";
@ -27,6 +22,7 @@ const defaultAssetInclude = [
test('js-import', async () => { test('js-import', async () => {
expect.addSnapshotSerializer(serializer);
const bundle = await rollup({ const bundle = await rollup({
input: 'index.js', input: 'index.js',
plugins: [ plugins: [
@ -39,9 +35,9 @@ test('js-import', async () => {
}), }),
] ]
}); });
const code = await getCode(bundle, output); const code = await getCode(bundle);
debugPrintOutput('js-import',code); debugPrintOutput('js-import',code);
expect(code).toMatchSnapshot(); expect({code}).toMatchSnapshot();
}); });

File diff suppressed because one or more lines are too long

View File

@ -15,6 +15,7 @@ import {runBrowserTest} from "../util/browser-test.ts";
import {fileURLToPath} from "node:url"; import {fileURLToPath} from "node:url";
import handlebars from "handlebars"; import handlebars from "handlebars";
import {serializer} from "../util/index.ts";
// import {debugPrintOutput, getCode, runBrowserTest} from "../util/index.ts"; // import {debugPrintOutput, getCode, runBrowserTest} from "../util/index.ts";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(join(__dirname, 'fixtures')); process.chdir(join(__dirname, 'fixtures'));
@ -29,11 +30,15 @@ const defaultAssetInclude = [
jest.setTimeout(30*1000);// Bundling react + typescript is getting heavy jest.setTimeout(30*1000);// Bundling react + typescript is getting heavy
test('web-bundle', async () => { test('web-bundle', async () => {
expect.addSnapshotSerializer(serializer);
const out = await runBrowserTest({ const out = await runBrowserTest({
input: 'index.hbs', input: 'index.hbs',
treeshake: 'smallest', treeshake: 'smallest',
plugins: [ plugins: [
html({ html({
include: [
'**/*.(html|hbs)',// html or handlebars
],
transform(src) { transform(src) {
return handlebars.compile(src)({ return handlebars.compile(src)({
head: `<title>I'm cool!</title>` head: `<title>I'm cool!</title>`
@ -77,6 +82,7 @@ test('web-bundle', async () => {
entryFileNames: '[name].[extname]', entryFileNames: '[name].[extname]',
assetFileNames: '[name].[extname]', assetFileNames: '[name].[extname]',
}); });
delete out.code; // Filter out code output (because this would be a huge snapshot)
expect(out).toMatchSnapshot(); expect(out).toMatchSnapshot();
// const code = await getCode(bundle, output); // const code = await getCode(bundle, output);

View File

@ -1,54 +1,31 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`live-reload 1`] = ` exports[`live-reload 1`] = `
[ ##############
{ # index.html #
"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); <html><head>
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> </head>
<body> <body>
<script src="batman-1a5fc364.js" type="module"></script> <script src="batman.js" type="module"></script>
</body></html>", </body></html>
}, #############
] # batman.js #
#############
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':<port>/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
#################
# batman.js.map #
#################
{"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;;;;"}
`; `;

View File

@ -3,22 +3,17 @@ import {test, expect} from "@jest/globals";
import {rollup} from "rollup"; import {rollup} from "rollup";
import liveReload from "rollup-plugin-livereload"; import liveReload from "rollup-plugin-livereload";
import {debugPrintOutput, getCode} from "../util/index.ts"; import {debugPrintOutput, getCode, serializer} from "../util/index.ts";
import html from "../../src/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"; import {fileURLToPath} from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(join(__dirname, 'fixtures')); process.chdir(join(__dirname, 'fixtures'));
test('live-reload', async () => { test('live-reload', async () => {
expect.addSnapshotSerializer(serializer);
const bundle = await rollup({ const bundle = await rollup({
input: 'index.html', input: 'index.html',
plugins: [ plugins: [
@ -29,10 +24,16 @@ test('live-reload', async () => {
}) })
] ]
}); });
const code = await getCode(bundle, output); const code = await getCode(bundle);
await bundle.close();// Make sure live-reload closes itself await bundle.close();// Make sure live-reload closes itself
debugPrintOutput('live-reload',code); debugPrintOutput('live-reload',code);
expect(code).toMatchSnapshot(); const portRE = /:[0-9]+\/livereload\.js/gi;
for(const file of code){
if(file.code && portRE.test(file.code)){
file.code = file.code.replaceAll(portRE,":<port>/livereload.js"); // remove any references to a port
}
}
expect({code}).toMatchSnapshot();
}); });
// TODO various parameters // TODO various parameters

View File

@ -1,125 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`multi-entry 1`] = ` exports[`multi-entry 1`] = `
[ ####################
{ # admin/index.html #
"code": "const b = ()=>'batman'; ####################
console.log(b()); <html><head>
export { b };
//# sourceMappingURL=batman-c7fa228c.js.map
",
"fileName": "admin/batman-c7fa228c.js",
"map": SourceMap {
"file": "batman-c7fa228c.js",
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
"names": [],
"sources": [
"../../admin/batman.js",
],
"sourcesContent": [
"export const b = ()=>'batman';
console.log(b());
",
],
"version": 3,
},
"source": undefined,
},
{
"code": undefined,
"fileName": "admin/batman-c7fa228c.js.map",
"map": undefined,
"source": "{"version":3,"file":"batman-c7fa228c.js","sources":["../../admin/batman.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n"],"names":[],"mappings":"AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;"}",
},
{
"code": undefined,
"fileName": "admin/index.body.script0.js-15dfaff3.js.map",
"map": undefined,
"source": "{"version":3,"file":"index.body.script0.js-15dfaff3.js","sources":["../../app/admin-deps.js","../../admin/index.html.body.script0.js"],"sourcesContent":["export function adminDeps(){\\n return \\"robin!\\";\\n}\\n","\\n import {bootstrap} from \\"../app/app.js\\"\\n import {adminDeps} from \\"../app/admin-deps.js\\";\\n bootstrap(document.getElementById('root'), adminDeps());\\n "],"names":[],"mappings":";;AAAO,SAAS,SAAS,EAAE;AAC3B,IAAI,OAAO,QAAQ,CAAC;AACpB;;ACCY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC"}",
},
{
"code": undefined,
"fileName": "admin/index.html",
"map": undefined,
"source": "<html><head>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module">import { b as bootstrap } from '../app-01141b67.js'; <script type="module">import { b as bootstrap } from '../app.js';
function adminDeps(){ function adminDeps(){
return "robin!"; return "robin!";
} }
bootstrap(document.getElementById('root'), adminDeps()); bootstrap(document.getElementById('root'), adminDeps());
//# sourceMappingURL=index.body.script0.js-15dfaff3.js.map //# sourceMappingURL=index.body.script0.js.js.map
</script> </script>
<script src="batman-c7fa228c.js" type="module"></script> <script src="batman.js" type="module"></script>
</body></html>", </body></html>
}, ##############
{ # index.html #
"code": "const bootstrap = (el,deps = [])=>{ ##############
el.innerHtml = \` <html><head>
<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": "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.body.script.js-45303f0f.js.map",
"map": undefined,
"source": "{"version":3,"file":"index.body.script.js-45303f0f.js","sources":["../index.html.body.script.js"],"sourcesContent":["\\n import {bootstrap} from \\"./app/app.js\\"\\n bootstrap(document.getElementById('root'), \\"<none>\\");\\n "],"names":[],"mappings":";;AAEY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC"}",
},
{
"code": undefined,
"fileName": "index.html",
"map": undefined,
"source": "<html><head>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module">import { b as bootstrap } from './app-01141b67.js'; <script type="module">import { b as bootstrap } from './app.js';
bootstrap(document.getElementById('root'), "<none>"); bootstrap(document.getElementById('root'), "<none>");
//# sourceMappingURL=index.body.script.js-45303f0f.js.map //# sourceMappingURL=index.body.script.js.js.map
</script> </script>
</body></html>", </body></html>
}, ###################
] # admin/batman.js #
###################
const b = ()=>'batman';
console.log(b());
export { b };
//# sourceMappingURL=batman.js.map
#######################
# admin/batman.js.map #
#######################
{"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;;;;"}
######################################
# admin/index.body.script0.js.js.map #
######################################
{"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"}
##########
# app.js #
##########
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
##############
# app.js.map #
##############
{"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;;;;"}
###############################
# index.body.script.js.js.map #
###############################
{"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"}
`; `;

View File

@ -2,22 +2,17 @@ import {resolve, join, dirname} from "node:path";
import {test, expect} from "@jest/globals"; import {test, expect} from "@jest/globals";
import { rollup } from "rollup"; import { rollup } from "rollup";
import {debugPrintOutput, getCode} from "../util/index.ts"; import {debugPrintOutput, getCode, serializer} from "../util/index.ts";
import html from "../../src/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"; import {fileURLToPath} from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(join(__dirname, 'fixtures')); process.chdir(join(__dirname, 'fixtures'));
test('multi-entry', async () => { test('multi-entry', async () => {
expect.addSnapshotSerializer(serializer);
const bundle = await rollup({ const bundle = await rollup({
input: { input: {
['index']: 'index.html', ['index']: 'index.html',
@ -28,9 +23,9 @@ test('multi-entry', async () => {
}), }),
] ]
}); });
const code = await getCode(bundle, output); const code = await getCode(bundle);
debugPrintOutput('multi-entry',code); debugPrintOutput('multi-entry',code);
expect(code).toMatchSnapshot(); expect({code}).toMatchSnapshot();
}); });
// TODO various parameters // TODO various parameters

View File

@ -1,68 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`rewrite-url 1`] = ` exports[`rewrite-url 1`] = `
[ ####################
{ # admin/index.html #
"code": "const bootstrap = (el,deps = [])=>{ ####################
el.innerHtml = \` <html><head>
<div>load the app</div>
\`;
};
export { bootstrap };
//# sourceMappingURL=app-88ed8fd6.js.map
",
"fileName": "admin/app-88ed8fd6.js",
"map": SourceMap {
"file": "app-88ed8fd6.js",
"mappings": "AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB;AACA,IAAI,CAAC,CAAC;AACN;;;;",
"names": [],
"sources": [
"../../admin/app.js",
],
"sourcesContent": [
"export const bootstrap = (el,deps = [])=>{
el.innerHtml = \`
<div>load the app</div>
\`;
}
",
],
"version": 3,
},
"source": undefined,
},
{
"code": undefined,
"fileName": "admin/app-88ed8fd6.js.map",
"map": undefined,
"source": "{"version":3,"file":"app-88ed8fd6.js","sources":["../../admin/app.js"],"sourcesContent":["export const bootstrap = (el,deps = [])=>{\\n el.innerHtml = \`\\n <div>load the app</div>\\n \`;\\n}\\n"],"names":[],"mappings":"AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB;AACA,IAAI,CAAC,CAAC;AACN;;;;"}",
},
{
"code": undefined,
"fileName": "admin/index.html",
"map": undefined,
"source": "<html><head>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="/admin/app-88ed8fd6.js" type="module"></script> <script src="/admin/app.js" type="module"></script>
</body></html>", </body></html>
}, ##############
{ # index.html #
"code": undefined, ##############
"fileName": "index.html", <html><head>
"map": undefined,
"source": "<html><head>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script src="/admin/app-88ed8fd6.js" type="module"></script> <script src="/admin/app.js" type="module"></script>
</body></html>", </body></html>
}, ################
] # admin/app.js #
################
const bootstrap = (el,deps = [])=>{
el.innerHtml = \`
<div>load the app</div>
\`;
};
export { bootstrap };
//# sourceMappingURL=app.js.map
####################
# admin/app.js.map #
####################
{"version":3,"file":"app.js","sources":["../../admin/app.js"],"sourcesContent":["export const bootstrap = (el,deps = [])=>{\\n el.innerHtml = \`\\n <div>load the app</div>\\n \`;\\n}\\n"],"names":[],"mappings":"AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB;AACA,IAAI,CAAC,CAAC;AACN;;;;"}
#################
# RENDERED HTML #
#################
<html><head>
</head>
<body>
<div id="root"></div>
<script src="/admin/app.js" type="module"></script>
</body></html>
#############
# RESPONSES #
#############
200 http://localhost/admin
200 http://localhost/admin/app.js
200 http://localhost/favicon.ico
`; `;

View File

@ -1,7 +1,7 @@
import {resolve, join, dirname} from "node:path"; import {resolve, join, dirname} from "node:path";
import {test, expect} from "@jest/globals"; import {test, expect} from "@jest/globals";
import {runBrowserTest} from "../util/index.ts"; import {runBrowserTest, serializer} from "../util/index.ts";
import html from "../../src/index.ts"; import html from "../../src/index.ts";
@ -11,6 +11,7 @@ process.chdir(join(__dirname, 'fixtures'));
test('rewrite-url', async () => { test('rewrite-url', async () => {
expect.addSnapshotSerializer(serializer);
const out = await runBrowserTest({ const out = await runBrowserTest({
input: { input: {
['index']: 'index.html', ['index']: 'index.html',
@ -34,7 +35,7 @@ test('rewrite-url', async () => {
format: 'es', // iifi and cjs should be added to tests format: 'es', // iifi and cjs should be added to tests
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
}); });
expect(out.code).toMatchSnapshot(); // Snapshot the result code expect(out).toMatchSnapshot(); // Snapshot the result code
// const bundle = await rollup({ // const bundle = await rollup({
// input: { // input: {

View File

@ -1,49 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`handlebars 1`] = ` exports[`handlebars 1`] = `
[ ##############
{ # index.html #
"code": "const b = ()=>'batman'; ##############
console.log(b()); <html><head>
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"> <meta data-test="a">
</head> </head>
<body> <body>
<script src="batman-c7fa228c.js" type="module"></script> <script src="batman.js" type="module"></script>
</body></html>", </body></html>
}, #############
] # batman.js #
#############
const b = ()=>'batman';
console.log(b());
export { b };
//# sourceMappingURL=batman.js.map
#################
# batman.js.map #
#################
{"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;;;;"}
`; `;

View File

@ -3,35 +3,33 @@ import {test, expect} from "@jest/globals";
import { rollup } from "rollup"; import { rollup } from "rollup";
import {debugPrintOutput, getCode} from "../util/index.ts"; import {debugPrintOutput, getCode, serializer} from "../util/index.ts";
import html from "../../src/index.ts"; import html from "../../src/index.ts";
import handlebars from "handlebars"; import handlebars from "handlebars";
const output = {
dir: 'output', // Output all files
format: 'es', // iifi and cjs should be added to tests
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
};
import {fileURLToPath} from "node:url"; import {fileURLToPath} from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(join(__dirname, 'fixtures')); process.chdir(join(__dirname, 'fixtures'));
test('handlebars', async () => { test('handlebars', async () => {
expect.addSnapshotSerializer(serializer);
const bundle = await rollup({ const bundle = await rollup({
input: 'index.hbs', input: 'index.hbs',
plugins: [ plugins: [
html({ html({
include: [
'**/*.(html|hbs)',// html or handlebars
],
transform(src){ transform(src){
return handlebars.compile(src)({a:'a'}) return handlebars.compile(src)({a:'a'})
} }
}) })
] ]
}); });
const code = await getCode(bundle, output); const code = await getCode(bundle);
debugPrintOutput('handlebars',code); debugPrintOutput('handlebars',code);
expect(code).toMatchSnapshot(); expect({code}).toMatchSnapshot();
}); });

View File

@ -1,97 +1,55 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`url-plugin copied-assets 1`] = ` exports[`url-plugin copied-assets 1`] = `
[ ##############
{ # index.html #
"code": "const b = ()=>'batman'; ##############
console.log(b()); <html><head>
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"> <link rel="icon" href="fb585fdb6db313c9.svg">
</head> </head>
<body> <body>
<script src="batman-c7fa228c.js" type="module"></script> <script src="batman.js" type="module"></script>
</body></html>", </body></html>
}, #############
] # batman.js #
#############
const b = ()=>'batman';
console.log(b());
export { b };
//# sourceMappingURL=batman.js.map
#################
# batman.js.map #
#################
{"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;;;;"}
`; `;
exports[`url-plugin inlined-assets 1`] = ` exports[`url-plugin inlined-assets 1`] = `
[ ##############
{ # index.html #
"code": "const b = ()=>'batman'; ##############
console.log(b()); <html><head>
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"> <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> </head>
<body> <body>
<script src="batman-c7fa228c.js" type="module"></script> <script src="batman.js" type="module"></script>
</body></html>", </body></html>
}, #############
] # batman.js #
#############
const b = ()=>'batman';
console.log(b());
export { b };
//# sourceMappingURL=batman.js.map
#################
# batman.js.map #
#################
{"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;;;;"}
`; `;

View File

@ -4,15 +4,10 @@ import {test, expect} from "@jest/globals";
import { rollup } from "rollup"; import { rollup } from "rollup";
import urlPlugin from "@rollup/plugin-url"; import urlPlugin from "@rollup/plugin-url";
import {debugPrintOutput, getCode} from "../util/index.ts"; import {debugPrintOutput, getCode, serializer} from "../util/index.ts";
import html from "../../src/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"; import {fileURLToPath} from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
@ -26,6 +21,7 @@ const defaultAssetInclude = [
]; ];
describe("url-plugin", ()=>{ describe("url-plugin", ()=>{
expect.addSnapshotSerializer(serializer);
test('copied-assets', async () => { test('copied-assets', async () => {
const bundle = await rollup({ const bundle = await rollup({
input: 'index.html', input: 'index.html',
@ -38,9 +34,9 @@ describe("url-plugin", ()=>{
}), }),
], ],
}); });
const code = await getCode(bundle, output); const code = await getCode(bundle);
debugPrintOutput('copied-assets',code); debugPrintOutput('copied-assets',code);
expect(code).toMatchSnapshot(); expect({code}).toMatchSnapshot();
}); });
@ -56,9 +52,9 @@ describe("url-plugin", ()=>{
}), }),
] ]
}); });
const code = await getCode(bundle, output); const code = await getCode(bundle);
debugPrintOutput('inlined-assets',code); debugPrintOutput('inlined-assets',code);
expect(code).toMatchSnapshot(); expect({code}).toMatchSnapshot();
}); });
}) })

View File

@ -2,8 +2,8 @@ import {Plugin, InputPluginOption, RollupOptions, OutputOptions, RollupOutput} f
import {TestOptions as BrowserTestOptions, TestOutput as PuppeteerTestOutput} from "./puppeteer-run-test.js"; import {TestOptions as BrowserTestOptions, TestOutput as PuppeteerTestOutput} from "./puppeteer-run-test.js";
import { rollup } from "rollup"; import { rollup } from "rollup";
import serveTest, {LogCallback} from "./serve-test.ts"; import serveTest, {LogCallback} from "./serve-test.ts";
import type {ExecutionContext} from "ava";
import {getCode, TestOutput} from "./code-output.ts"; import {getCode, TestOutput} from "./code-output.ts";
import {defaultOutput} from "./default-output.ts";
export interface OutputFilterOptions { export interface OutputFilterOptions {
html?: boolean html?: boolean
@ -49,24 +49,18 @@ export async function runBrowserTest(
...test, ...test,
log: test.log ?? console.log, log: test.log ?? console.log,
onResult: (output)=>{ onResult: (output)=>{
testOutput = {...testOutput, ...output}; Object.assign(testOutput, output);
} }
})]: []) })]: [])
] ]
}); });
// TODO make configurable? // TODO make configurable?
const generated = await bundle.generate({
dir: 'output', // Output all files
format: 'es', // iifi and cjs should be added to tests
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
chunkFileNames: '[name].js',
entryFileNames: '[name].mjs',
assetFileNames: '[name].[extname]',
});
if(output){ if(output){
testOutput.code = await getCode(bundle, output); testOutput.code = await getCode(bundle);
}else{
const generated = await bundle.generate(defaultOutput);
} }
await bundle.close(); await bundle.close();

View File

@ -1,4 +1,5 @@
import type {RollupBuild, OutputOptions, OutputAsset, OutputChunk, SourceMap} from "rollup"; import type {RollupBuild, OutputOptions, OutputAsset, OutputChunk, SourceMap} from "rollup";
import {defaultOutput} from "./default-output.ts";
export interface TestOutput{ export interface TestOutput{
code: string, code: string,
@ -7,7 +8,7 @@ export interface TestOutput{
map: any map: any
} }
export const getCode = async (bundle: RollupBuild, outputOptions: OutputOptions): Promise<TestOutput[]> => { export const getCode = async (bundle: RollupBuild, outputOptions: OutputOptions = defaultOutput): Promise<TestOutput[]> => {
const { output } = await bundle.generate(outputOptions || { format: 'cjs', exports: 'auto' }); const { output } = await bundle.generate(outputOptions || { format: 'cjs', exports: 'auto' });
return output.sort((a,b)=> { return output.sort((a,b)=> {

View 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]',
};

View File

@ -1,5 +1,7 @@
// TODO: this should be the main module used, other should be imported manually if exceptions are needed? // TODO: this should be the main module used, other should be imported manually if exceptions are needed?
export * from "./browser-test.ts"; export * from "./browser-test.ts";
export {defaultOutput} from "./default-output.ts";
export {serializer} from "./test-serializer.ts";
export * from "./code-output.ts"; export * from "./code-output.ts";
export * from "./print-code-output.ts"; export * from "./print-code-output.ts";

View File

@ -55,6 +55,9 @@ export async function puppeteerRunTest(opts: Partial<TestOptions>, hostUrl: stri
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: isInDebugMode()? false : 'new', headless: isInDebugMode()? false : 'new',
args: [
...(process.env.PUPPETEER_CHROME_ARGS??'').split(' '),
]// --use-gl=egl
}); });
const page = await browser.newPage(); const page = await browser.newPage();
@ -106,7 +109,9 @@ export async function puppeteerRunTest(opts: Partial<TestOptions>, hostUrl: stri
await cb(page); await cb(page);
} }
const htmlHandle = await page.$('html'); const htmlHandle = await page.$('html');
const html = await page.evaluate(html => html?.outerHTML ?? html?.innerHTML, htmlHandle); const html = await page.evaluate( // potentially trips up Jest's code coverage, hence the istanbul ignore
/* istanbul ignore next */html => html?.outerHTML ?? html?.innerHTML, htmlHandle
);
// Add the final html // Add the final html
output.html = html || ''; output.html = html || '';

View File

@ -28,9 +28,6 @@ import type {
} from 'http' } from 'http'
import type { ServerOptions } from 'https' import type { ServerOptions } from 'https'
import test, {ExecutionContext} from "ava";
import {createReadStream} from "fs";
type TypeMap = { type TypeMap = {
[key: string]: string[]; [key: string]: string[];
@ -136,6 +133,10 @@ export default function serveTest (options: RollupServeTestOptions ): Plugin {
let server : Server; let server : Server;
let bundle : OutputBundle = {}; let bundle : OutputBundle = {};
const closeServer = async ()=>new Promise((resolve, reject)=>{
server.close((err)=>err?reject(err):resolve(undefined));
server = null as unknown as Server; // unset
});
const logTest = (msg: string, mode: 'info'|'warn' = 'info')=>{ const logTest = (msg: string, mode: 'info'|'warn' = 'info')=>{
if(isInDebugMode()){ if(isInDebugMode()){
@ -220,9 +221,9 @@ export default function serveTest (options: RollupServeTestOptions ): Plugin {
function closeServerOnTermination () { function closeServerOnTermination () {
const terminationSignals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP'] const terminationSignals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP']
terminationSignals.forEach(signal => { terminationSignals.forEach(signal => {
process.on(signal, () => { process.on(signal, async () => {
if (server) { if (server) {
server.close() await closeServer();
process.exit() process.exit()
} }
}) })
@ -232,7 +233,7 @@ export default function serveTest (options: RollupServeTestOptions ): Plugin {
// release previous server instance if rollup is reloading configuration in watch mode // release previous server instance if rollup is reloading configuration in watch mode
// @ts-ignore // @ts-ignore
if (server) { if (server) {
server.close() closeServer()
} else { } else {
closeServerOnTermination() closeServerOnTermination()
} }
@ -282,8 +283,9 @@ export default function serveTest (options: RollupServeTestOptions ): Plugin {
} }
} }
}, },
closeBundle (){ async closeBundle(){
// Done with the bundle // Done with the bundle
await closeServer();
} }
} }
} }

View File

@ -0,0 +1,79 @@
import type {runBrowserTest} from "./browser-test.ts";
import type {expect} from "@jest/globals";
import chalk from "chalk";
type TestOutput = Awaited<ReturnType<typeof runBrowserTest>>;
type Serializer = Parameters<typeof expect.addSnapshotSerializer>[0];
function headerFor(name: string): string[]{
const hr = name.split('').map(()=>`#`).join('')
return [
`##${hr}##`,
`# ${name} #`,
`##${hr}##`,
];
}
export const serializer: Serializer = {
test: (val: TestOutput)=> !!(
(val?.code && Array.isArray(val?.code))
|| (val?.html && typeof(val?.html)==='string')
),
serialize(val: TestOutput,
config,
indentation,
depth,
refs,
printer): string{
const indent = (config.indent||'')+(indentation+'');
let linesOut: string[] = [];
if(val.code){
const fileLines: string[][] = val.code.slice().sort(((a,b)=>{
const sortPropsA = [!a.fileName.endsWith('html'), a.fileName];
const sortPropsB = [!b.fileName.endsWith('html'), b.fileName];
for(let i = 0; i< 2;++i){
if(sortPropsA[i]<sortPropsB[i]) return -1;
else if(sortPropsA[i]>sortPropsB[i]) return 1;
}
return 0;
})).map(({fileName, code, source})=>{
return [
...headerFor(fileName),
...((code||source).split('\n'))
]
});
linesOut = linesOut.concat(...fileLines);
}
if(val.html){
linesOut = linesOut.concat([
...headerFor("RENDERED HTML"),
...(val.html.split('\n')),
]);
}
if(val.errors?.length){
linesOut = linesOut.concat([
...headerFor("ERRORS"),
], ...val.errors.map(x=>x.split("\n")));
}
if(val.console?.length){
linesOut = linesOut.concat([
...headerFor("CONSOLE"),
], ...val.console.map(x=>x.split("\n")));
}
if(val.requestsFailed?.length){
linesOut = linesOut.concat([
...headerFor("FAILED REQUESTS"),
], ...val.requestsFailed.map(x=>x.split("\n")));
}
if(val.responses?.length){
linesOut = linesOut.concat([
...headerFor("RESPONSES"),
], ...val.responses.map(x=>x.split("\n")));
}
return linesOut.map(x=>`${indent}${x}`).join('\n');
},
}

View File

@ -8,18 +8,10 @@ import {writeFile} from "node:fs/promises";
import html from "../../src/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
// Prevent hashes from being added (and screw up the snapshots)
chunkFileNames: '[name].js',
entryFileNames: '[name].[extname]',
assetFileNames: '[name].[extname]',
};
import {fileURLToPath} from "node:url"; import {fileURLToPath} from "node:url";
import {pathToFileURL} from "url"; import {pathToFileURL} from "url";
import {defaultOutput} from "../util/default-output.ts";
const __dirname = dirname(fileURLToPath(import.meta.url)); const __dirname = dirname(fileURLToPath(import.meta.url));
process.chdir(join(__dirname, 'fixtures')); process.chdir(join(__dirname, 'fixtures'));
@ -35,9 +27,10 @@ test('watch', async () => {
const path = resolve(__dirname, 'fixtures/watched-file.js'); const path = resolve(__dirname, 'fixtures/watched-file.js');
await writeFile(path, origContent, {encoding: 'utf-8'}); await writeFile(path, origContent, {encoding: 'utf-8'});
const output = defaultOutput;
const watcher = rollup.watch({ const watcher = rollup.watch({
input: 'index.html', input: 'index.html',
output, output: output,
plugins: [ plugins: [
html({ html({
}), }),
@ -53,7 +46,7 @@ test('watch', async () => {
// Just wait on the watch mode to pick up on the changes // Just wait on the watch mode to pick up on the changes
}, },
async (bundle)=>{ async (bundle)=>{
const code = await getCode(bundle, output); const code = await getCode(bundle);
debugPrintOutput('watch',code); debugPrintOutput('watch',code);
// Reset the source file // Reset the source file

View File

@ -11,12 +11,12 @@
"sourceMap": true, "sourceMap": true,
"strict": true, "strict": true,
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"allowJs": true, "allowJs": true,
"allowImportingTsExtensions": true "allowImportingTsExtensions": true
}, },
"exclude": ["dist", "node_modules", "test/types"], "exclude": ["dist", "node_modules", "test/types","test/**/fixtures/*"],
"include": ["src/**/*", "types/**/*"], "include": ["src/**/*", "types/**/*"],
"ts-node": { "ts-node": {
"esm": true // from the top of https://typestrong.org/ts-node/docs/imports/ "esm": true // from the top of https://typestrong.org/ts-node/docs/imports/

86
types/index.d.ts vendored
View File

@ -1,86 +0,0 @@
import type {Plugin, OutputChunk, OutputAsset, OutputBundle, TransformModuleJSON, } from 'rollup';
import {FilterPattern} from "@rollup/pluginutils";
import type {DefaultTreeAdapterMap} from "parse5";
import {PreRenderedChunk} from "rollup";
import type {LoadNodeCallback} from "./load.d.ts";
export type * from "./load.d.ts"
import type {ResolveCallback} from "./resolve.d.ts";
export type * from "./resolve.d.ts"
export interface RollupHtmlTransformContext {
id?: string;
// bundle: OutputBundle;
// 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 {
publicPath?: string;
/**
* 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
* ```
* transform(source){
* return handlebars.compile(source)({myVar:'example'})
* }
* ```
*/
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.
*
* Return false to skip any further processing on this node. Use the load function to add any resources from this node, and replace the import with a placeholder so the plugin knows where to inject the end result
*/
load?: LoadNodeCallback;
/**
* Callback to filter which references actually need to be resolved. Here you can filter out:
* - Links to extensions that don't need to be handled through rollup
* - Resources that are external to the app (for example non-relative paths)
* - Page navigation within the app
*
* 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
}
/**
* A Rollup plugin which creates HTML files to serve Rollup bundles.
* @param options - Plugin options.
* @returns Plugin instance.
*/
export default function html(options?: RollupHtmlOptions): Plugin;