Compare commits
27 Commits
7e45443d19
...
feature/je
| Author | SHA1 | Date | |
|---|---|---|---|
| c4878caef3 | |||
| e46f668ac8 | |||
| 3b540d0c48 | |||
| 1c55b894c9 | |||
| 3e46055845 | |||
| e96c2248ee | |||
| 2adfbee74b | |||
| 71a377417d | |||
| afd4a3c9ae | |||
| 980d33c48e | |||
| 5c1e528304 | |||
| 5d2a45ef81 | |||
| 6e50208557 | |||
| 48dcdefee1 | |||
| ba09aaf915 | |||
| 9bf026f0c3 | |||
| a784abc1b0 | |||
| 52c104f781 | |||
| b18ac5c361 | |||
| ba07649981 | |||
| 9768b3efe5 | |||
| 4006f3954e | |||
| e3a022d420 | |||
| 5ae59102b5 | |||
| 831e607591 | |||
| 240d5cfe9a | |||
| da9dc3bdc1 |
10
.run/Template Jest.run.xml
Normal file
10
.run/Template Jest.run.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="true" type="JavaScriptTestRunnerJest">
|
||||
<node-interpreter value="/usr/bin/node" />
|
||||
<node-options value="--experimental-vm-modules" />
|
||||
<jest-options value="--detectOpenHandles" />
|
||||
<envs />
|
||||
<scope-kind value="ALL" />
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
||||
98
CHANGELOG.md
98
CHANGELOG.md
@@ -1,86 +1,28 @@
|
||||
# @rollup/plugin-html ChangeLog
|
||||
|
||||
## v1.0.2
|
||||
# 0.0.2
|
||||
|
||||
_2023-01-20_
|
||||
Private release update. Added experimental support for:
|
||||
- multiple-entrypoints (i.e index.html and admin/index.html)
|
||||
- Inlined scripts (i.e <script type="module">...</script>)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- fix: types should come first in exports [#1403](https://github.com/rollup/plugins/pull/1403)
|
||||
# 0.0.1
|
||||
Initial private release
|
||||
|
||||
## v1.0.1
|
||||
|
||||
_2022-10-21_
|
||||
|
||||
### Updates
|
||||
# Open issues / Short-term ToDo's:
|
||||
|
||||
- chore: update rollup dependencies ([3038271](https://github.com/rollup/plugins/commit/303827191ede6b2e4eade96c6968ed16a587683f))
|
||||
|
||||
## v1.0.0
|
||||
|
||||
_2022-10-09_
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- fix: prepare for Rollup 3 [#1294](https://github.com/rollup/plugins/pull/1294)
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- fix: function signature of html updated [#975](https://github.com/rollup/plugins/pull/975)
|
||||
|
||||
## v0.2.3
|
||||
|
||||
_2021-02-14_
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- fix: package.json files (#802)
|
||||
|
||||
## v0.2.2
|
||||
|
||||
_2021-01-29_
|
||||
|
||||
### Updates
|
||||
|
||||
- chore: run build before publish (ce65c12)
|
||||
- chore: upgrade TypeScript (#713)
|
||||
- refactor: refactor to typescript (#634)
|
||||
- chore: update dependencies (6c8614c)
|
||||
|
||||
## v0.2.1
|
||||
|
||||
_2021-01-29_
|
||||
|
||||
### Updates
|
||||
|
||||
- chore: run build before publish (ce65c12)
|
||||
- chore: upgrade TypeScript (#713)
|
||||
- refactor: refactor to typescript (#634)
|
||||
- chore: update dependencies (6c8614c)
|
||||
|
||||
## v0.2.0
|
||||
|
||||
_2020-05-02_
|
||||
|
||||
### Features
|
||||
|
||||
- feat: support custom meta element creation (#308)
|
||||
|
||||
### Updates
|
||||
|
||||
- test: update snapshots for rollup v2 (c09509f)
|
||||
|
||||
## v0.1.1
|
||||
|
||||
_2020-01-04_
|
||||
|
||||
### Updates
|
||||
|
||||
- docs: fix <br> tag (#89)
|
||||
- test: change tests to serial to avoid weird snapshot conflicts (a492ce7)
|
||||
|
||||
## 0.1.0
|
||||
|
||||
_2019-11-29_
|
||||
|
||||
- First Release
|
||||
- Implement importing style (#1 linking to a pcss, #2 inlined style)
|
||||
- Importing html as a JSModule
|
||||
- Testing on a windows machine and fix whatever issues with paths that come out of it
|
||||
- Code clean-up / Watch-mode support
|
||||
- Properly use 'meta' property, and supporting caching
|
||||
- Supporting 'assets' directly (LoadType) using emitFile({type:'asset',...}). Removes the need for @rollup/plugin-url in small projects (altough it is still the preferred way of including assets)
|
||||
- Getting rid of the module evaluation step if possible
|
||||
- Clean up our API, keeping in mind the configurability desired:
|
||||
- resolving language for inline script/style
|
||||
- excluding non-relative imports (ie unpkg stuff etc)
|
||||
- customizing how to import certain things (LoadType)
|
||||
- support for typescript (might not need extra work, but it should be integrated in tests)
|
||||
- cjs & iifi supported in tests
|
||||
|
||||
190
README.md
190
README.md
@@ -1,33 +1,44 @@
|
||||
# Work-in-progress changelog
|
||||
|
||||
|
||||
|
||||
|
||||
[npm]: https://img.shields.io/npm/v/@rollup/plugin-html
|
||||
[npm-url]: https://www.npmjs.com/package/@rollup/plugin-html
|
||||
[size]: https://packagephobia.now.sh/badge?p=@rollup/plugin-html
|
||||
[size-url]: https://packagephobia.now.sh/result?p=@rollup/plugin-html
|
||||
[npm]: https://img.shields.io/npm/v/rollup-plugin-html-entry2
|
||||
[npm-url]: https://www.npmjs.com/package/rollup-plugin-html-entry2
|
||||
[size]: https://packagephobia.now.sh/badge?p=rollup-plugin-html-entry2
|
||||
[size-url]: https://packagephobia.now.sh/result?p=rollup-plugin-html-entry2
|
||||
[handlebars]: https://www.npmjs.com/package/handlebars
|
||||
|
||||
|
||||
[![npm][npm]][npm-url]
|
||||
[![size][size]][size-url]
|
||||
[](https://liberamanifesto.com)
|
||||
|
||||
# @rollup/plugin-html
|
||||
# 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 Rollup plugin which creates HTML files to serve Rollup bundles.
|
||||
A(nother) rollup plugin that tries to teach Rollup to start from an HTML entry, and the use of (multiple) HTML files in general.
|
||||
The goal is to include assets referenced by the HTML file into the build-process as to copy/inline where appropriate and
|
||||
optionally optimize them. Without having to seperatly copy them to the output directory.
|
||||
|
||||
Please see [Supported Output Formats](#supported-output-formats) for information about using this plugin with output formats other than `esm` (`es`), `iife`, and `umd`.
|
||||
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),
|
||||
this plugin will also allow you to transform the source files by any HTML-templating engine such as [handlebars].
|
||||
|
||||
|
||||
Please see [Supported Output Formats](#supported-output-formats) for information about using this plugin with output formats other than `esm` (`es`).
|
||||
|
||||
## Requirements
|
||||
|
||||
This plugin requires an [LTS](https://github.com/nodejs/Release) Node version (v14.0.0+) and Rollup v1.20.0+.
|
||||
This plugin requires an [LTS](https://github.com/nodejs/Release) Node version (v18.0.0+) and Rollup v3.?.?+.
|
||||
|
||||
## Install
|
||||
|
||||
Using npm:
|
||||
|
||||
```console
|
||||
npm install @rollup/plugin-html --save-dev
|
||||
npm install rollup-plugin-html-entry2 --save-dev
|
||||
```
|
||||
|
||||
## Usage
|
||||
@@ -35,131 +46,86 @@ npm install @rollup/plugin-html --save-dev
|
||||
Create a `rollup.config.js` [configuration file](https://www.rollupjs.org/guide/en/#configuration-files) and import the plugin:
|
||||
|
||||
```js
|
||||
const html = require('@rollup/plugin-html');
|
||||
import html from 'rollup-plugin-html-entry2';
|
||||
|
||||
module.exports = {
|
||||
input: 'src/index.js',
|
||||
export default {
|
||||
input: 'src/index.html',
|
||||
output: {
|
||||
dir: 'output',
|
||||
format: 'cjs'
|
||||
},
|
||||
plugins: [html()]
|
||||
};
|
||||
```
|
||||
|
||||
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).
|
||||
!! 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.
|
||||
Due to the early stage development of this plugin, old-style CommonJS modules are completely ignored for now.
|
||||
|
||||
Once run successfully, an HTML file should be written to the bundle output destination.
|
||||
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).
|
||||
|
||||
## Options
|
||||
|
||||
### `attributes`
|
||||
|
||||
Type: `Object`<br>
|
||||
Default: `{ html: { lang: 'en' }, link: null, script: null }`
|
||||
|
||||
Specifies additional attributes for `html`, `link`, and `script` elements. For each property, provide an object with key-value pairs that represent an HTML element attribute name and value. By default, the `html` element is rendered with an attribute of `lang="en"`.
|
||||
|
||||
_Note: If using the `es` / `esm` output format, `{ type: 'module'}` is automatically added to `attributes.script`._
|
||||
|
||||
### `fileName`
|
||||
|
||||
Type: `String`<br>
|
||||
Default: `'index.html'`
|
||||
|
||||
### `meta`
|
||||
|
||||
Type: `Array[...object]`<br>
|
||||
Default: `[{ charset: 'utf-8' }]`
|
||||
|
||||
Specifies attributes used to create `<meta>` elements. For each array item, provide an object with key-value pairs that represent `<meta>` element attribute names and values.
|
||||
|
||||
Specifies the name of the HTML to emit.
|
||||
|
||||
### `publicPath`
|
||||
|
||||
Type: `String`<br>
|
||||
Default: `''`
|
||||
|
||||
Specifies a path to prepend to all bundle assets (files) in the HTML output.
|
||||
|
||||
### `template`
|
||||
|
||||
Type: `Function`<br>
|
||||
Default: `internal function`
|
||||
Type: `Function`\
|
||||
Default: `undefined`\
|
||||
Returns: `String`
|
||||
|
||||
Specifies a function that provides the rendered source for the HTML output. The function should be in the form of:
|
||||
Specifies a transform to be applied before parsing the HTML, this allows you to transform the sourcefile with a templating engine such as [handlebars] first.
|
||||
|
||||
```js
|
||||
const template = ({ attributes, bundle, files, publicPath, title }) => { ... }
|
||||
```
|
||||
```javascript
|
||||
import {rollup} from "rollup";
|
||||
import handlebars from "handlebars";
|
||||
import html from "rollup-plugin-html-entry2";
|
||||
|
||||
- `attributes`: Corresponds to the `attributes` option passed to the plugin
|
||||
- `bundle`: An `Object` containing key-value pairs of [`AssetInfo` or `ChunkInfo`](https://rollupjs.org/guide/en/#generatebundle)
|
||||
- `files`: An `Array` of `AssetInfo` or `ChunkInfo` containing any entry (`isEntry: true`) files, and any asset (`isAsset: true`) files in the bundle that will be emitted
|
||||
- `publicPath`: Corresponds to the `publicPath` option passed to the plugin
|
||||
- `title`: Corresponds to the `title` option passed to the plugin
|
||||
|
||||
By default this is handled internally and produces HTML in the following format:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html ${attributes}>
|
||||
<head>
|
||||
${metas}
|
||||
<title>${title}</title>
|
||||
${links}
|
||||
</head>
|
||||
<body>
|
||||
${scripts}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Where `${links}` represents all `<link ..` tags for CSS and `${scripts}` represents all `<script...` tags for JavaScript files.
|
||||
|
||||
### `title`
|
||||
|
||||
Type: `String`<br>
|
||||
Default: `'Rollup Bundle'`
|
||||
|
||||
Specifies the HTML document title.
|
||||
|
||||
## Exports
|
||||
|
||||
### `makeHtmlAttributes(attributes)`
|
||||
|
||||
Parameters: `attributes`, Type: `Object`<br>
|
||||
Returns: `String`
|
||||
|
||||
Consumes an object with key-value pairs that represent an HTML element attribute name and value. The function returns all pairs as a space-separated string of valid HTML element attributes. e.g.
|
||||
|
||||
```js
|
||||
const { makeHtmlAttributes } = require('@rollup/plugin-html');
|
||||
|
||||
makeHtmlAttributes({ lang: 'en', 'data-batcave': 'secret' });
|
||||
// -> 'lang="en" data-batcave="secret"'
|
||||
async function build() {
|
||||
await rollup({
|
||||
input: 'index.hbs',
|
||||
plugins: [
|
||||
html({
|
||||
transform(src) {
|
||||
return handlebars.compile(src)({a: 'a'})
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Output Formats
|
||||
|
||||
By default, this plugin supports the `esm` (`es`), `iife`, and `umd` [output formats](https://rollupjs.org/guide/en/#outputformat), as those are most commonly used as browser bundles. Other formats can be used, but will require using the [`template`](#template) option to specify a custom template function which renders the unique requirements of other 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)
|
||||
|
||||
### `amd`
|
||||
## Status
|
||||
### (Rudimentarily) supported
|
||||
- Importing JS via `<script src="..." type="module">` tags
|
||||
- Importing assets using @rollup/plugin-url (which could use an update TBH)
|
||||
- Compatibility with other plugins such as @rollup/plugin-node-resolve, @rollup/plugin-babel, @rollup/plugin-commonjs, @rollup/plugin-terser and rollup-plugin-livereload
|
||||
- Inline scripts (i.e `<script>...</script>`)
|
||||
|
||||
Will likely require use of RequireJS semantics, which allows only for a single entry `<script>` tag. If more entry chunks are emitted, these need to be loaded via a proxy file. RequireJS would also need to be a dependency and added to the build: https://requirejs.org/docs/start.html.
|
||||
|
||||
### `system`
|
||||
### Not (yet/properly) supported
|
||||
- Sourcemaps (inlined script) (dev-note: we're already including magic-string for this, but do not use it yet, neeeds refactoring)
|
||||
- Plugins importing CSS files
|
||||
- CommonJS (cjs) and IIFI output formats. (Is UMD actually ever used?)
|
||||
- Overriding which DOM-nodes and resulting URLS to ignore/include (in a clean way)
|
||||
- Other (various) plugins such as typescript, or those for HMR etc
|
||||
- ...
|
||||
|
||||
Would require a separate `<script>` tag first that adds the `s.js` minimal loader. Loading modules might then resemble: `<script>System.import('./batman.js')</script>`.
|
||||
# Contibuting
|
||||
|
||||
## Attribution
|
||||
You can be helpful by testing, proving helpful feedback, expanding the documentation, responding to issues/questions being reported, resolving the many ToDo`s in the code, implementating features...\
|
||||
[Get in touch](mailto:rollup-plugin-html-entry2@cerxes.net) or just dive into [the code](https://git.cerxes.net/rollup-apps/plugin-html) or [issues](https://git.cerxes.net/rollup-apps/plugin-html/issues).
|
||||
|
||||
This plugin was inspired by and is based upon [mini-html-webpack-plugin](https://github.com/styleguidist/mini-html-webpack-plugin) by Juho Vepsäläinen and Artem Sapegin, with permission.
|
||||
See also the ToDo-list at the end of the [changelog](./CHANGELOG.md)
|
||||
|
||||
## Meta
|
||||
|
||||
[CONTRIBUTING](/.github/CONTRIBUTING.md)
|
||||
|
||||
[LICENSE (MIT)](/LICENSE)
|
||||
# Notes
|
||||
## git.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
|
||||
## 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.
|
||||
- [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...
|
||||
- [@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).
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
const babelConfig ={
|
||||
presets: [
|
||||
["@babel/preset-typescript", {
|
||||
|
||||
}],
|
||||
],
|
||||
plugins: [
|
||||
|
||||
105
package.json
105
package.json
@@ -1,21 +1,18 @@
|
||||
{
|
||||
"name": "@rollup-apps/plugin-html",
|
||||
"version": "0.0.1",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"description": "Creates HTML files to serve Rollup bundles",
|
||||
"name": "rollup-plugin-html-entry2",
|
||||
"version": "0.0.7",
|
||||
"description": "Teaches rollup how to deal with HTML, allows to use HTML-files as entry-points.",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"url": "rollup/plugins",
|
||||
"directory": "packages/html"
|
||||
"type": "git",
|
||||
"url": "https://git.cerxes.net/rollup-apps/plugin-html.git"
|
||||
},
|
||||
"author": "Andrew Powell <andrew@shellscape.org>",
|
||||
"homepage": "https://github.com/rollup/plugins/tree/master/packages/html#readme",
|
||||
"bugs": "https://github.com/rollup/plugins/issues",
|
||||
"author": "Miel Truyen <miel.truyen@cerxes.net>",
|
||||
"homepage": "https://git.cerxes.net/rollup-apps/plugin-html",
|
||||
"bugs": "https://git.cerxes.net/rollup-apps/plugin-html/issues",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"main": "dist/es/index.js",
|
||||
"module": "./dist/es/index.js",
|
||||
@@ -24,15 +21,17 @@
|
||||
"import": "./dist/es/index.js"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://npm.cerxes.net"
|
||||
"registry": "https://npm.cerxes.net",
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rollup -c",
|
||||
"prerelease": "pnpm build",
|
||||
"test": "ava",
|
||||
"ci:coverage": "nyc pnpm test && nyc report --reporter=text-lcov > coverage.lcov",
|
||||
"ci:lint": "pnpm build && pnpm lint-staged",
|
||||
"ci:test": "pnpm test -- --verbose"
|
||||
"ci:test": "pnpm test -- --verbose",
|
||||
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
||||
"save-test": "NODE_OPTIONS='--experimental-vm-modules' jest -u"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@@ -48,49 +47,57 @@
|
||||
"template"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"rollup": "^3.0.0"
|
||||
"rollup": "^4.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"rollup": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^5.0.5",
|
||||
"magic-string": "^0.30.5",
|
||||
"parse5": "^7.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.15.11",
|
||||
"@rollup/plugin-typescript": "^11.1.0",
|
||||
"postcss": "^8.4.22",
|
||||
"rollup": "^3.20.3",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"typescript": "^5.0.4",
|
||||
"del-cli": "^5.0.0",
|
||||
"tslib": "^2.5.0",
|
||||
"ava": "^5.2.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"@babel/core": "^7.21.4",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.20.0",
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@babel/core": "^7.23.3",
|
||||
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||
"@babel/preset-typescript": "^7.23.3",
|
||||
"@babel/preset-env": "^7.23.6",
|
||||
"@babel/preset-react": "^7.23.3",
|
||||
"@rollup/plugin-babel": "^6.0.4",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.5",
|
||||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@rollup/plugin-url": "^8.0.2",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-replace": "^5.0.5",
|
||||
"@types/node": "^18.18.12",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"chalk": "^5.3.0",
|
||||
"del-cli": "^5.1.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"lint-staged": "^13.3.0",
|
||||
"nyc": "^15.1.0",
|
||||
"lint-staged": "^13.2.1"
|
||||
"postcss": "^8.4.31",
|
||||
"rollup": "^4.12.0",
|
||||
"rollup-plugin-delete": "^2.0.0",
|
||||
"rollup-plugin-livereload": "^2.0.5",
|
||||
"rollup-plugin-postcss": "^4.0.2",
|
||||
"typescript": "^5.3.2",
|
||||
"puppeteer": "^21.5.2",
|
||||
"mime": "^4.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"ts-jest": "^29.1.2",
|
||||
"@jest/globals": "^29.7.0"
|
||||
},
|
||||
"types": "./types/index.d.ts",
|
||||
"ava": {
|
||||
"workerThreads": false,
|
||||
"files": [
|
||||
"!**/fixtures/**",
|
||||
"!**/util/**",
|
||||
"!**/helpers/**",
|
||||
"!**/recipes/**",
|
||||
"!**/types.ts"
|
||||
],
|
||||
"extensions": {
|
||||
"ts": "module",
|
||||
"js": true
|
||||
},
|
||||
"nodeArguments": [
|
||||
"--loader=ts-node/esm"
|
||||
]
|
||||
"jest": {
|
||||
"preset":"ts-jest/presets/default-esm",
|
||||
"setupFiles": ["./test/setup.js"]
|
||||
}
|
||||
}
|
||||
|
||||
4709
pnpm-lock.yaml
generated
4709
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,64 +0,0 @@
|
||||
This method provides the ability to reference external css/js files for the generated html, and supports adjusting the file loading sequence.
|
||||
|
||||
when using it:
|
||||
|
||||
```js
|
||||
import html from '@rollup/plugin-html';
|
||||
import templateExternalFiles from '@rollup/plugin-html/recipes/external-files';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
|
||||
export default [
|
||||
{
|
||||
input: ['demo/demo.ts'],
|
||||
output: [{ file: 'dist/demo.js' }],
|
||||
plugins: [
|
||||
postcss({
|
||||
extract: 'demo.css',
|
||||
minimize: false,
|
||||
use: ['sass'],
|
||||
extensions: ['.scss', '.css']
|
||||
}),
|
||||
html({
|
||||
title: 'sdk demo page',
|
||||
publicPath: './',
|
||||
fileName: 'demo.html',
|
||||
attributes: { html: { lang: 'zh-cn' } },
|
||||
template: templateExternalFiles([
|
||||
{ type: 'js', file: 'example1.js', pos: 'before' },
|
||||
{ type: 'js', file: 'example2.js', pos: 'before' },
|
||||
{ type: 'js', file: 'example3.js' },
|
||||
{ type: 'js', file: 'example4.js', pos: 'before' },
|
||||
{ type: 'css', file: 'example1.css', pos: 'before' },
|
||||
{ type: 'css', file: 'example2.css', pos: 'before' },
|
||||
{ type: 'css', file: 'example3.css' },
|
||||
{ type: 'css', file: 'example4.css', pos: 'before' }
|
||||
])
|
||||
})
|
||||
]
|
||||
}
|
||||
];
|
||||
```
|
||||
|
||||
The content of the generated html file:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>sdk demo page</title>
|
||||
<link href="./example1.css" rel="stylesheet" />
|
||||
<link href="./example2.css" rel="stylesheet" />
|
||||
<link href="./example4.css" rel="stylesheet" />
|
||||
<link href="./demo.css" rel="stylesheet" />
|
||||
<link href="./example3.css" rel="stylesheet" />
|
||||
</head>
|
||||
<body>
|
||||
<script src="./example1.js"></script>
|
||||
<script src="./example2.js"></script>
|
||||
<script src="./example4.js"></script>
|
||||
<script src="./demo.js"></script>
|
||||
<script src="./example3.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* Provides the ability to reference external css/js files for the generated html
|
||||
* Method source once issues: https://github.com/rollup/plugins/issues/755
|
||||
* @param {Array} externals List of external files.
|
||||
* The format is: [{ type: 'js', file: '//xxxx1.js', pos: 'before' }, { type: 'css', file: '//xxxx1.css' }]
|
||||
*
|
||||
* @return {Function} The templae method required by plugin-html
|
||||
*/
|
||||
export default function htmlTemplate(externals) {
|
||||
return ({ attributes, files, meta, publicPath, title }) => {
|
||||
let scripts = [...(files.js || [])];
|
||||
let links = [...(files.css || [])];
|
||||
|
||||
// externals = [{ type: 'js', file: '//xxxx1.js', pos: 'before' }, { type: 'css', file: '//xxxx1.css' }]
|
||||
if (Array.isArray(externals)) {
|
||||
const beforeLinks = [];
|
||||
const beforeScripts = [];
|
||||
externals.forEach((node) => {
|
||||
let fileList;
|
||||
const isCssFile = node.type === 'css';
|
||||
if (node.pos === 'before') {
|
||||
fileList = isCssFile ? beforeLinks : beforeScripts;
|
||||
} else {
|
||||
fileList = isCssFile ? links : scripts;
|
||||
}
|
||||
fileList.push({ fileName: node.file });
|
||||
});
|
||||
scripts = beforeScripts.concat(scripts);
|
||||
links = beforeLinks.concat(links);
|
||||
}
|
||||
|
||||
scripts = scripts
|
||||
.map(({ fileName }) => {
|
||||
const attrs = makeHtmlAttributes(attributes.script);
|
||||
return `<script src="${publicPath}${fileName}"${attrs}></script>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
links = links
|
||||
.map(({ fileName }) => {
|
||||
const attrs = makeHtmlAttributes(attributes.link);
|
||||
return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const metas = meta
|
||||
.map((input) => {
|
||||
const attrs = makeHtmlAttributes(input);
|
||||
return `<meta${attrs}>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html${makeHtmlAttributes(attributes.html)}>
|
||||
<head>
|
||||
${metas}
|
||||
<title>${title}</title>
|
||||
${links}
|
||||
</head>
|
||||
<body>
|
||||
${scripts}
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
}
|
||||
|
||||
function makeHtmlAttributes(attributes) {
|
||||
if (!attributes) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const keys = Object.keys(attributes);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
return keys.reduce((result, key) => (result += ` ${key}="${attributes[key]}"`), '');
|
||||
}
|
||||
@@ -30,13 +30,14 @@ export function createConfig({ pkg, external = [] }) {
|
||||
},
|
||||
strictDeprecations: true,
|
||||
output: [
|
||||
{
|
||||
format: 'cjs',
|
||||
file: pkg.main,
|
||||
exports: 'named',
|
||||
footer: 'module.exports = Object.assign(exports.default, exports);',
|
||||
sourcemap: true
|
||||
},
|
||||
// TODO: cjs output not supported for now
|
||||
// {
|
||||
// format: 'cjs',
|
||||
// file: pkg.main,
|
||||
// exports: 'named',
|
||||
// footer: 'module.exports = Object.assign(exports.default, exports);',
|
||||
// sourcemap: true
|
||||
// },
|
||||
{
|
||||
format: 'es',
|
||||
file: pkg.module,
|
||||
@@ -57,7 +58,6 @@ export function createConfig({ pkg, external = [] }) {
|
||||
...commonOpts,
|
||||
babelHelpers: "bundled",
|
||||
}),
|
||||
// typescript({ sourceMap: true })]
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
34
src/html-module.ts
Normal file
34
src/html-module.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// The HTML-Module is an internal helper structure to track the processing of an HTML file
|
||||
// This is intended to be serialized into chunk-meta, so it can be cached. (thus keep any functions and circular references out of it)
|
||||
// TODO: Actually making this serialiable (check rollupResolved, node, as we might no longer need them)
|
||||
|
||||
import type {
|
||||
ModuleInfo,
|
||||
ResolvedId,
|
||||
} from 'rollup';
|
||||
|
||||
import type {
|
||||
LoadedReference
|
||||
} from "../types/load.d.ts";
|
||||
import type {DefaultTreeAdapterMap} from "parse5";
|
||||
|
||||
// Internal type
|
||||
export type HtmlImport = LoadedReference & {
|
||||
id: string;
|
||||
resolved: ResolvedId|null;
|
||||
// loaded: ModuleInfo|null;
|
||||
node: DefaultTreeAdapterMap['element'];
|
||||
referenceId: string|null;
|
||||
placeholder: string,
|
||||
index: number;
|
||||
}
|
||||
|
||||
export type HtmlModule = {
|
||||
// TODO might want to impose an own unique id, in case this changes after multiple builds
|
||||
id: string;
|
||||
name: string;
|
||||
importers: Set<string|undefined>,
|
||||
imports: HtmlImport[];
|
||||
assetId?: string|null;
|
||||
document?: DefaultTreeAdapterMap['document'];
|
||||
}
|
||||
547
src/index.ts
547
src/index.ts
@@ -1,133 +1,464 @@
|
||||
import { extname } from "node:path";
|
||||
|
||||
import type { Plugin, NormalizedOutputOptions, OutputBundle, EmittedAsset } from 'rollup';
|
||||
|
||||
import type { RollupHtmlOptions, RollupHtmlTemplateOptions } from '../types/index.d.ts';
|
||||
import type {
|
||||
Plugin,
|
||||
OutputBundle,
|
||||
OutputChunk,
|
||||
OutputAsset,
|
||||
NormalizedOutputOptions,
|
||||
// ModuleInfo,
|
||||
ResolvedId,
|
||||
PreRenderedChunk,
|
||||
RenderedChunk,
|
||||
} from 'rollup';
|
||||
|
||||
const getFiles = (bundle: OutputBundle): RollupHtmlTemplateOptions['files'] => {
|
||||
const result = {} as ReturnType<typeof getFiles>;
|
||||
for (const file of Object.values(bundle)) {
|
||||
const { fileName } = file;
|
||||
const extension = extname(fileName).substring(1);
|
||||
import type {
|
||||
LoadResult,
|
||||
RollupHtmlOptions,
|
||||
LoadNodeCallback,
|
||||
LoadReference, BodyReference, AttributeReference, LoadFunction
|
||||
} from '../types/index.d.ts';
|
||||
|
||||
result[extension] = (result[extension] || []).concat(file);
|
||||
}
|
||||
// createFilter function is a utility that constructs a filter function from include/exclude patterns.
|
||||
import {createFilter} from '@rollup/pluginutils';
|
||||
// parse5 package is used for parsing HTML.
|
||||
import {parse as parseHtml, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5";
|
||||
// magic-string to transform code and keeping a sourcemap aligned
|
||||
import MagicString from "magic-string";
|
||||
|
||||
return result;
|
||||
|
||||
// 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
|
||||
]
|
||||
};
|
||||
|
||||
export const makeHtmlAttributes = (attributes: Record<string, any>): string => {
|
||||
if (!attributes) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const keys = Object.keys(attributes);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
return keys.reduce((result, key) => (result += ` ${key}="${attributes[key]}"`), '');
|
||||
};
|
||||
|
||||
const defaultTemplate = async ({
|
||||
attributes,
|
||||
files,
|
||||
meta,
|
||||
publicPath,
|
||||
title
|
||||
}: RollupHtmlTemplateOptions) => {
|
||||
const scripts = (files.js || [])
|
||||
.map(({ fileName }) => {
|
||||
const attrs = makeHtmlAttributes(attributes.script);
|
||||
return `<script src="${publicPath}${fileName}"${attrs}></script>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const links = (files.css || [])
|
||||
.map(({ fileName }) => {
|
||||
const attrs = makeHtmlAttributes(attributes.link);
|
||||
return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
const metas = meta
|
||||
.map((input) => {
|
||||
const attrs = makeHtmlAttributes(input);
|
||||
return `<meta${attrs}>`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return `
|
||||
<!doctype html>
|
||||
<html${makeHtmlAttributes(attributes.html)}>
|
||||
<head>
|
||||
${metas}
|
||||
<title>${title}</title>
|
||||
${links}
|
||||
</head>
|
||||
<body>
|
||||
${scripts}
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
|
||||
const supportedFormats = ['es', 'esm', 'iife', 'umd'];
|
||||
|
||||
const defaults = {
|
||||
attributes: {
|
||||
link: null,
|
||||
html: { lang: 'en' },
|
||||
script: null
|
||||
},
|
||||
fileName: 'index.html',
|
||||
meta: [{ charset: 'utf-8' }],
|
||||
publicPath: '',
|
||||
template: defaultTemplate,
|
||||
title: 'Rollup Bundle'
|
||||
};
|
||||
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 { attributes, fileName, meta, publicPath, template, title } = Object.assign(
|
||||
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: 'html',
|
||||
name: pluginName,
|
||||
|
||||
async generateBundle(output: NormalizedOutputOptions, bundle: OutputBundle) {
|
||||
if (!supportedFormats.includes(output.format) && !opts.template) {
|
||||
this.warn(
|
||||
`plugin-html: The output format '${
|
||||
output.format
|
||||
}' is not directly supported. A custom \`template\` is probably required. Supported formats include: ${supportedFormats.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
// 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])
|
||||
);
|
||||
},
|
||||
|
||||
if (output.format === 'es') {
|
||||
attributes.script = Object.assign({}, attributes.script, {
|
||||
type: 'module'
|
||||
resolveId: {
|
||||
async handler(specifier,
|
||||
importer,
|
||||
options){
|
||||
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, {
|
||||
skipSelf: false, // defaults to true since rollup 4, and for virtual files this is problematic
|
||||
isEntry: type==='entryChunk',
|
||||
});
|
||||
if(!resolved){
|
||||
throw new Error(`Could not resolve ${sourceId} from ${id}`);
|
||||
}
|
||||
|
||||
const selfInfo = this.getModuleInfo(id);
|
||||
|
||||
let entryName: string|undefined = undefined;
|
||||
const parentName = entryNames.get(id)??selfInfo?.meta[pluginName].name;
|
||||
if(type==='entryChunk'){
|
||||
entryName= posix.join(posix.dirname(parentName),sourceId);
|
||||
entryName = entryName.slice(0,-(posix.extname(entryName).length)); // Cut off the extension (TODO, is this wise?)
|
||||
}
|
||||
|
||||
const importName = (source && selfInfo?.meta[pluginName].name)
|
||||
? makeInlineId(parentName, node, extname(sourceId))
|
||||
: entryName;
|
||||
|
||||
const htmlImport: HtmlImport = {
|
||||
id: <string>sourceId,
|
||||
resolved: resolved,
|
||||
// loaded: loaded,
|
||||
node: el,
|
||||
type,
|
||||
source,
|
||||
referenceId:
|
||||
(resolved && (['chunk','entryChunk'].includes(type!))) ? this.emitFile({
|
||||
type: 'chunk', // Might want to adapt, or make configurable (see LoadType)
|
||||
id: resolved.id,
|
||||
name: importName,
|
||||
importer: id,
|
||||
}) : null,
|
||||
placeholder: `html-import-${crypto.randomBytes(32).toString('base64')}`,
|
||||
index: htmlImports.length,
|
||||
}
|
||||
// if(entryName){
|
||||
// addedEntries.set(resolved.id, entryName);// (we could do this using meta?)
|
||||
// }
|
||||
htmlImports.push(htmlImport);
|
||||
return htmlImport.placeholder;
|
||||
}
|
||||
|
||||
let toLoad: LoadResult | undefined = load? await Promise.resolve(load({
|
||||
node: el,
|
||||
sourceId: id
|
||||
}, loadFunction)) : undefined;
|
||||
|
||||
if (toLoad !== false) {
|
||||
let asParent = (<DefaultTreeAdapterMap['parentNode']>node);
|
||||
if (asParent.childNodes) {
|
||||
nextQueue.push(asParent.childNodes);
|
||||
}
|
||||
}
|
||||
}));
|
||||
nodeQueue = nextQueue.flat();
|
||||
} while (nodeQueue.length > 0);
|
||||
}
|
||||
|
||||
// Beware leak of AST (we're starting MagicString on a parsed and modified version of the HTML file, sourcemappings in the HTML file will be off. (can't add a sourcemap for a html file anyway, unless it is outputted as JS module)
|
||||
// @ts-ignore
|
||||
let htmlJS = new MagicString(serializeHtml(htmlModule.document));
|
||||
htmlJS.replaceAll(/`/g,'\\\`').replaceAll(/\$\{/g,'\\${');
|
||||
|
||||
const moduleImports = [];
|
||||
for(const htmlImport of htmlImports){
|
||||
if(htmlImport.type === 'default') {
|
||||
const assetId: string = `asset${moduleImports.length}`;
|
||||
moduleImports.push(`import ${assetId} from "${htmlImport.id}";`);// TODO: This is just the easy & safe solution. Would prefer to have recognizable names, and reeuse when something is the exact same resource..
|
||||
htmlJS = htmlJS.replace(htmlImport.placeholder, `\${${assetId}}`);// TODO: Should we be worried about windows absolute URLs here?
|
||||
}else{
|
||||
// TODO: this will probably not do for complicated cases ( presumably no other method then emitting the chunk as file, loading its result but excluding it from the output bundle)
|
||||
// html = html.replace(htmlImport.placeholder, htmlImport.loaded?.code||htmlImport.source||'');
|
||||
}
|
||||
}
|
||||
|
||||
// Import all dependencies and wrap the HTML in a `...`, assign to a var and export (escaping any ` characters in the HTML)
|
||||
htmlJS.prepend([
|
||||
...moduleImports,
|
||||
`export const html = \``
|
||||
].join('\n')).append([
|
||||
`\`;`,
|
||||
`export default html;`,
|
||||
].join('\n'));
|
||||
|
||||
const map = htmlJS.generateMap({
|
||||
source: id,
|
||||
file: `${id}.map`,
|
||||
includeContent: true,
|
||||
hires: 'boundary'
|
||||
});
|
||||
|
||||
return {
|
||||
code: htmlJS.toString(),
|
||||
map: map.toString(),
|
||||
meta: moduleMeta,
|
||||
};
|
||||
}
|
||||
},
|
||||
outputOptions(options){
|
||||
return {
|
||||
...options,
|
||||
entryFileNames: (chunkInfo)=>{
|
||||
const 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const files = getFiles(bundle);
|
||||
const source = await template({
|
||||
attributes,
|
||||
bundle,
|
||||
files,
|
||||
meta,
|
||||
publicPath,
|
||||
title
|
||||
});
|
||||
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);
|
||||
|
||||
const htmlFile: EmittedAsset = {
|
||||
type: 'asset',
|
||||
source,
|
||||
name: 'Rollup HTML Asset',
|
||||
fileName
|
||||
};
|
||||
// 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;
|
||||
|
||||
this.emitFile(htmlFile);
|
||||
// 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?)
|
||||
}
|
||||
}
|
||||
|
||||
110
src/loader-mappings.ts
Normal file
110
src/loader-mappings.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
// The loader parses a DOM node and detects which resource (script, style, image, ...) needs to be loaded from it
|
||||
|
||||
import type {
|
||||
NodeMapping,
|
||||
} from '../types/load.d.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)
|
||||
// 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.
|
||||
export const KnownMappings : {[name: string]: NodeMapping} = {
|
||||
externalScript: {
|
||||
tagName: 'script',
|
||||
attr: 'src',
|
||||
loadType: 'entryChunk' // TODO: assuming entryChunk is always the right option for now. However we might want to switch to just chunk and leave it to the rollup to decide if this script should be inlined or not.
|
||||
},
|
||||
inlinedScript: {
|
||||
tagName: 'script',
|
||||
body: true,
|
||||
ext: 'js',
|
||||
loadType: 'chunk'
|
||||
},
|
||||
externalStylesheet: {
|
||||
tagName: 'link',
|
||||
match: {
|
||||
attr: {
|
||||
rel: 'stylesheet'
|
||||
},
|
||||
},
|
||||
attr: 'href',
|
||||
},
|
||||
inlinedStylesheet: {
|
||||
tagName: 'style',
|
||||
body: true,
|
||||
ext: 'css',
|
||||
},
|
||||
|
||||
externalResource: { // i.e favicons.
|
||||
tagName: 'link',
|
||||
match: {
|
||||
attr: {
|
||||
rel: /^(?!.*stylesheet$)/ // Anything that is not rel="stylesheet",
|
||||
}
|
||||
},
|
||||
attr: 'href'
|
||||
// Could probably use finetuning, see possible values: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel
|
||||
},
|
||||
|
||||
externalImage: {
|
||||
tagName: 'img',
|
||||
attr: 'src',
|
||||
},
|
||||
|
||||
link: {
|
||||
tagName: 'a',
|
||||
attr: 'href',
|
||||
},
|
||||
iframe: {
|
||||
tagName: 'iframe',
|
||||
attr: 'src',
|
||||
},
|
||||
|
||||
videoSource: {
|
||||
tagName: 'source',
|
||||
attr: 'src'
|
||||
},
|
||||
subtitle: {
|
||||
tagName: 'track',
|
||||
attr: 'src'
|
||||
},
|
||||
audio: {
|
||||
tagName: 'audio',
|
||||
attr: 'src'
|
||||
},
|
||||
|
||||
portal: {
|
||||
tagName: 'portal',
|
||||
attr: 'src',
|
||||
},
|
||||
|
||||
object: {
|
||||
tagName: 'object',
|
||||
attr: 'data'
|
||||
}
|
||||
}
|
||||
export type KnownMappingTypes = keyof typeof KnownMappings;
|
||||
|
||||
export const defaultMapping: NodeMapping[] = [
|
||||
// Scripts
|
||||
KnownMappings.externalScript,
|
||||
KnownMappings.inlinedScript,
|
||||
|
||||
// Stylesheet
|
||||
KnownMappings.externalStylesheet,
|
||||
KnownMappings.inlinedStylesheet,
|
||||
|
||||
// Images, svgs
|
||||
KnownMappings.externalImage,
|
||||
|
||||
// Links
|
||||
// knownMappings.link,
|
||||
// knownMappings.iframe, // Very unlikely to become a default, but who knows if someone has a valid use for this
|
||||
|
||||
// Media
|
||||
KnownMappings.videoSource,
|
||||
KnownMappings.subtitle,
|
||||
KnownMappings.audio,
|
||||
|
||||
// Misc
|
||||
// knownMappings.portal,// <portal src="..."> An experimental feature to replace valid use cases for iframes? Might want to [look into it...](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/portal)
|
||||
// knownMappings.object,// <object data="..."> Not sure what to do with this, is this still commonly used? Any valid use-case for this? [See MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object)
|
||||
]
|
||||
103
src/loader.ts
Normal file
103
src/loader.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
// The loader parses a DOM node and detects which resource (script, style, image, ...) needs to be loaded from it
|
||||
|
||||
import type {
|
||||
LoadResult,
|
||||
LoadNodeCallback,
|
||||
LoadReference,
|
||||
NodeMapping,
|
||||
AttributeReference, BodyReference, LoadedReference
|
||||
} from '../types/index.d.ts';
|
||||
import {parseFragment as parseHtmlFragment, serialize as serializeHtml, DefaultTreeAdapterMap} from "parse5";
|
||||
|
||||
import {KnownMappings, defaultMapping} from "./loader-mappings.ts";
|
||||
|
||||
/**
|
||||
* Makes a unique but human-readable name from a path within a HTML file.
|
||||
* i.e html.body.script0
|
||||
* @param node
|
||||
*/
|
||||
export function makeHtmlPath(node: DefaultTreeAdapterMap['childNode']){
|
||||
const path = [];
|
||||
let cur = node;
|
||||
while(cur?.parentNode){
|
||||
const parent = cur.parentNode;
|
||||
const asElement = (<DefaultTreeAdapterMap['element']>cur);
|
||||
const similarChildNodes = parent.childNodes?.filter(x=>(<DefaultTreeAdapterMap['element']>x).nodeName == cur.nodeName) || [];
|
||||
const pathName = `${asElement.tagName}${similarChildNodes.length>1? similarChildNodes.indexOf(cur): ''}`;
|
||||
path.unshift(pathName);
|
||||
cur = (<DefaultTreeAdapterMap['childNode']>cur.parentNode);
|
||||
if((<DefaultTreeAdapterMap['element']>cur).tagName==='html'
|
||||
&& (!cur.parentNode || cur.parentNode?.nodeName === '#document')
|
||||
&& (!cur.parentNode || cur.parentNode?.childNodes.length===1)
|
||||
){
|
||||
break; // Break early, don't include 'html0' if we can prevent it
|
||||
}
|
||||
}
|
||||
return path.join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* // TODO check if this works cross platform (windows)
|
||||
* @param sourceId
|
||||
* @param node
|
||||
* @param ext
|
||||
*/
|
||||
export function makeInlineId(sourceId: string, node: DefaultTreeAdapterMap['childNode'], ext = '.js'){
|
||||
return [sourceId, [makeHtmlPath(node), 'js'].join('.')].join('.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a loader function that maps node types and attributes to load operations.
|
||||
*
|
||||
* @param mappings - An array of NodeMapping objects specifying how to map and load different nodes.
|
||||
* @returns A LoadNodeCallback function that can be used to load nodes based on the mappings.
|
||||
*/
|
||||
export function makeLoader(mappings: NodeMapping[] = defaultMapping){
|
||||
const fn : LoadNodeCallback = async function ({node, sourceId}, load){
|
||||
for(const mapping of mappings){
|
||||
|
||||
// Test the mapping for a match
|
||||
if (mapping.tagName && mapping.tagName !== node.tagName) continue; // No match, skip
|
||||
if (mapping.match){
|
||||
if(typeof(mapping.match) === 'function'){
|
||||
if(!mapping.match(node)) continue;
|
||||
}else{
|
||||
if(mapping.match.body && !(node.childNodes?.length>0)) continue; // No match, skip
|
||||
if(mapping.match.attr) {
|
||||
for (const [attrName, attrMatch] of Object.entries(mapping.match.attr)) {
|
||||
if(!node.attrs.find(attr=>{
|
||||
if(attr.name !== attrName) return false;
|
||||
if(typeof(attrMatch) === 'string') return attrMatch === attr.value;
|
||||
if(attrMatch instanceof RegExp) return !!(attrMatch.exec(attr.value));
|
||||
if(typeof(attrMatch) === 'function') return attrMatch(attr.value);
|
||||
})) continue; // No match, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we've gotten this far its a valid mapping. (either inline or a src/href attribute)
|
||||
if((<AttributeReference>mapping).attr){
|
||||
// Mapped on attribute, resolve its src or href (or whatever was returned)
|
||||
const attr = node.attrs.find(attr=>attr.name === (<AttributeReference>mapping).attr);
|
||||
if(!attr) continue ;// No match, skip
|
||||
const placeholder = await load({
|
||||
id: attr.value,
|
||||
type: mapping.loadType||'default', // Use the default export unless explicitely mapped differently
|
||||
});
|
||||
attr.value = placeholder;
|
||||
}else if((<BodyReference>mapping).body){
|
||||
// Mapped as body, use the contents of the DOM element
|
||||
const body = serializeHtml(node); // unlike what you' might expect, this doesn't serialize the <script>-tag itself, only its contents. Which is what we want.
|
||||
if(!body) continue; // Empty body, skip
|
||||
const placeholder = await load({
|
||||
source: body,
|
||||
type: mapping.loadType||'chunk'
|
||||
});
|
||||
node.childNodes = parseHtmlFragment(placeholder).childNodes;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fn;
|
||||
}
|
||||
78
test/basic/__snapshots__/test.js.snap
Normal file
78
test/basic/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,78 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`basic inline-script 1`] = `
|
||||
[
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "script.body.script.js.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"script.body.script.js.js","sources":["../batman.js","../script.html.body.script.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n","\\n import {b} from \\"./batman.js\\";\\n document.body.appendChild(\\n document.createTextNode(\`Inline script including \${b()}\`)\\n );\\n "],"names":[],"mappings":"AAAO,MAAM,CAAC,GAAG,IAAI,QAAQ,CAAC;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;ACCJ,QAAQ,CAAC,IAAI,CAAC,WAAW;AACrC,gBAAgB,QAAQ,CAAC,cAAc,CAAC,CAAC,wBAAwB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,aAAa"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "script.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
document.body.appendChild(
|
||||
document.createTextNode(\`Inline script including \${b()}\`)
|
||||
);
|
||||
//# sourceMappingURL=script.body.script.js.js.map
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`basic simple 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../batman.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n"],"names":[],"mappings":"AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
2
test/basic/fixtures/batman.js
Normal file
2
test/basic/fixtures/batman.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
7
test/basic/fixtures/index.html
Normal file
7
test/basic/fixtures/index.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="./batman.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
12
test/basic/fixtures/script.html
Normal file
12
test/basic/fixtures/script.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import {b} from "./batman.js";
|
||||
document.body.appendChild(
|
||||
document.createTextNode(`Inline script including ${b()}`)
|
||||
);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
47
test/basic/test.js
Normal file
47
test/basic/test.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import {join, dirname} from "node:path";
|
||||
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import { rollup } from "rollup";
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
describe("basic", ()=> {
|
||||
test('simple', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
await bundle.close();
|
||||
debugPrintOutput('simple', code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('inline-script', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'script.html',
|
||||
plugins: [
|
||||
html({}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
await bundle.close();
|
||||
debugPrintOutput('inline-script', code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||
// Watch mode tests would be its own dir
|
||||
// ...
|
||||
|
||||
});
|
||||
36
test/evaluated-web-bundle/__snapshots__/test.js.snap
Normal file
36
test/evaluated-web-bundle/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,36 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`evaluated-web-bundle 1`] = `
|
||||
{
|
||||
"console": [
|
||||
"[log] Bootstrapped, ready to go!",
|
||||
"[log] Test my sourcemap: App started",
|
||||
"[log] Test my sourcemap: App tick",
|
||||
"[log] Test my sourcemap: App ended",
|
||||
],
|
||||
"errors": [],
|
||||
"html": "<html lang="en"><head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
Test bundle!
|
||||
</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%20%20%20%20%3Ctitle%3EHTML5%20Logo%3C%2Ftitle%3E%20%20%20%20%3Cpath%20d%3D%22M108.4%200h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206%2023h-20.3V0h63.7v23H229v46h-23M259.5%200h24.1l14.8%2024.3L313.2%200h24.1v69h-23V34.8l-16.1%2024.8l-16.1-24.8v34.2h-22.6M348.7%200h23v46.2h32.6V69h-55.6%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23e44d26%22%20d%3D%22M107.6%20471l-33-370.4h362.8l-33%20370.2L255.7%20512%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23f16529%22%20d%3D%22M256%20480.5V131H404.3L376%20447%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23ebebeb%22%20d%3D%22M142%20176.3h114v45.4h-64.2l4.2%2046.5h60v45.3H154.4M156.4%20336.3H202l3.2%2036.3%2050.8%2013.6v47.4l-93.2-26%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M369.6%20176.3H255.8v45.4h109.6M361.3%20268.2H255.8v45.4h56l-5.3%2059-50.7%2013.6v47.2l93-25.8%22%2F%3E%3C%2Fsvg%3E">
|
||||
|
||||
<title>I'm cool!</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"><div style="align-self: center"><b>App ended</b></div></div>
|
||||
<script src="index.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
"requestsFailed": [],
|
||||
"responses": [
|
||||
"200 http://localhost/index.html",
|
||||
"200 http://localhost/index.js",
|
||||
"200 http://localhost/app.js",
|
||||
],
|
||||
}
|
||||
`;
|
||||
13
test/evaluated-web-bundle/fixtures/app.mjs
Normal file
13
test/evaluated-web-bundle/fixtures/app.mjs
Normal file
@@ -0,0 +1,13 @@
|
||||
export async function app({root}){
|
||||
|
||||
const states = ['started', 'tick', 'ended'];
|
||||
|
||||
for(let state of states){
|
||||
const text = `App ${state}`;
|
||||
console.log(`Test my sourcemap: ${text}`);
|
||||
root.innerHTML = `<div style="align-self: center"><b>${text}</b></div>`;
|
||||
await new Promise((resolve,reject)=>
|
||||
setTimeout(()=>resolve(), 10)
|
||||
);
|
||||
}
|
||||
}
|
||||
BIN
test/evaluated-web-bundle/fixtures/assets/assistant.ttf
Normal file
BIN
test/evaluated-web-bundle/fixtures/assets/assistant.ttf
Normal file
Binary file not shown.
8
test/evaluated-web-bundle/fixtures/assets/logo-sq.svg
Normal file
8
test/evaluated-web-bundle/fixtures/assets/logo-sq.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<title>HTML5 Logo</title>
|
||||
<path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/>
|
||||
<path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/>
|
||||
<path fill="#f16529" d="M256 480.5V131H404.3L376 447"/>
|
||||
<path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/>
|
||||
<path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 693 B |
18
test/evaluated-web-bundle/fixtures/index.hbs
Normal file
18
test/evaluated-web-bundle/fixtures/index.hbs
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
Test bundle!
|
||||
</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" href="./assets/logo-sq.svg">
|
||||
|
||||
{{{ head }}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">Here the app should load!</div>
|
||||
<script src="./index.mjs" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
test/evaluated-web-bundle/fixtures/index.mjs
Normal file
27
test/evaluated-web-bundle/fixtures/index.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
// Dynamically loads libraries and bootstraps the application
|
||||
(async ()=>{
|
||||
// Add a loader here if any
|
||||
const root = document.getElementById('root')
|
||||
if(root) root.innerHTML= `<div style="align-self: center">My app has loaded!!</div>`;
|
||||
|
||||
try {
|
||||
// Load app
|
||||
const [
|
||||
appModule,
|
||||
] = await Promise.all([
|
||||
import("./app.mjs"),
|
||||
]);
|
||||
|
||||
console.log("Bootstrapped, ready to go!");
|
||||
|
||||
// Wait for DOM to be ready
|
||||
if(document.readyState === 'loading') {
|
||||
await new Promise((resolve)=>document.addEventListener('DOMContentLoaded', resolve));
|
||||
}
|
||||
|
||||
// Start the app!
|
||||
await appModule.app({root});
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}
|
||||
})()
|
||||
49
test/evaluated-web-bundle/test.js
Normal file
49
test/evaluated-web-bundle/test.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import urlPlugin from "@rollup/plugin-url";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
import {runBrowserTest} from "../util/browser-test.ts";
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
import handlebars from "handlebars";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
const defaultAssetInclude = [
|
||||
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||
'**/*.(webm|mp4)',// video
|
||||
];
|
||||
|
||||
test('evaluated-web-bundle', async ()=>{
|
||||
const out = await runBrowserTest({
|
||||
input: 'index.hbs',
|
||||
treeshake: 'smallest',
|
||||
plugins: [
|
||||
html({
|
||||
transform(src) {
|
||||
return handlebars.compile(src)({
|
||||
head: `<title>I'm cool!</title>`
|
||||
});
|
||||
}
|
||||
}),
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
}),
|
||||
],
|
||||
}, {
|
||||
path: 'index.html',
|
||||
},{
|
||||
dir: 'output', // Output all files
|
||||
format: 'es', // iifi and cjs should be added to tests
|
||||
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||
chunkFileNames: '[name].js',
|
||||
entryFileNames: '[name].[extname]',
|
||||
assetFileNames: '[name].[extname]',
|
||||
});
|
||||
expect(out).toMatchSnapshot();
|
||||
});
|
||||
0
test/fixtures/batman.js
vendored
0
test/fixtures/batman.js
vendored
2
test/fixtures/joker.js
vendored
2
test/fixtures/joker.js
vendored
@@ -1,2 +0,0 @@
|
||||
// eslint-disable-next-line
|
||||
import style from './joker.css';
|
||||
2
test/fixtures/robin.js
vendored
2
test/fixtures/robin.js
vendored
@@ -1,2 +0,0 @@
|
||||
// eslint-disable-next-line
|
||||
import * as batman from './batman.js';
|
||||
69
test/js-import/__snapshots__/test.js.snap
Normal file
69
test/js-import/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,69 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`js-import 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "var asset0 = "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E";
|
||||
|
||||
const html = \`<html><head>
|
||||
<link rel="icon" href="\${asset0}">
|
||||
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->
|
||||
<!-- <link rel="stylesheet" href="./joker.css">-->
|
||||
</head>
|
||||
<body>
|
||||
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->
|
||||
<!--<script src="./batman.js" type="module"></script>-->
|
||||
|
||||
|
||||
</body></html>\`;
|
||||
|
||||
function render(){
|
||||
return html;
|
||||
}
|
||||
|
||||
export { render };
|
||||
//# sourceMappingURL=index.js.map
|
||||
",
|
||||
"fileName": "index.js",
|
||||
"map": SourceMap {
|
||||
"file": "index.js",
|
||||
"mappings": "AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACRN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../icon.svg",
|
||||
"../index.html",
|
||||
"../index.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export default "data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E"",
|
||||
"<html>
|
||||
<head>
|
||||
<link rel="icon" href="./icon.svg">
|
||||
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->
|
||||
<!-- <link rel="stylesheet" href="./joker.css">-->
|
||||
</head>
|
||||
<body>
|
||||
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->
|
||||
<!--<script src="./batman.js" type="module"></script>-->
|
||||
</body>
|
||||
</html>
|
||||
",
|
||||
"import html from "./index.html"
|
||||
|
||||
export function render(){
|
||||
return html;
|
||||
}
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"index.js","sources":["../icon.svg","../index.html","../index.js"],"sourcesContent":["export default \\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E\\"","<html>\\n <head>\\n <link rel=\\"icon\\" href=\\"./icon.svg\\">\\n <!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->\\n<!-- <link rel=\\"stylesheet\\" href=\\"./joker.css\\">-->\\n </head>\\n <body>\\n <!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->\\n <!--<script src=\\"./batman.js\\" type=\\"module\\"></script>-->\\n </body>\\n</html>\\n","import html from \\"./index.html\\"\\n\\nexport function render(){\\n return html;\\n}\\n"],"names":[],"mappings":"AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACRN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;"}",
|
||||
},
|
||||
]
|
||||
`;
|
||||
2
test/js-import/fixtures/batman.js
Normal file
2
test/js-import/fixtures/batman.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
3
test/js-import/fixtures/icon.svg
Normal file
3
test/js-import/fixtures/icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path style="fill:none;stroke:#00ff0d;stroke-width:5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M4.1 14.72 16 26.31 28.38 5.09"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 244 B |
11
test/js-import/fixtures/index.html
Normal file
11
test/js-import/fixtures/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="icon" href="./icon.svg">
|
||||
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->
|
||||
<!-- <link rel="stylesheet" href="./joker.css">-->
|
||||
</head>
|
||||
<body>
|
||||
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->
|
||||
<!--<script src="./batman.js" type="module"></script>-->
|
||||
</body>
|
||||
</html>
|
||||
5
test/js-import/fixtures/index.js
Normal file
5
test/js-import/fixtures/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import html from "./index.html"
|
||||
|
||||
export function render(){
|
||||
return html;
|
||||
}
|
||||
47
test/js-import/test.js
Normal file
47
test/js-import/test.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import { rollup } from "rollup";
|
||||
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
import handlebars from "handlebars";
|
||||
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
import urlPlugin from "@rollup/plugin-url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
const defaultAssetInclude = [
|
||||
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||
'**/*.(webm|mp4)',// video
|
||||
];
|
||||
|
||||
|
||||
test('js-import', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.js',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
// Test with assets
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
limit: Number.MAX_SAFE_INTEGER,// Always inline things
|
||||
}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('js-import',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||
// Watch mode tests would be its own dir
|
||||
// ...
|
||||
36
test/jsx-web-app/__snapshots__/test.js.snap
Normal file
36
test/jsx-web-app/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,36 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`web-bundle 1`] = `
|
||||
{
|
||||
"console": [
|
||||
"[info] %cDownload the React DevTools for a better development experience: https://reactjs.org/link/react-devtools font-weight:bold",
|
||||
"[log] Bootstrapped, ready to go!",
|
||||
"[log] Test my sourcemap: tick",
|
||||
"[log] Test my sourcemap: ended",
|
||||
],
|
||||
"errors": [],
|
||||
"html": "<html lang="en"><head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
Test bundle!
|
||||
</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20512%20512%22%3E%20%20%20%20%3Ctitle%3EHTML5%20Logo%3C%2Ftitle%3E%20%20%20%20%3Cpath%20d%3D%22M108.4%200h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206%2023h-20.3V0h63.7v23H229v46h-23M259.5%200h24.1l14.8%2024.3L313.2%200h24.1v69h-23V34.8l-16.1%2024.8l-16.1-24.8v34.2h-22.6M348.7%200h23v46.2h32.6V69h-55.6%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23e44d26%22%20d%3D%22M107.6%20471l-33-370.4h362.8l-33%20370.2L255.7%20512%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23f16529%22%20d%3D%22M256%20480.5V131H404.3L376%20447%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23ebebeb%22%20d%3D%22M142%20176.3h114v45.4h-64.2l4.2%2046.5h60v45.3H154.4M156.4%20336.3H202l3.2%2036.3%2050.8%2013.6v47.4l-93.2-26%22%2F%3E%20%20%20%20%3Cpath%20fill%3D%22%23fff%22%20d%3D%22M369.6%20176.3H255.8v45.4h109.6M361.3%20268.2H255.8v45.4h56l-5.3%2059-50.7%2013.6v47.2l93-25.8%22%2F%3E%3C%2Fsvg%3E">
|
||||
|
||||
<title>I'm cool!</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"><div style="align-self: center;"><b>ended</b></div></div>
|
||||
<script src="index.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
"requestsFailed": [],
|
||||
"responses": [
|
||||
"200 http://localhost/index.html",
|
||||
"200 http://localhost/index.js",
|
||||
"200 http://localhost/app.js",
|
||||
],
|
||||
}
|
||||
`;
|
||||
36
test/jsx-web-app/fixtures/app.tsx
Normal file
36
test/jsx-web-app/fixtures/app.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import {createRoot} from "react-dom/client";
|
||||
import {StrictMode, useEffect, useState} from "react";
|
||||
|
||||
const states = ['started', 'tick', 'ended'];
|
||||
export function App(){
|
||||
const [state, setState] = useState(states[0])
|
||||
|
||||
useEffect(()=>{
|
||||
let timeout: any;
|
||||
let nextState = states[states.indexOf(state)+1];
|
||||
if(nextState) {
|
||||
timeout = setTimeout(() => {
|
||||
console.log(`Test my sourcemap: ${nextState}`);
|
||||
setState(nextState)
|
||||
}, 10);
|
||||
}
|
||||
|
||||
return ()=>{
|
||||
if(timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
}, [state])
|
||||
|
||||
return (<div style={{alignSelf: "center"}}>
|
||||
<b>{state}</b>
|
||||
</div>);
|
||||
}
|
||||
|
||||
export async function start({root: rootContainer}: {root: HTMLElement}){
|
||||
if(!rootContainer) throw new Error("Missing root element");
|
||||
const root = createRoot(rootContainer);
|
||||
root.render(<StrictMode>
|
||||
<App />
|
||||
</StrictMode>);
|
||||
}
|
||||
8
test/jsx-web-app/fixtures/assets/logo-sq.svg
Normal file
8
test/jsx-web-app/fixtures/assets/logo-sq.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<title>HTML5 Logo</title>
|
||||
<path d="M108.4 0h23v22.8h21.2V0h23v69h-23V46h-21v23h-23.2M206 23h-20.3V0h63.7v23H229v46h-23M259.5 0h24.1l14.8 24.3L313.2 0h24.1v69h-23V34.8l-16.1 24.8l-16.1-24.8v34.2h-22.6M348.7 0h23v46.2h32.6V69h-55.6"/>
|
||||
<path fill="#e44d26" d="M107.6 471l-33-370.4h362.8l-33 370.2L255.7 512"/>
|
||||
<path fill="#f16529" d="M256 480.5V131H404.3L376 447"/>
|
||||
<path fill="#ebebeb" d="M142 176.3h114v45.4h-64.2l4.2 46.5h60v45.3H154.4M156.4 336.3H202l3.2 36.3 50.8 13.6v47.4l-93.2-26"/>
|
||||
<path fill="#fff" d="M369.6 176.3H255.8v45.4h109.6M361.3 268.2H255.8v45.4h56l-5.3 59-50.7 13.6v47.2l93-25.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 693 B |
14
test/jsx-web-app/fixtures/babel.config.js
Normal file
14
test/jsx-web-app/fixtures/babel.config.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export default {
|
||||
presets: [
|
||||
["@babel/preset-env", {
|
||||
shippedProposals: true,
|
||||
}],
|
||||
["@babel/preset-typescript", {
|
||||
|
||||
}],
|
||||
["@babel/preset-react", {
|
||||
development: process.env.BABEL_ENV === "development",
|
||||
runtime: "automatic"
|
||||
}]
|
||||
],
|
||||
}
|
||||
18
test/jsx-web-app/fixtures/index.hbs
Normal file
18
test/jsx-web-app/fixtures/index.hbs
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>
|
||||
Test bundle!
|
||||
</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" href="./assets/logo-sq.svg">
|
||||
|
||||
{{{ head }}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root">Here the app should load!</div>
|
||||
<script src="./index.mjs" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
test/jsx-web-app/fixtures/index.mjs
Normal file
27
test/jsx-web-app/fixtures/index.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
// Dynamically loads libraries and bootstraps the application
|
||||
(async ()=>{
|
||||
// Add a loader here if any
|
||||
const root = document.getElementById('root')
|
||||
if(root) root.innerHTML= `<div style="align-self: center">My app has loaded!!</div>`;
|
||||
|
||||
try {
|
||||
// Load app
|
||||
const [
|
||||
appModule,
|
||||
] = await Promise.all([
|
||||
import("./app.tsx"),
|
||||
]);
|
||||
|
||||
console.log("Bootstrapped, ready to go!");
|
||||
|
||||
// Wait for DOM to be ready
|
||||
if(document.readyState === 'loading') {
|
||||
await new Promise((resolve)=>document.addEventListener('DOMContentLoaded', resolve));
|
||||
}
|
||||
|
||||
// Start the app!
|
||||
await appModule.start({root});
|
||||
}catch(err){
|
||||
console.error(err);
|
||||
}
|
||||
})()
|
||||
16
test/jsx-web-app/fixtures/tsconfig.json
Normal file
16
test/jsx-web-app/fixtures/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"target": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"module": "ESNext",
|
||||
"strict": true,
|
||||
"paths":{
|
||||
"react": ["./node_modules/@types/react"]
|
||||
},
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnreachableCode": true,
|
||||
"allowUnusedLabels": true,
|
||||
"noUnusedLocals": false
|
||||
},
|
||||
}
|
||||
85
test/jsx-web-app/test.js
Normal file
85
test/jsx-web-app/test.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect, jest} from "@jest/globals";
|
||||
|
||||
// Rollup * plugins
|
||||
import { rollup } from "rollup";
|
||||
import urlPlugin from "@rollup/plugin-url";
|
||||
import nodeResolve from "@rollup/plugin-node-resolve";
|
||||
import babelPlugin from "@rollup/plugin-babel";
|
||||
import commonJsPlugin from "@rollup/plugin-commonjs";
|
||||
import typescriptPlugin from "@rollup/plugin-typescript";
|
||||
import replacePlugin from "@rollup/plugin-replace";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
import {runBrowserTest} from "../util/browser-test.ts";
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
import handlebars from "handlebars";
|
||||
// import {debugPrintOutput, getCode, runBrowserTest} from "../util/index.ts";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
const defaultAssetInclude = [
|
||||
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||
'**/*.(webm|mp4)',// video
|
||||
];
|
||||
|
||||
|
||||
jest.setTimeout(30*1000);// Bundling react + typescript is getting heavy
|
||||
test('web-bundle', async () => {
|
||||
const out = await runBrowserTest({
|
||||
input: 'index.hbs',
|
||||
treeshake: 'smallest',
|
||||
plugins: [
|
||||
html({
|
||||
transform(src) {
|
||||
return handlebars.compile(src)({
|
||||
head: `<title>I'm cool!</title>`
|
||||
});
|
||||
}
|
||||
}),
|
||||
nodeResolve({
|
||||
extensions: ['.js', '.mjs', '.jsx', '.ts', '.tsx'],
|
||||
browser: true,
|
||||
}),
|
||||
commonJsPlugin({
|
||||
}),
|
||||
typescriptPlugin({
|
||||
sourceMap: true,
|
||||
// exclude: 'node_modules/**',
|
||||
noEmitOnError: true,
|
||||
outputToFilesystem: false,
|
||||
noForceEmit: true,
|
||||
jsx: "preserve",
|
||||
}),
|
||||
babelPlugin({
|
||||
extensions: ['.js', '.mjs', '.jsx', '.ts', '.tsx'],
|
||||
babelHelpers: "bundled",
|
||||
}),
|
||||
replacePlugin({
|
||||
preventAssignment: false,
|
||||
'process.env.NODE_ENV': process.env.NODE_ENV==='production'?`'${process.env.NODE_ENV}'` : '"development"'
|
||||
}),
|
||||
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
}),
|
||||
],
|
||||
}, {
|
||||
path: 'index.html'
|
||||
},{
|
||||
dir: 'output', // Output all files
|
||||
format: 'es', // iifi and cjs should be added to tests
|
||||
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||
chunkFileNames: '[name].js',
|
||||
entryFileNames: '[name].[extname]',
|
||||
assetFileNames: '[name].[extname]',
|
||||
});
|
||||
expect(out).toMatchSnapshot();
|
||||
|
||||
// const code = await getCode(bundle, output);
|
||||
// debugPrintOutput('jsx-web-app',code);
|
||||
});
|
||||
|
||||
54
test/live-reload/__snapshots__/test.js.snap
Normal file
54
test/live-reload/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,54 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`live-reload 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "
|
||||
(function(l, r) { if (!l || l.getElementById('livereloadscript')) return; r = l.createElement('script'); r.async = 1; r.src = '//' + (self.location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; r.id = 'livereloadscript'; l.getElementsByTagName('head')[0].appendChild(r) })(self.document);
|
||||
const test = ()=>{
|
||||
return \`I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this files \\'tries\\' to include all allowed forms of 'it'\`;
|
||||
};
|
||||
console.log(test());
|
||||
|
||||
export { test };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": ";;AAAY,MAAC,IAAI,GAAG,IAAI;AACxB,IAAI,OAAO,CAAC,eAAe,EAAE,8CAA8C,CAAC,iEAAiE,CAAC,CAAC;AAC/I,EAAC;AACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const test = ()=>{
|
||||
return \`I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this files \\'tries\\' to include all allowed forms of 'it'\`;
|
||||
}
|
||||
console.log(test());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../batman.js"],"sourcesContent":["export const test = ()=>{\\n return \`I'm \\"annoying\\" \${\\"in case we need to test \\\\\`string\\\\\` escaping.\\"}. Hence this files \\\\'tries\\\\' to include all allowed forms of 'it'\`;\\n}\\nconsole.log(test());\\n"],"names":[],"mappings":";;AAAY,MAAC,IAAI,GAAG,IAAI;AACxB,IAAI,OAAO,CAAC,eAAe,EAAE,8CAA8C,CAAC,iEAAiE,CAAC,CAAC;AAC/I,EAAC;AACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
4
test/live-reload/fixtures/batman.js
Normal file
4
test/live-reload/fixtures/batman.js
Normal file
@@ -0,0 +1,4 @@
|
||||
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());
|
||||
7
test/live-reload/fixtures/index.html
Normal file
7
test/live-reload/fixtures/index.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="./batman.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
36
test/live-reload/test.js
Normal file
36
test/live-reload/test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import {rollup} from "rollup";
|
||||
import liveReload from "rollup-plugin-livereload";
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test('live-reload', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
liveReload({
|
||||
verbose: false// this oddly enough prevents it from actually starting the liveserver, which would've left the test to wait indefinatly to close
|
||||
})
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
await bundle.close();// Make sure live-reload closes itself
|
||||
debugPrintOutput('live-reload',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||
// Watch mode tests would be its own dir
|
||||
// ...
|
||||
125
test/multi-entry/__snapshots__/test.js.snap
Normal file
125
test/multi-entry/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,125 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`multi-entry 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "admin/batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../../admin/batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "admin/batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../../admin/batman.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n"],"names":[],"mappings":"AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "admin/index.body.script0.js.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"index.body.script0.js.js","sources":["../../app/admin-deps.js","../../admin/index.html.body.script0.js"],"sourcesContent":["export function adminDeps(){\\n return \\"robin!\\";\\n}\\n","\\n import {bootstrap} from \\"../app/app.js\\"\\n import {adminDeps} from \\"../app/admin-deps.js\\";\\n bootstrap(document.getElementById('root'), adminDeps());\\n "],"names":[],"mappings":";;AAAO,SAAS,SAAS,EAAE;AAC3B,IAAI,OAAO,QAAQ,CAAC;AACpB;;ACCY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "admin/index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">import { b as bootstrap } from '../app.js';
|
||||
|
||||
function adminDeps(){
|
||||
return "robin!";
|
||||
}
|
||||
|
||||
bootstrap(document.getElementById('root'), adminDeps());
|
||||
//# sourceMappingURL=index.body.script0.js.js.map
|
||||
</script>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
{
|
||||
"code": "const bootstrap = (el,deps = [])=>{
|
||||
el.innerHtml = \`
|
||||
<div>I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this file \\'tries\\' to include all allowed forms of 'it'</div>
|
||||
<div>Deps: \${deps}</div>
|
||||
\`;
|
||||
};
|
||||
|
||||
export { bootstrap as b };
|
||||
//# sourceMappingURL=app.js.map
|
||||
",
|
||||
"fileName": "app.js",
|
||||
"map": SourceMap {
|
||||
"file": "app.js",
|
||||
"mappings": "AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB,4BAA4B,EAAE,8CAA8C,CAAC;AAC7E,mBAAmB,EAAE,IAAI,CAAC;AAC1B,IAAI,CAAC,CAAC;AACN;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../app/app.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const bootstrap = (el,deps = [])=>{
|
||||
el.innerHtml = \`
|
||||
<div>I'm "annoying" \${"in case we need to test \\\`string\\\` escaping."}. Hence this file \\'tries\\' to include all allowed forms of 'it'</div>
|
||||
<div>Deps: \${deps}</div>
|
||||
\`;
|
||||
}
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "app.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"app.js","sources":["../app/app.js"],"sourcesContent":["export const bootstrap = (el,deps = [])=>{\\n el.innerHtml = \`\\n <div>I'm \\"annoying\\" \${\\"in case we need to test \\\\\`string\\\\\` escaping.\\"}. Hence this file \\\\'tries\\\\' to include all allowed forms of 'it'</div>\\n <div>Deps: \${deps}</div>\\n \`;\\n}\\n"],"names":[],"mappings":"AAAY,MAAC,SAAS,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG;AACzC,IAAI,EAAE,CAAC,SAAS,GAAG,CAAC;AACpB,4BAA4B,EAAE,8CAA8C,CAAC;AAC7E,mBAAmB,EAAE,IAAI,CAAC;AAC1B,IAAI,CAAC,CAAC;AACN;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.body.script.js.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"index.body.script.js.js","sources":["../index.html.body.script.js"],"sourcesContent":["\\n import {bootstrap} from \\"./app/app.js\\"\\n bootstrap(document.getElementById('root'), \\"<none>\\");\\n "],"names":[],"mappings":";;AAEY,SAAS,CAAC,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">import { b as bootstrap } from './app.js';
|
||||
|
||||
bootstrap(document.getElementById('root'), "<none>");
|
||||
//# sourceMappingURL=index.body.script.js.js.map
|
||||
</script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
2
test/multi-entry/fixtures/admin/batman.js
Normal file
2
test/multi-entry/fixtures/admin/batman.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
13
test/multi-entry/fixtures/admin/index.html
Normal file
13
test/multi-entry/fixtures/admin/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">
|
||||
import {bootstrap} from "../app/app.js"
|
||||
import {adminDeps} from "../app/admin-deps.js";
|
||||
bootstrap(document.getElementById('root'), adminDeps());
|
||||
</script>
|
||||
<script src="./batman.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
test/multi-entry/fixtures/app/admin-deps.js
Normal file
3
test/multi-entry/fixtures/app/admin-deps.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export function adminDeps(){
|
||||
return "robin!";
|
||||
}
|
||||
6
test/multi-entry/fixtures/app/app.js
Normal file
6
test/multi-entry/fixtures/app/app.js
Normal file
@@ -0,0 +1,6 @@
|
||||
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>
|
||||
`;
|
||||
}
|
||||
11
test/multi-entry/fixtures/index.html
Normal file
11
test/multi-entry/fixtures/index.html
Normal file
@@ -0,0 +1,11 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">
|
||||
import {bootstrap} from "./app/app.js"
|
||||
bootstrap(document.getElementById('root'), "<none>");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
34
test/multi-entry/test.js
Normal file
34
test/multi-entry/test.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import {resolve, join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import { rollup } from "rollup";
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test('multi-entry', async () => {
|
||||
const bundle = await rollup({
|
||||
input: {
|
||||
['index']: 'index.html',
|
||||
['admin/index']: resolve(__dirname,'fixtures','admin/index.html'),
|
||||
},
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('multi-entry',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||
// Watch mode tests would be its own dir
|
||||
// ...
|
||||
3
test/rewrite-url/__snapshots__/test.js.snap
Normal file
3
test/rewrite-url/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`rewrite-url 1`] = `undefined`;
|
||||
5
test/rewrite-url/fixtures/admin/app.js
Normal file
5
test/rewrite-url/fixtures/admin/app.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export const bootstrap = (el,deps = [])=>{
|
||||
el.innerHtml = `
|
||||
<div>load the app</div>
|
||||
`;
|
||||
}
|
||||
8
test/rewrite-url/fixtures/admin/index.html
Normal file
8
test/rewrite-url/fixtures/admin/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
test/rewrite-url/fixtures/index.html
Normal file
8
test/rewrite-url/fixtures/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="./admin/app.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
62
test/rewrite-url/test.js
Normal file
62
test/rewrite-url/test.js
Normal file
@@ -0,0 +1,62 @@
|
||||
import {resolve, join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import {runBrowserTest} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test('rewrite-url', async () => {
|
||||
const out = await runBrowserTest({
|
||||
input: {
|
||||
['index']: 'index.html',
|
||||
['admin/index']: resolve(__dirname,'fixtures','admin/index.html'),
|
||||
['admin/app']: resolve(__dirname,'fixtures','admin/app.js'),
|
||||
},
|
||||
plugins: [
|
||||
html({
|
||||
rewriteUrl(relative, {rootPath, from}){
|
||||
return `/${rootPath}`;
|
||||
}
|
||||
}),
|
||||
],
|
||||
},{
|
||||
filterOutput:{
|
||||
// TODO: Currently only need the "await getCode(bundle, output);" as output
|
||||
},
|
||||
path: '/admin'
|
||||
}, {
|
||||
dir: 'output', // Output all files
|
||||
format: 'es', // iifi and cjs should be added to tests
|
||||
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||
});
|
||||
expect(out.code).toMatchSnapshot(); // Snapshot the result code
|
||||
|
||||
// const bundle = await rollup({
|
||||
// input: {
|
||||
// ['index']: 'index.html',
|
||||
// ['admin/index']: resolve(__dirname,'fixtures','admin/index.html'),
|
||||
// ['admin/app']: resolve(__dirname,'fixtures','admin/app.js'),
|
||||
// },
|
||||
// plugins: [
|
||||
// html({
|
||||
// rewriteUrl(relative, {rootPath, from}){
|
||||
// return `/${rootPath}`;
|
||||
// }
|
||||
// }),
|
||||
// ]
|
||||
// });
|
||||
// const code = await getCode(bundle, output);
|
||||
// debugPrintOutput('rewrite-url',code);
|
||||
// t.snapshot(code);
|
||||
});
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||
// Watch mode tests would be its own dir
|
||||
// ...
|
||||
2
test/setup.js
Normal file
2
test/setup.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// Replace the jest console with the normal one (jest makes console.log too verbose)
|
||||
global.console = await import("node:console");
|
||||
@@ -1,357 +0,0 @@
|
||||
# Snapshot report for `test/test.js`
|
||||
|
||||
The actual snapshot is saved in `test.js.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## default options
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `(function (factory) {␊
|
||||
typeof define === 'function' && define.amd ? define(factory) :␊
|
||||
factory();␊
|
||||
})((function () { 'use strict';␊
|
||||
␊
|
||||
␊
|
||||
␊
|
||||
}));␊
|
||||
`,
|
||||
fileName: 'batman.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html lang="en">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<title>Rollup Bundle</title>␊
|
||||
␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman.js"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## options
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `(function (factory) {␊
|
||||
typeof define === 'function' && define.amd ? define(factory) :␊
|
||||
factory();␊
|
||||
})((function () { 'use strict';␊
|
||||
␊
|
||||
␊
|
||||
␊
|
||||
}));␊
|
||||
`,
|
||||
fileName: 'batman.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'batman.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html lang="en">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width">␊
|
||||
<title>Batcave</title>␊
|
||||
␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batcave/batman.js"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## iife
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `(function () {␊
|
||||
'use strict';␊
|
||||
␊
|
||||
␊
|
||||
␊
|
||||
})();␊
|
||||
`,
|
||||
fileName: 'batman.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html lang="en">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<title>Rollup Bundle</title>␊
|
||||
␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman.js"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## esm
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `␊
|
||||
`,
|
||||
fileName: 'batman.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html lang="en">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<title>Rollup Bundle</title>␊
|
||||
␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman.js" type="module"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## unsupported output format
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `'use strict';␊
|
||||
␊
|
||||
`,
|
||||
fileName: 'batman.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html lang="en">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<title>Rollup Bundle</title>␊
|
||||
␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="batman.js" type="module"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
> Snapshot 2
|
||||
|
||||
[
|
||||
{
|
||||
code: 'EMPTY_BUNDLE',
|
||||
message: 'Generated an empty chunk: "batman".',
|
||||
names: [
|
||||
'batman',
|
||||
],
|
||||
toString: Function {},
|
||||
},
|
||||
{
|
||||
code: 'PLUGIN_WARNING',
|
||||
message: 'plugin-html: The output format \'cjs\' is not directly supported. A custom `template` is probably required. Supported formats include: es, esm, iife, umd',
|
||||
plugin: 'html',
|
||||
toString: Function {},
|
||||
},
|
||||
]
|
||||
|
||||
## css
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `(function (factory) {␊
|
||||
typeof define === 'function' && define.amd ? define(factory) :␊
|
||||
factory();␊
|
||||
})((function () { 'use strict';␊
|
||||
␊
|
||||
␊
|
||||
␊
|
||||
}));␊
|
||||
`,
|
||||
fileName: 'joker.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'joker.css',
|
||||
map: undefined,
|
||||
source: Buffer @Uint8Array [
|
||||
2a207b20 77696474 683a2031 3030253b 207d0a
|
||||
],
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html lang="en">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<title>Rollup Bundle</title>␊
|
||||
<link href="joker.css" rel="stylesheet">␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="joker.js" type="module"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## attributes
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `(function (factory) {␊
|
||||
typeof define === 'function' && define.amd ? define(factory) :␊
|
||||
factory();␊
|
||||
})((function () { 'use strict';␊
|
||||
␊
|
||||
␊
|
||||
␊
|
||||
}));␊
|
||||
`,
|
||||
fileName: 'joker.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'joker.css',
|
||||
map: undefined,
|
||||
source: Buffer @Uint8Array [
|
||||
2a207b20 77696474 683a2031 3030253b 207d0a
|
||||
],
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html batsignal="on" lang="bat">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<title>Rollup Bundle</title>␊
|
||||
<link href="joker.css" rel="stylesheet" data-vilian="joker">␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="joker.js" defer="true"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## imports
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `(function (factory) {␊
|
||||
typeof define === 'function' && define.amd ? define(factory) :␊
|
||||
factory();␊
|
||||
})((function () { 'use strict';␊
|
||||
␊
|
||||
␊
|
||||
␊
|
||||
}));␊
|
||||
`,
|
||||
fileName: 'robin.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: `␊
|
||||
<!doctype html>␊
|
||||
<html lang="en">␊
|
||||
<head>␊
|
||||
<meta charset="utf-8">␊
|
||||
<title>Rollup Bundle</title>␊
|
||||
␊
|
||||
</head>␊
|
||||
<body>␊
|
||||
<script src="robin.js" type="module"></script>␊
|
||||
</body>␊
|
||||
</html>`,
|
||||
},
|
||||
]
|
||||
|
||||
## template
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
{
|
||||
code: `(function (factory) {␊
|
||||
typeof define === 'function' && define.amd ? define(factory) :␊
|
||||
factory();␊
|
||||
})((function () { 'use strict';␊
|
||||
␊
|
||||
␊
|
||||
␊
|
||||
}));␊
|
||||
`,
|
||||
fileName: 'batman.js',
|
||||
map: null,
|
||||
source: undefined,
|
||||
},
|
||||
{
|
||||
code: undefined,
|
||||
fileName: 'index.html',
|
||||
map: undefined,
|
||||
source: '<html><body><main></main></body></html>',
|
||||
},
|
||||
]
|
||||
Binary file not shown.
49
test/templating/__snapshots__/test.js.snap
Normal file
49
test/templating/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`handlebars 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../batman.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n"],"names":[],"mappings":"AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
<meta data-test="a">
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
2
test/templating/fixtures/batman.js
Normal file
2
test/templating/fixtures/batman.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
3
test/templating/fixtures/icon.svg
Normal file
3
test/templating/fixtures/icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path style="fill:none;stroke:#00ff0d;stroke-width:5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M4.1 14.72 16 26.31 28.38 5.09"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 244 B |
8
test/templating/fixtures/index.hbs
Normal file
8
test/templating/fixtures/index.hbs
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta data-test="{{a}}"/>
|
||||
</head>
|
||||
<body>
|
||||
<script src="./batman.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
8
test/templating/fixtures/index.html
Normal file
8
test/templating/fixtures/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="icon" href="./icon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<script src="./batman.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
1
test/templating/fixtures/joker.css
Normal file
1
test/templating/fixtures/joker.css
Normal file
@@ -0,0 +1 @@
|
||||
* { width: 100%; }
|
||||
36
test/templating/test.js
Normal file
36
test/templating/test.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import { rollup } from "rollup";
|
||||
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
import handlebars from "handlebars";
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
test('handlebars', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.hbs',
|
||||
plugins: [
|
||||
html({
|
||||
transform(src){
|
||||
return handlebars.compile(src)({a:'a'})
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('handlebars',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||
// Watch mode tests would be its own dir
|
||||
// ...
|
||||
124
test/test.js
124
test/test.js
@@ -1,124 +0,0 @@
|
||||
import {join, dirname} from "node:path";
|
||||
|
||||
import test from "ava";
|
||||
import { rollup } from "rollup";
|
||||
import css from "rollup-plugin-postcss";
|
||||
|
||||
import { getCode } from "./util/test.js";
|
||||
|
||||
import html from "../src/index.ts";
|
||||
|
||||
// const read = (file = 'index.html') => readFileSync(join('output/', file), 'utf-8');
|
||||
|
||||
const output = { dir: 'output', format: 'umd' };
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
test.serial('default options', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'batman.js',
|
||||
plugins: [html()]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('options', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'batman.js',
|
||||
plugins: [
|
||||
html({
|
||||
fileName: 'batman.html',
|
||||
publicPath: 'batcave/',
|
||||
title: 'Batcave',
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'minimum-scale=1, initial-scale=1, width=device-width' }
|
||||
]
|
||||
})
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('iife', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'batman.js',
|
||||
plugins: [html()]
|
||||
});
|
||||
const code = await getCode(bundle, { dir: 'output', format: 'iife' }, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('esm', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'batman.js',
|
||||
plugins: [html()]
|
||||
});
|
||||
const code = await getCode(bundle, { dir: 'output', format: 'es' }, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('unsupported output format', async (t) => {
|
||||
const warnings = [];
|
||||
const bundle = await rollup({
|
||||
input: 'batman.js',
|
||||
onwarn: (warning) => warnings.push(warning),
|
||||
plugins: [html()]
|
||||
});
|
||||
const code = await getCode(bundle, { dir: 'output', format: 'cjs' }, true);
|
||||
t.snapshot(code);
|
||||
t.snapshot(warnings);
|
||||
});
|
||||
|
||||
test.serial('css', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'joker.js',
|
||||
plugins: [css({ extract: true }), html()]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('attributes', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'joker.js',
|
||||
plugins: [
|
||||
css({ extract: true }),
|
||||
html({
|
||||
attributes: {
|
||||
html: { batsignal: 'on', lang: 'bat' },
|
||||
link: { 'data-vilian': 'joker' },
|
||||
script: { defer: true }
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('imports', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'robin.js',
|
||||
plugins: [html()]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
|
||||
test.serial('template', async (t) => {
|
||||
const bundle = await rollup({
|
||||
input: 'batman.js',
|
||||
plugins: [
|
||||
html({
|
||||
template: () => '<html><body><main></main></body></html>'
|
||||
})
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle, output, true);
|
||||
t.snapshot(code);
|
||||
});
|
||||
97
test/url-plugin/__snapshots__/test.js.snap
Normal file
97
test/url-plugin/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,97 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`url-plugin copied-assets 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../batman.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n"],"names":[],"mappings":"AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
<link rel="icon" href="fb585fdb6db313c9.svg">
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`url-plugin inlined-assets 1`] = `
|
||||
[
|
||||
{
|
||||
"code": "const b = ()=>'batman';
|
||||
console.log(b());
|
||||
|
||||
export { b };
|
||||
//# sourceMappingURL=batman.js.map
|
||||
",
|
||||
"fileName": "batman.js",
|
||||
"map": SourceMap {
|
||||
"file": "batman.js",
|
||||
"mappings": "AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../batman.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "batman.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"batman.js","sources":["../batman.js"],"sourcesContent":["export const b = ()=>'batman';\\nconsole.log(b());\\n"],"names":[],"mappings":"AAAY,MAAC,CAAC,GAAG,IAAI,SAAS;AAC9B,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;;;;"}",
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E">
|
||||
</head>
|
||||
<body>
|
||||
<script src="batman.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
]
|
||||
`;
|
||||
2
test/url-plugin/fixtures/batman.js
Normal file
2
test/url-plugin/fixtures/batman.js
Normal file
@@ -0,0 +1,2 @@
|
||||
export const b = ()=>'batman';
|
||||
console.log(b());
|
||||
3
test/url-plugin/fixtures/icon.svg
Normal file
3
test/url-plugin/fixtures/icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path style="fill:none;stroke:#00ff0d;stroke-width:5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M4.1 14.72 16 26.31 28.38 5.09"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 244 B |
8
test/url-plugin/fixtures/index.html
Normal file
8
test/url-plugin/fixtures/index.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<link rel="icon" href="./icon.svg">
|
||||
</head>
|
||||
<body>
|
||||
<script src="./batman.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
test/url-plugin/fixtures/output/fb585fdb6db313c9.svg
Normal file
3
test/url-plugin/fixtures/output/fb585fdb6db313c9.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
|
||||
<path style="fill:none;stroke:#00ff0d;stroke-width:5;stroke-linecap:square;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" d="M4.1 14.72 16 26.31 28.38 5.09"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 244 B |
65
test/url-plugin/test.js
Normal file
65
test/url-plugin/test.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import { rollup } from "rollup";
|
||||
import urlPlugin from "@rollup/plugin-url";
|
||||
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
const defaultAssetInclude = [
|
||||
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||
'**/*.(webm|mp4)',// video
|
||||
];
|
||||
|
||||
describe("url-plugin", ()=>{
|
||||
test('copied-assets', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
limit: 0,// Never inline something
|
||||
}),
|
||||
],
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('copied-assets',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
|
||||
|
||||
test('inlined-assets', async () => {
|
||||
const bundle = await rollup({
|
||||
input: 'index.html',
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
urlPlugin({
|
||||
include: defaultAssetInclude,
|
||||
limit: Number.MAX_SAFE_INTEGER,// Always inline things
|
||||
}),
|
||||
]
|
||||
});
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('inlined-assets',code);
|
||||
expect(code).toMatchSnapshot();
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
// TODO various parameters
|
||||
// - format: cjs, iifi, ...
|
||||
// - sourcemap: inline, false, (and the various exotic sourcemap options)
|
||||
// Watch mode tests would be its own dir
|
||||
// ...
|
||||
69
test/util/browser-test.ts
Normal file
69
test/util/browser-test.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {Plugin, InputPluginOption, RollupOptions, OutputOptions, RollupOutput} from "rollup";
|
||||
import {TestOptions as BrowserTestOptions, TestOutput as PuppeteerTestOutput} from "./puppeteer-run-test.js";
|
||||
import { rollup } from "rollup";
|
||||
import serveTest, {LogCallback} from "./serve-test.ts";
|
||||
import {getCode, TestOutput} from "./code-output.ts";
|
||||
import {defaultOutput} from "./default-output.ts";
|
||||
|
||||
export interface OutputFilterOptions {
|
||||
html?: boolean
|
||||
console?: ('log'|'error'|'warn')[] | true
|
||||
errors?: boolean, // again don't know possible values
|
||||
responses?: boolean, // interesting to see what other values were requested
|
||||
requestsFailed?: boolean, // will probably also be replicated into console errors, but helpful to have if imports werent found
|
||||
}
|
||||
export interface BrowserTestInput extends BrowserTestOptions{
|
||||
log?: LogCallback;
|
||||
/**
|
||||
* Optionally specify what to filter from the output
|
||||
*/
|
||||
filterOutput?: OutputFilterOptions;
|
||||
}
|
||||
|
||||
|
||||
export interface BrowserTestOutput extends PuppeteerTestOutput{
|
||||
code: TestOutput[];
|
||||
}
|
||||
|
||||
export async function runBrowserTest(
|
||||
build: RollupOptions,
|
||||
test?: BrowserTestInput | false,
|
||||
output?: OutputOptions
|
||||
) : Promise<Partial<BrowserTestOutput>>{
|
||||
const resolvedPlugins = await Promise.resolve(build.plugins||null);
|
||||
let pluginsArray : InputPluginOption[] = [];
|
||||
if(resolvedPlugins && resolvedPlugins instanceof Array){
|
||||
pluginsArray = resolvedPlugins
|
||||
}else if(resolvedPlugins){
|
||||
pluginsArray = [resolvedPlugins];
|
||||
}
|
||||
|
||||
let testOutput: Partial<BrowserTestOutput> = {};
|
||||
const bundle = await rollup({
|
||||
...build,
|
||||
plugins: [
|
||||
...pluginsArray,
|
||||
// TODO check if browser output is requested (either for snapshot or for testing)
|
||||
...(test? [serveTest({
|
||||
// TODO: intercept output from the serveTest? (and include as one bit in output options below, for snapshotting)
|
||||
...test,
|
||||
log: test.log ?? console.log,
|
||||
onResult: (output)=>{
|
||||
testOutput = {...testOutput, ...output};
|
||||
}
|
||||
})]: [])
|
||||
]
|
||||
});
|
||||
|
||||
// TODO make configurable?
|
||||
|
||||
if(output){
|
||||
testOutput.code = await getCode(bundle);
|
||||
}else{
|
||||
const generated = await bundle.generate(defaultOutput);
|
||||
}
|
||||
await bundle.close();
|
||||
|
||||
return testOutput
|
||||
|
||||
}
|
||||
27
test/util/code-output.ts
Normal file
27
test/util/code-output.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type {RollupBuild, OutputOptions, OutputAsset, OutputChunk, SourceMap} from "rollup";
|
||||
import {defaultOutput} from "./default-output.ts";
|
||||
|
||||
export interface TestOutput{
|
||||
code: string,
|
||||
fileName: string,
|
||||
source: any,
|
||||
map: any
|
||||
}
|
||||
|
||||
export const getCode = async (bundle: RollupBuild, outputOptions: OutputOptions = defaultOutput): Promise<TestOutput[]> => {
|
||||
const { output } = await bundle.generate(outputOptions || { format: 'cjs', exports: 'auto' });
|
||||
|
||||
return output.sort((a,b)=> {
|
||||
if(a.fileName === b.fileName && (<OutputAsset>a).source !== (<OutputAsset>b).source){ return (<OutputAsset>a).source<(<OutputAsset>b).source?-1:1}
|
||||
return a.fileName < b.fileName ? -1 : (a.fileName > b.fileName? 1 : 0);
|
||||
}).map(chunk=> {
|
||||
const { code, map } = (<OutputChunk>chunk);
|
||||
const { fileName, source } = (<OutputAsset>chunk);
|
||||
return {
|
||||
code,
|
||||
fileName,
|
||||
source,
|
||||
map
|
||||
};
|
||||
});
|
||||
};
|
||||
5
test/util/debug-mode.ts
Normal file
5
test/util/debug-mode.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import inspector from 'node:inspector';
|
||||
|
||||
export function isInDebugMode() {
|
||||
return (inspector.url() !== undefined) || process.env.DEBUG;
|
||||
}
|
||||
11
test/util/default-output.ts
Normal file
11
test/util/default-output.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type {OutputOptions} from "rollup";
|
||||
|
||||
export const defaultOutput : OutputOptions = {
|
||||
dir: 'output', // Output all files
|
||||
format: 'es', // iifi and cjs should be added to tests
|
||||
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||
// Prevent hashes from being added (and screw up the snapshots)
|
||||
chunkFileNames: '[name].js',
|
||||
entryFileNames: '[name].js',
|
||||
assetFileNames: '[name].[extname]',
|
||||
};
|
||||
8
test/util/index.ts
Normal file
8
test/util/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
// TODO: this should be the main module used, other should be imported manually if exceptions are needed?
|
||||
export * from "./browser-test.ts";
|
||||
|
||||
export * from "./code-output.ts";
|
||||
export * from "./print-code-output.ts";
|
||||
export * from "./serve-test.ts";
|
||||
|
||||
// export * from './misc.js';
|
||||
@@ -1,27 +1,8 @@
|
||||
// This is still from the old rollup plugin we forked from. For now not used.
|
||||
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
|
||||
/**
|
||||
* @param {import('rollup').RollupBuild} bundle
|
||||
* @param {import('rollup').OutputOptions} [outputOptions]
|
||||
*/
|
||||
export const getCode = async (bundle, outputOptions, allFiles = false) => {
|
||||
const { output } = await bundle.generate(outputOptions || { format: 'cjs', exports: 'auto' });
|
||||
|
||||
if (allFiles) {
|
||||
return output.map(({ code, fileName, source, map }) => {
|
||||
return {
|
||||
code,
|
||||
fileName,
|
||||
source,
|
||||
map
|
||||
};
|
||||
});
|
||||
}
|
||||
const [{ code }] = output;
|
||||
return code;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {import('rollup').RollupBuild} bundle
|
||||
* @param {import('rollup').OutputOptions} [outputOptions]
|
||||
32
test/util/print-code-output.ts
Normal file
32
test/util/print-code-output.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import process from "node:process";
|
||||
import chalk from "chalk";
|
||||
|
||||
import {TestOutput} from "./code-output.ts";
|
||||
|
||||
export const debugPrintOutput = async (header: string, files: TestOutput[]) => {
|
||||
const out = [];
|
||||
|
||||
const headFn = chalk.bgCyan;
|
||||
const headPadding = header.split('').map(x=>'#').join('');
|
||||
out.push(...[
|
||||
headFn(`##${headPadding}##`),
|
||||
headFn(`# ${header} #`),
|
||||
headFn(`##${headPadding}##`),
|
||||
]);
|
||||
|
||||
const fileHeadFn = chalk.blue;
|
||||
const fileContentFn = chalk.blackBright;
|
||||
out.push(...(files.map(file=>{
|
||||
return [
|
||||
fileHeadFn(`${file.fileName}:`),
|
||||
fileContentFn(`${file.code??file.source}`),
|
||||
'',
|
||||
]
|
||||
}).flat()));
|
||||
|
||||
out.push(...[
|
||||
headFn(`##${headPadding}##`),
|
||||
]);
|
||||
|
||||
process.env.DEBUG? console.log(out.join('\n')) : null;
|
||||
};
|
||||
133
test/util/puppeteer-run-test.ts
Normal file
133
test/util/puppeteer-run-test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Puppeteer + from-memory devServer rollup plugin to open the result in a webpage en output the result
|
||||
* (after an optional series of commands to the puppeteer Page)
|
||||
*/
|
||||
|
||||
|
||||
import puppeteer, {Page} from "puppeteer";
|
||||
import {fileURLToPath, URL} from "node:url";
|
||||
import {isInDebugMode} from "./debug-mode.ts";
|
||||
|
||||
|
||||
export type PageTestCallback = (page: Page)=>Promise<void>;
|
||||
|
||||
export interface TestOptions {
|
||||
path: string
|
||||
cb: PageTestCallback
|
||||
replaceHost: boolean
|
||||
replaceHostWith?: string
|
||||
}
|
||||
const defaultOptions: Partial<TestOptions> = {
|
||||
path: 'index.html',
|
||||
cb: async (page: Page)=>{
|
||||
await page.waitForNetworkIdle({});
|
||||
},
|
||||
replaceHost: true,
|
||||
replaceHostWith: `http://localhost`,
|
||||
}
|
||||
export interface TestOutput{
|
||||
html: string,
|
||||
console: string[],
|
||||
errors: string[],
|
||||
responses: string[],
|
||||
requestsFailed: string[],
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a page in a puppeteer browser and return the resulting HTML and logmessages produced.
|
||||
* Optionally a callback can be provided to simulate user interactions on the page before returning the HTML
|
||||
* When DEBUG mode is detected, puppeteer headless mode will be disabled allowing you to inspect the page if you set a breakpoint
|
||||
*
|
||||
* @param opts
|
||||
* @param hostUrl
|
||||
*/
|
||||
export async function puppeteerRunTest(opts: Partial<TestOptions>, hostUrl: string){
|
||||
const options : TestOptions = (<TestOptions>{
|
||||
...defaultOptions,
|
||||
...opts,
|
||||
});
|
||||
const {
|
||||
path,
|
||||
cb,
|
||||
replaceHost,
|
||||
replaceHostWith,
|
||||
} = options;
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: isInDebugMode()? false : 'new',
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
let output : TestOutput = {
|
||||
html: '',
|
||||
console: [],
|
||||
errors: [],
|
||||
responses: [],
|
||||
requestsFailed: []
|
||||
};
|
||||
|
||||
let errored = false;
|
||||
|
||||
try {
|
||||
// Track requests, errors and console
|
||||
page.on('console', message => {
|
||||
let [type, text] = [message.type(), message.text()];
|
||||
if (replaceHost) {
|
||||
text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||
}
|
||||
output.console?.push(`[${type}] ${text}`);
|
||||
}).on('pageerror', ({message}) => {
|
||||
let text = message;
|
||||
if (replaceHost) {
|
||||
text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||
}
|
||||
output.errors?.push(text);
|
||||
}).on('response', response => {
|
||||
let [status, url] = [response.status(), response.url()]
|
||||
if (replaceHost) {
|
||||
url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||
}
|
||||
output.responses?.push(`${status} ${url}`);
|
||||
}).on('requestfailed', request => {
|
||||
let [failure, url] = [request.failure()?.errorText, request.url()];
|
||||
if (replaceHost) {
|
||||
failure = failure?.replaceAll(hostUrl, replaceHostWith!);
|
||||
url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||
}
|
||||
output.requestsFailed?.push(`${failure} ${url}`);
|
||||
});
|
||||
|
||||
const url = new URL(path??'', hostUrl);
|
||||
await page.goto(url.href);
|
||||
|
||||
if (!cb) {
|
||||
await page.waitForNetworkIdle({});
|
||||
} else {
|
||||
await cb(page);
|
||||
}
|
||||
const htmlHandle = await page.$('html');
|
||||
const html = await page.evaluate(html => html?.outerHTML ?? html?.innerHTML, htmlHandle);
|
||||
|
||||
// Add the final html
|
||||
output.html = html || '';
|
||||
|
||||
return output;
|
||||
}catch(err){
|
||||
errored = true;
|
||||
throw err;
|
||||
}finally{
|
||||
if(isInDebugMode() && !errored){
|
||||
console.log(`DEBUG MODE ENABLED, Close the puppeteer browsertab to continue!\n${import.meta.url}:144`);
|
||||
await new Promise((resolve)=>{
|
||||
page.on('close', ()=>{
|
||||
console.log("Page closed");
|
||||
resolve(null);
|
||||
})
|
||||
});
|
||||
}else{
|
||||
await page.close();
|
||||
}
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
286
test/util/serve-test.ts
Normal file
286
test/util/serve-test.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Puppeteer + from-memory devServer rollup plugin to open the result in a webpage en output the result
|
||||
* (after an optional series of commands to the puppeteer Page)
|
||||
*/
|
||||
|
||||
|
||||
import {puppeteerRunTest, PageTestCallback, TestOutput} from "./puppeteer-run-test.ts";
|
||||
import {isInDebugMode} from "./debug-mode.ts";
|
||||
|
||||
import {resolve, posix} from "node:path";
|
||||
import fs from "node:fs/promises";
|
||||
import type {Stats} from "node:fs";
|
||||
|
||||
import { createServer as createHttpsServer } from 'https'
|
||||
import { createServer} from 'http'
|
||||
|
||||
import { Mime } from 'mime/lite'
|
||||
import standardTypes from 'mime/types/standard.js'
|
||||
import otherTypes from 'mime/types/other.js'
|
||||
|
||||
|
||||
import type {NormalizedOutputOptions, OutputAsset, OutputBundle, OutputChunk, Plugin} from 'rollup';
|
||||
|
||||
import type {
|
||||
IncomingHttpHeaders, OutgoingHttpHeaders,
|
||||
IncomingMessage, ServerResponse,
|
||||
Server
|
||||
} from 'http'
|
||||
import type { ServerOptions } from 'https'
|
||||
|
||||
|
||||
type TypeMap = {
|
||||
[key: string]: string[];
|
||||
};
|
||||
|
||||
type ErrorCodeException = Error & {code: string};
|
||||
export type TestResultCallback = (output: TestOutput)=>void;
|
||||
export type LogCallback = (...args: string[])=>void;
|
||||
|
||||
|
||||
export interface ServeTestOptions {
|
||||
/**
|
||||
* Change the path to be opened when the test is started
|
||||
* Remember to start with a slash, e.g. `'/different/page'`
|
||||
*/
|
||||
path?: string
|
||||
|
||||
|
||||
/**
|
||||
* Fallback to serving from a specified srcDir, this allows setting breakpoints on sourcecode and test the sourcemaps
|
||||
*/
|
||||
srcDir?: string|boolean;
|
||||
|
||||
/**
|
||||
* A callback to manually take control of the page and simulate user interactions
|
||||
*/
|
||||
cb?: PageTestCallback;
|
||||
|
||||
/**
|
||||
* Set to `true` to return index.html (200) instead of error page (404)
|
||||
* or path to fallback page
|
||||
*/
|
||||
historyApiFallback?: boolean | string
|
||||
|
||||
/**
|
||||
* Change the host of the server (default: `'localhost'`)
|
||||
*/
|
||||
host?: string
|
||||
|
||||
/**
|
||||
* Change the port that the server will listen on (default: `10001`)
|
||||
*/
|
||||
port?: number | string
|
||||
|
||||
/**
|
||||
* By default server will be served over HTTP (https: `false`). It can optionally be served over HTTPS.
|
||||
*/
|
||||
https?: ServerOptions | false
|
||||
|
||||
/**
|
||||
* Set custom response headers
|
||||
*/
|
||||
headers?:
|
||||
| IncomingHttpHeaders
|
||||
| OutgoingHttpHeaders
|
||||
| {
|
||||
// i.e. Parameters<OutgoingMessage["setHeader"]>
|
||||
[name: string]: number | string | ReadonlyArray<string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Set custom mime types, usage https://github.com/broofa/mime#mimedefinetypemap-force--false
|
||||
*/
|
||||
mimeTypes?: TypeMap
|
||||
|
||||
/**
|
||||
* Execute function after server has begun listening
|
||||
*/
|
||||
onListening?: (server: Server) => void
|
||||
|
||||
|
||||
}
|
||||
|
||||
export interface RollupServeTestOptions extends ServeTestOptions{
|
||||
/**
|
||||
* A callback to run when a test has been run
|
||||
*/
|
||||
onResult?: TestResultCallback;
|
||||
|
||||
/**
|
||||
* Callback to log messages
|
||||
*/
|
||||
log?: LogCallback;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Serve your rolled up bundle like webpack-dev-server
|
||||
* @param {import('..').RollupServeOptions} options
|
||||
*/
|
||||
export default function serveTest (options: RollupServeTestOptions ): Plugin {
|
||||
const mime = new Mime(standardTypes, otherTypes)
|
||||
const testOptions = {
|
||||
port: 0,
|
||||
headers: {},
|
||||
historyApiFallback: true,
|
||||
srcDir: '', // Serve source dir as fallback (for sourcemaps / debugging)
|
||||
onListening: function noop (){},
|
||||
...options||{},
|
||||
https: options.https??false,
|
||||
mimeTypes: options.mimeTypes? mime.define(options.mimeTypes, true): false
|
||||
}
|
||||
|
||||
let server : Server;
|
||||
let bundle : OutputBundle = {};
|
||||
|
||||
const logTest = (msg: string, mode: 'info'|'warn' = 'info')=>{
|
||||
if(isInDebugMode()){
|
||||
console.log(msg);
|
||||
}
|
||||
const modeColor = {
|
||||
green: 32,
|
||||
info: 34,
|
||||
warn: 33,
|
||||
}[mode];
|
||||
testOptions.log?.(`\u001b[${modeColor}m${msg}\u001b[0m`);
|
||||
}
|
||||
|
||||
const requestListener = async (request: IncomingMessage, response: ServerResponse) => {
|
||||
// Remove querystring
|
||||
const unsafePath = decodeURI(request.url!.split('?')[0])
|
||||
|
||||
// Don't allow path traversal
|
||||
const urlPath = posix.normalize(unsafePath)
|
||||
|
||||
for(const [key, value] of Object.entries((<OutgoingHttpHeaders>testOptions.headers))){
|
||||
response.setHeader(key, value!);
|
||||
}
|
||||
|
||||
function urlToFilePath(url:string){
|
||||
return url[0]==='/'?url.slice(1):url;
|
||||
}
|
||||
let filePath = urlToFilePath(urlPath);
|
||||
let absPath: string | undefined = undefined;
|
||||
let stats: Stats | undefined = undefined;
|
||||
|
||||
if(!bundle[filePath]){
|
||||
if(testOptions.srcDir || testOptions.srcDir===''){
|
||||
try{
|
||||
absPath = resolve(<string>testOptions.srcDir||'',filePath);
|
||||
stats = await fs.stat(absPath);
|
||||
}catch(err){
|
||||
// File not found
|
||||
}
|
||||
}
|
||||
if(!(stats?.isFile()) && testOptions.historyApiFallback) {
|
||||
const fallbackPath = typeof testOptions.historyApiFallback === 'string'
|
||||
? testOptions.historyApiFallback
|
||||
: '/index.html';
|
||||
if(bundle[urlToFilePath(fallbackPath)]){
|
||||
filePath = urlToFilePath(fallbackPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mimeType = mime.getType(filePath!);
|
||||
if(bundle[filePath]) {
|
||||
let file: OutputChunk | OutputAsset = bundle[filePath];
|
||||
const content = (<OutputChunk>file).code || (<OutputAsset>file).source; // Todo might need to read a source file;
|
||||
response.writeHead(200, {'Content-Type': mimeType || 'text/plain'});
|
||||
response.end(content, 'utf-8');
|
||||
logTest(`[200] ${request.url}`);
|
||||
return;
|
||||
}else if(stats?.isFile()){
|
||||
response.writeHead(200, {
|
||||
'Content-Type': mimeType || 'text/plain',
|
||||
'Content-Length': stats.size,
|
||||
'Last-Modified': stats.mtime.toUTCString()
|
||||
});
|
||||
const content = await fs.readFile(absPath!);
|
||||
response.end(content);
|
||||
response.end();
|
||||
logTest(`[200] ${request.url} (src)`);
|
||||
}else{
|
||||
response.writeHead(404)
|
||||
response.end(
|
||||
'404 Not Found' + '\n\n' + filePath,
|
||||
'utf-8'
|
||||
)
|
||||
|
||||
logTest(`[404] ${request.url}`, "warn");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function closeServerOnTermination () {
|
||||
const terminationSignals = ['SIGINT', 'SIGTERM', 'SIGQUIT', 'SIGHUP']
|
||||
terminationSignals.forEach(signal => {
|
||||
process.on(signal, () => {
|
||||
if (server) {
|
||||
server.close()
|
||||
process.exit()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// release previous server instance if rollup is reloading configuration in watch mode
|
||||
// @ts-ignore
|
||||
if (server) {
|
||||
server.close()
|
||||
} else {
|
||||
closeServerOnTermination()
|
||||
}
|
||||
|
||||
// If HTTPS options are available, create an HTTPS server
|
||||
server = testOptions.https
|
||||
? createHttpsServer(testOptions.https, requestListener)
|
||||
: createServer(requestListener)
|
||||
server.listen(
|
||||
typeof(testOptions.port)==='string'? Number.parseInt(testOptions.port):testOptions.port,
|
||||
testOptions.host,
|
||||
undefined,
|
||||
() => testOptions.onListening?.(server)
|
||||
)
|
||||
|
||||
testOptions.port = (<any>server.address())?.port ?? testOptions.port;
|
||||
|
||||
// Assemble url for error and info messages
|
||||
const url = (testOptions.https ? 'https' : 'http') + '://' + (testOptions.host || 'localhost') + ':' + testOptions.port
|
||||
|
||||
// Handle common server errors
|
||||
server.on('error', e => {
|
||||
if ((<ErrorCodeException>e).code === 'EADDRINUSE') {
|
||||
console.error(url + ' is in use, either stop the other server or use a different port.')
|
||||
process.exit()
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
})
|
||||
|
||||
let first = true
|
||||
|
||||
return {
|
||||
name: 'serve',
|
||||
generateBundle: {
|
||||
order: 'post',
|
||||
async handler(options, output){
|
||||
bundle = output;
|
||||
if (first) {
|
||||
first = false
|
||||
|
||||
const testOutput = await puppeteerRunTest({
|
||||
...testOptions
|
||||
}, url);
|
||||
|
||||
testOptions.onResult?.(testOutput);
|
||||
}
|
||||
}
|
||||
},
|
||||
closeBundle (){
|
||||
// Done with the bundle
|
||||
}
|
||||
}
|
||||
}
|
||||
45
test/util/test.d.ts
vendored
45
test/util/test.d.ts
vendored
@@ -1,45 +0,0 @@
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import type { RollupBuild, OutputOptions, OutputChunk, OutputAsset } from 'rollup';
|
||||
import type { Assertions } from 'ava';
|
||||
|
||||
interface GetCode {
|
||||
(bundle: RollupBuild, outputOptions?: OutputOptions | null, allFiles?: false): Promise<string>;
|
||||
(bundle: RollupBuild, outputOptions: OutputOptions | null | undefined, allFiles: true): Promise<
|
||||
Array<{
|
||||
code: OutputChunk['code'] | undefined;
|
||||
fileName: OutputChunk['fileName'] | OutputAsset['fileName'];
|
||||
source: OutputAsset['source'] | undefined;
|
||||
}>
|
||||
>;
|
||||
}
|
||||
|
||||
export const getCode: GetCode;
|
||||
|
||||
export function getFiles(
|
||||
bundle: RollupBuild,
|
||||
outputOptions?: OutputOptions
|
||||
): Promise<
|
||||
{
|
||||
fileName: string;
|
||||
content: any;
|
||||
}[]
|
||||
>;
|
||||
|
||||
export function evaluateBundle(bundle: RollupBuild): Promise<Pick<NodeModule, 'exports'>>;
|
||||
|
||||
export function getImports(bundle: RollupBuild): Promise<string[]>;
|
||||
|
||||
export function getResolvedModules(bundle: RollupBuild): Promise<Record<string, string>>;
|
||||
|
||||
export function onwarn(warning: string | any): void;
|
||||
|
||||
export function testBundle(
|
||||
t: Assertions,
|
||||
bundle: RollupBuild,
|
||||
options: { inject: Record<string, any>; options: Record<string, any> }
|
||||
): Promise<{
|
||||
code: string;
|
||||
error?: any;
|
||||
result?: any;
|
||||
module: Pick<NodeModule, 'exports'>;
|
||||
}>;
|
||||
47
test/watch/__snapshots__/test.js.snap
Normal file
47
test/watch/__snapshots__/test.js.snap
Normal file
@@ -0,0 +1,47 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`watch 1`] = `
|
||||
[
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "index.html",
|
||||
"map": undefined,
|
||||
"source": "<html><head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="watched-file.js" type="module"></script>
|
||||
|
||||
|
||||
</body></html>",
|
||||
},
|
||||
{
|
||||
"code": "const a = 2; // If i show up as a changed file, then the watch test has gone wrong!
|
||||
|
||||
export { a };
|
||||
//# sourceMappingURL=watched-file.js.map
|
||||
",
|
||||
"fileName": "watched-file.js",
|
||||
"map": SourceMap {
|
||||
"file": "watched-file.js",
|
||||
"mappings": "AACgB,MAAC,CAAC,GAAG,EAAE;;;;",
|
||||
"names": [],
|
||||
"sources": [
|
||||
"../watched-file.js",
|
||||
],
|
||||
"sourcesContent": [
|
||||
"
|
||||
export const a = 2; // If i show up as a changed file, then the watch test has gone wrong!
|
||||
",
|
||||
],
|
||||
"version": 3,
|
||||
},
|
||||
"source": undefined,
|
||||
},
|
||||
{
|
||||
"code": undefined,
|
||||
"fileName": "watched-file.js.map",
|
||||
"map": undefined,
|
||||
"source": "{"version":3,"file":"watched-file.js","sources":["../watched-file.js"],"sourcesContent":["\\n export const a = 2; // If i show up as a changed file, then the watch test has gone wrong!\\n "],"names":[],"mappings":"AACgB,MAAC,CAAC,GAAG,EAAE;;;;"}",
|
||||
},
|
||||
]
|
||||
`;
|
||||
7
test/watch/fixtures/index.html
Normal file
7
test/watch/fixtures/index.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<script src="./watched-file.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
3
test/watch/fixtures/watched-file.js
Normal file
3
test/watch/fixtures/watched-file.js
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
export const a = 1; // DO NOT CHANGE ME HERE, but in ../test.js
|
||||
|
||||
97
test/watch/test.js
Normal file
97
test/watch/test.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import {join, dirname} from "node:path";
|
||||
import {test, expect} from "@jest/globals";
|
||||
|
||||
import * as rollup from "rollup";
|
||||
import {debugPrintOutput, getCode} from "../util/index.ts";
|
||||
import {resolve} from "node:path";
|
||||
import {writeFile} from "node:fs/promises";
|
||||
|
||||
import html from "../../src/index.ts";
|
||||
|
||||
|
||||
import {fileURLToPath} from "node:url";
|
||||
import {pathToFileURL} from "url";
|
||||
import {defaultOutput} from "../util/default-output.ts";
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
process.chdir(join(__dirname, 'fixtures'));
|
||||
|
||||
|
||||
test('watch', async () => {
|
||||
const origContent = `
|
||||
export const a = 1; // DO NOT CHANGE ME HERE, but in ../test.js
|
||||
`;
|
||||
const changeContent = `
|
||||
export const a = 2; // If i show up as a changed file, then the watch test has gone wrong!
|
||||
`
|
||||
|
||||
const path = resolve(__dirname, 'fixtures/watched-file.js');
|
||||
await writeFile(path, origContent, {encoding: 'utf-8'});
|
||||
|
||||
const output = defaultOutput;
|
||||
const watcher = rollup.watch({
|
||||
input: 'index.html',
|
||||
output: output,
|
||||
plugins: [
|
||||
html({
|
||||
}),
|
||||
],
|
||||
watch: {
|
||||
skipWrite: true,
|
||||
}
|
||||
});
|
||||
|
||||
const steps = [
|
||||
async (bundle)=>{
|
||||
await writeFile(path, changeContent, {encoding: 'utf-8'});
|
||||
// Just wait on the watch mode to pick up on the changes
|
||||
},
|
||||
async (bundle)=>{
|
||||
const code = await getCode(bundle);
|
||||
debugPrintOutput('watch',code);
|
||||
|
||||
// Reset the source file
|
||||
await writeFile(path, origContent, {encoding: 'utf-8'});
|
||||
|
||||
// Assert the output is what we exapect;
|
||||
expect(code).toMatchSnapshot();
|
||||
|
||||
watcher
|
||||
},
|
||||
];
|
||||
const log = console.log;
|
||||
|
||||
await new Promise((resolve, reject)=>{
|
||||
watcher.on('event', async (event) => {
|
||||
const {result} = event;
|
||||
switch (event.code) {
|
||||
case "START":
|
||||
log(`WATCH STARTED`);
|
||||
break;
|
||||
case "BUNDLE_START":
|
||||
log(`REBUILDING...`);
|
||||
|
||||
break;
|
||||
case "BUNDLE_END":
|
||||
log(`Rebuilt...`);
|
||||
const cb = steps.shift();
|
||||
|
||||
const generated = await result.generate(output);
|
||||
const cbResult = await cb(result);
|
||||
if(steps.length===0){
|
||||
watcher.close();
|
||||
resolve();
|
||||
}
|
||||
|
||||
break;
|
||||
case "ERROR":
|
||||
reject(event.error);
|
||||
break;
|
||||
}
|
||||
if (result) {
|
||||
result.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
await watcher.close();
|
||||
});
|
||||
@@ -4,15 +4,17 @@
|
||||
"esModuleInterop": true,
|
||||
"noEmit": true,
|
||||
"noEmitOnError": false,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
// Surpress errors about unused stuff: doesn't solve any bugs and is just annyoing during development, leave warning about this to the IDE or linters
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"pretty": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"allowJs": true
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"exclude": ["dist", "node_modules", "test/types"],
|
||||
"include": ["src/**/*", "types/**/*"],
|
||||
|
||||
86
types/index.d.ts
vendored
86
types/index.d.ts
vendored
@@ -1,24 +1,82 @@
|
||||
import type { Plugin, OutputChunk, OutputAsset, OutputBundle } from 'rollup';
|
||||
import type {Plugin, OutputChunk, OutputAsset, OutputBundle, TransformModuleJSON, } from 'rollup';
|
||||
import {FilterPattern} from "@rollup/pluginutils";
|
||||
import type {DefaultTreeAdapterMap} from "parse5";
|
||||
import {PreRenderedChunk} from "rollup";
|
||||
|
||||
export interface RollupHtmlTemplateOptions {
|
||||
title: string;
|
||||
attributes: Record<string, any>;
|
||||
publicPath: string;
|
||||
meta: Record<string, any>[];
|
||||
bundle: OutputBundle;
|
||||
files: Record<string, (OutputChunk | OutputAsset)[]>;
|
||||
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 {
|
||||
title?: string;
|
||||
attributes?: Record<string, any>;
|
||||
fileName?: string;
|
||||
meta?: Record<string, any>[];
|
||||
publicPath?: string;
|
||||
template?: (templateoptions?: RollupHtmlTemplateOptions) => 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
|
||||
}
|
||||
|
||||
export function makeHtmlAttributes(attributes: Record<string, string>): string;
|
||||
|
||||
/**
|
||||
* A Rollup plugin which creates HTML files to serve Rollup bundles.
|
||||
|
||||
79
types/load.d.ts
vendored
Normal file
79
types/load.d.ts
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
import type {DefaultTreeAdapterMap} from "parse5";
|
||||
|
||||
|
||||
// Load hook types
|
||||
|
||||
export interface RollupHtmlLoadContext {
|
||||
node: DefaultTreeAdapterMap['element'];
|
||||
sourceId: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type AttributeReference = {
|
||||
attr: string;
|
||||
};
|
||||
export type BodyReference = {
|
||||
/**
|
||||
* Indiciate this is an inlined reference (node body)
|
||||
*/
|
||||
body: boolean;
|
||||
/**
|
||||
* Describes what the content type is. I.e 'js' for inlined <script>, 'css' for inlined <style>
|
||||
*/
|
||||
ext?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Describes how a resource should be loaded.
|
||||
*/
|
||||
export type LoadReference = AttributeReference | BodyReference
|
||||
/**
|
||||
* Indicate how to load this resource:
|
||||
* - 'default' uses the default export of the referenced id
|
||||
* - 'chunk' use the rendered chunk of this file (e.g inlined JS)
|
||||
* - 'entryChunk' mark this resource as its own entry-chunk and use its rendered output path
|
||||
* // TODO: add a type 'asset' here, in which we use rollups emitFile({type:'asset'} feature (which reduces the need for plugin-url, and probably makes more sense as the default option instead of 'default' in zero-config scenarios)
|
||||
*/
|
||||
export type LoadType = 'default'|'chunk'|'entryChunk';
|
||||
export type LoadedReference = (
|
||||
{
|
||||
// External (virtual) reference
|
||||
id: string; // path/url referenced. Or identifier for the virtual source
|
||||
source?: string; // Source to use for this id, for inlined chunks
|
||||
} | {
|
||||
// Inline
|
||||
id?: string; // A unique identifier for snippet
|
||||
source: string; // Source to use for this id, for inlined chunks
|
||||
}
|
||||
) & {
|
||||
type?: LoadType
|
||||
};
|
||||
|
||||
export type LoadResult = undefined|void|false;
|
||||
export type LoadFunction = (reference: LoadedReference)=>Promise<string>
|
||||
export type LoadNodeCallback = (loadContext: RollupHtmlLoadContext, load: LoadFunction) => LoadResult|Promise<LoadResult>;
|
||||
|
||||
|
||||
// Make load hook mapping
|
||||
/**
|
||||
* Describes which DOM nodes to extract references from
|
||||
*/
|
||||
export type NodeMapping = {
|
||||
tagName?: string;
|
||||
/** Filter to specific properties to DOM node must have nodes. TODO allowing a callback here probably makes sense */
|
||||
match?: ({
|
||||
/** Whether the element must have a non-null body */
|
||||
body?: boolean
|
||||
/** Which additional attributes the element must have to match */
|
||||
attr?: {[attrName: string]: (string|RegExp|((value:string)=>boolean))}
|
||||
} | ((el: DefaultTreeAdapterMap['element'])=>boolean));
|
||||
|
||||
/**
|
||||
* Indicate how to load this resource:
|
||||
* - 'default' uses the default export of the referenced id
|
||||
* - 'chunk' use the rendered chunk of this file (e.g inlined JS)
|
||||
* - 'entryChunk' mark this resource as its own entry-chunk and use its rendered output path
|
||||
*/
|
||||
loadType?: LoadType
|
||||
} & LoadReference;
|
||||
8
types/resolve.d.ts
vendored
Normal file
8
types/resolve.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
import type {DefaultTreeAdapterMap} from "parse5";
|
||||
|
||||
export interface RollupHtmlResolveContext {
|
||||
node: DefaultTreeAdapterMap['element'];
|
||||
sourceId: string;
|
||||
}
|
||||
export type ResolveResult = string|true|undefined|void|false;
|
||||
export type ResolveCallback = (id: string, resolveContext: RollupHtmlResolveContext) => ResolveResult|Promise<ResolveResult>;
|
||||
Reference in New Issue
Block a user