test: added a react application test (in the browser)
This commit is contained in:
parent
e96c2248ee
commit
3e46055845
@ -63,12 +63,18 @@
|
|||||||
"@babel/core": "^7.23.3",
|
"@babel/core": "^7.23.3",
|
||||||
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
"@babel/plugin-syntax-import-assertions": "^7.23.3",
|
||||||
"@babel/preset-typescript": "^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-babel": "^6.0.4",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-typescript": "^11.1.5",
|
"@rollup/plugin-typescript": "^11.1.5",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@rollup/plugin-url": "^8.0.2",
|
"@rollup/plugin-url": "^8.0.2",
|
||||||
|
"@rollup/plugin-commonjs": "^25.0.7",
|
||||||
|
"@rollup/plugin-replace": "^5.0.5",
|
||||||
"@types/node": "^18.18.12",
|
"@types/node": "^18.18.12",
|
||||||
|
"@types/react": "^18.2.0",
|
||||||
|
"@types/react-dom": "^18.2.0",
|
||||||
"ava": "^5.3.1",
|
"ava": "^5.3.1",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"del-cli": "^5.1.0",
|
"del-cli": "^5.1.0",
|
||||||
|
|||||||
1226
pnpm-lock.yaml
generated
1226
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
20
src/index.ts
20
src/index.ts
@ -77,25 +77,25 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
let filter = createFilter(include, exclude, {});
|
let filter = createFilter(include, exclude, {});
|
||||||
|
|
||||||
// TODO, we need to clear all these properly at sme point to avoid odd bugs in watch mode
|
// TODO, we need to clear all these properly at sme point to avoid odd bugs in watch mode
|
||||||
// let htmlModules = new Map<string, HtmlModule>();// todo clean this per new build?
|
|
||||||
let virtualSources = new Map<string, string>();
|
let virtualSources = new Map<string, string>();
|
||||||
let addedEntries = new Map<string, string>();
|
let addedEntries = new Map<string, string>();
|
||||||
let entryNames = new Map<string,string>();
|
let entryNames = new Map<string,string>();
|
||||||
|
|
||||||
const pluginName = 'html2'; // TODO: Need a better name
|
const pluginName = 'html2'; // TODO: Need a better name, and work to strip everything noted below except the short summary
|
||||||
/**
|
/**
|
||||||
* Short summary:
|
* Short summary:
|
||||||
* Intercepts the loading of the html files and parses it with parse5.
|
* 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).
|
* 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).
|
||||||
* To satisfy rollup a JS version of the html file is returned, optionally including a few imports left for rollup to resolve
|
* 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.
|
* 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 the html file.
|
* and replaced with a html file.
|
||||||
*
|
*
|
||||||
* Caveats:
|
* Caveats:
|
||||||
* - to get the resulting html content file we're parsing the evaluating the result JS module and take its default export
|
* - 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.
|
* [warn] other plugins such as CJS transformer and hot-reload can severely screw this up.
|
||||||
* - sourcemaps won't work yet (to add support we'd need to rewrite using magic-string?)
|
|
||||||
* - to fix the naming of resulting html files, and behave properly when files are entryPoints or not... we're fighting rollup alot
|
* - 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?
|
* Rework by testing a stripped down version with JS imports?
|
||||||
@ -182,6 +182,10 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
// Parse document and store it
|
// Parse document and store it
|
||||||
const document = htmlModule.document = parseHtml(htmlSrc);
|
const document = htmlModule.document = parseHtml(htmlSrc);
|
||||||
|
|
||||||
|
// TODO working on this: to preserve sourcemaps as much as possible we're starting the magic string on the raw html source
|
||||||
|
// question is if we need to though. sourcemaps only make sense for inlined bits of code
|
||||||
|
//let htmlJS = new MagicString(htmlSrc);// This is where we want to go!
|
||||||
|
|
||||||
// Figure out which references to load from this HTML by iterating all nodes (looking for src or href attributes)
|
// Figure out which references to load from this HTML by iterating all nodes (looking for src or href attributes)
|
||||||
let htmlImports: HtmlImport[] = htmlModule.imports = [];
|
let htmlImports: HtmlImport[] = htmlModule.imports = [];
|
||||||
if (document.childNodes) {
|
if (document.childNodes) {
|
||||||
@ -261,8 +265,9 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
} while (nodeQueue.length > 0);
|
} while (nodeQueue.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let htmlJS = new MagicString(serializeHtml(htmlModule.document));// TODO this is still a leak of AST, we're taking parse5 edited result and then transforming sourcemaps, this will only work when no edits were made
|
let htmlJS = new MagicString(serializeHtml(htmlModule.document));
|
||||||
htmlJS.replaceAll(/`/g,'\\\`').replaceAll(/\$\{/g,'\\${');
|
htmlJS.replaceAll(/`/g,'\\\`').replaceAll(/\$\{/g,'\\${');
|
||||||
|
|
||||||
const moduleImports = [];
|
const moduleImports = [];
|
||||||
for(const htmlImport of htmlImports){
|
for(const htmlImport of htmlImports){
|
||||||
if(htmlImport.type === 'default') {
|
if(htmlImport.type === 'default') {
|
||||||
@ -275,6 +280,7 @@ export default function html(opts: RollupHtmlOptions = {}): Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Import all dependencies and wrap the HTML in a `...`, assign to a var and export (escaping any ` characters in the HTML)
|
||||||
htmlJS.prepend([
|
htmlJS.prepend([
|
||||||
...moduleImports,
|
...moduleImports,
|
||||||
`export const html = \``
|
`export const html = \``
|
||||||
|
|||||||
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1 +0,0 @@
|
|||||||
export const batman = 'bum badum badum baaaaa dum!';
|
|
||||||
@ -13,6 +13,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root">Here the app should load!</div>
|
<div id="root">Here the app should load!</div>
|
||||||
<script src="./index.js" type="module"></script>
|
<script src="./index.mjs" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
const [
|
const [
|
||||||
appModule,
|
appModule,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
import("./batman.js"),
|
import("./app.mjs"),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.log("Bootstrapped, ready to go!");
|
console.log("Bootstrapped, ready to go!");
|
||||||
@ -20,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start the app!
|
// Start the app!
|
||||||
root.innerHTML = `<div style="align-self: center"><b>${appModule.batman}</b></div>`;
|
await appModule.app({root});
|
||||||
}catch(err){
|
}catch(err){
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
@ -11,6 +11,9 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
{
|
{
|
||||||
console: [
|
console: [
|
||||||
'[log] Bootstrapped, ready to go!',
|
'[log] Bootstrapped, ready to go!',
|
||||||
|
'[log] Test my sourcemap: App started',
|
||||||
|
'[log] Test my sourcemap: App tick',
|
||||||
|
'[log] Test my sourcemap: App ended',
|
||||||
],
|
],
|
||||||
errors: [],
|
errors: [],
|
||||||
html: `<html lang="en"><head>␊
|
html: `<html lang="en"><head>␊
|
||||||
@ -25,7 +28,7 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
<title>I'm cool!</title>␊
|
<title>I'm cool!</title>␊
|
||||||
</head>␊
|
</head>␊
|
||||||
<body>␊
|
<body>␊
|
||||||
<div id="root"><div style="align-self: center"><b>bum badum badum baaaaa dum!</b></div></div>␊
|
<div id="root"><div style="align-self: center"><b>App ended</b></div></div>␊
|
||||||
<script src="index.js" type="module"></script>␊
|
<script src="index.js" type="module"></script>␊
|
||||||
␊
|
␊
|
||||||
␊
|
␊
|
||||||
@ -34,40 +37,6 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
responses: [
|
responses: [
|
||||||
'200 http://localhost/index.html',
|
'200 http://localhost/index.html',
|
||||||
'200 http://localhost/index.js',
|
'200 http://localhost/index.js',
|
||||||
'200 http://localhost/batman.js',
|
'200 http://localhost/app.js',
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
## copied-assets
|
|
||||||
|
|
||||||
> Snapshot 1
|
|
||||||
|
|
||||||
{
|
|
||||||
console: [
|
|
||||||
'[log] Bootstrapped, ready to go!',
|
|
||||||
],
|
|
||||||
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>bum badum badum baaaaa dum!</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/batman.js',
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
@ -1,9 +1,11 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="icon" href="./icon.svg">
|
<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">-->
|
<!-- <link rel="stylesheet" href="./joker.css">-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->
|
||||||
<!--<script src="./batman.js" type="module"></script>-->
|
<!--<script src="./batman.js" type="module"></script>-->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -14,9 +14,11 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
␊
|
␊
|
||||||
const html = \`<html><head>␊
|
const html = \`<html><head>␊
|
||||||
<link rel="icon" href="${asset0}">␊
|
<link rel="icon" href="${asset0}">␊
|
||||||
|
<!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->␊
|
||||||
<!-- <link rel="stylesheet" href="./joker.css">-->␊
|
<!-- <link rel="stylesheet" href="./joker.css">-->␊
|
||||||
</head>␊
|
</head>␊
|
||||||
<body>␊
|
<body>␊
|
||||||
|
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->␊
|
||||||
<!--<script src="./batman.js" type="module"></script>-->␊
|
<!--<script src="./batman.js" type="module"></script>-->␊
|
||||||
␊
|
␊
|
||||||
␊
|
␊
|
||||||
@ -27,26 +29,28 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
}␊
|
}␊
|
||||||
␊
|
␊
|
||||||
export { render };␊
|
export { render };␊
|
||||||
//# sourceMappingURL=index-f75fa1e5.js.map␊
|
//# sourceMappingURL=index-3d1ca61b.js.map␊
|
||||||
`,
|
`,
|
||||||
fileName: 'index-f75fa1e5.js',
|
fileName: 'index-3d1ca61b.js',
|
||||||
map: SourceMap {
|
map: SourceMap {
|
||||||
file: 'index-f75fa1e5.js',
|
file: 'index-3d1ca61b.js',
|
||||||
mappings: 'AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACNN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;',
|
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: [],
|
names: [],
|
||||||
sources: [
|
sources: [
|
||||||
'../icon.svg',
|
'../icon.svg',
|
||||||
'../index.html',
|
'../index.html',
|
||||||
'../index.ts',
|
'../index.js',
|
||||||
],
|
],
|
||||||
sourcesContent: [
|
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"',
|
'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>␊
|
`<html>␊
|
||||||
<head>␊
|
<head>␊
|
||||||
<link rel="icon" href="./icon.svg">␊
|
<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">-->␊
|
<!-- <link rel="stylesheet" href="./joker.css">-->␊
|
||||||
</head>␊
|
</head>␊
|
||||||
<body>␊
|
<body>␊
|
||||||
|
<!-- TODO: this shouldn't have been commented out, but our plugin fails if it is included (which shoudn't happen!!) -->␊
|
||||||
<!--<script src="./batman.js" type="module"></script>-->␊
|
<!--<script src="./batman.js" type="module"></script>-->␊
|
||||||
</body>␊
|
</body>␊
|
||||||
</html>␊
|
</html>␊
|
||||||
@ -64,8 +68,8 @@ Generated by [AVA](https://avajs.dev).
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: undefined,
|
code: undefined,
|
||||||
fileName: 'index-f75fa1e5.js.map',
|
fileName: 'index-3d1ca61b.js.map',
|
||||||
map: undefined,
|
map: undefined,
|
||||||
source: '{"version":3,"file":"index-f75fa1e5.js","sources":["../icon.svg","../index.html","../index.ts"],"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<!-- <link rel=\\"stylesheet\\" href=\\"./joker.css\\">-->\\n </head>\\n <body>\\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,cAAa,CAAA;;ACNN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;"}',
|
source: '{"version":3,"file":"index-3d1ca61b.js","sources":["../icon.svg","../index.html","../index.js"],"sourcesContent":["export default \\"data:image/svg+xml,%3Csvg%20viewBox%3D%220%200%2032%2032%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%20%20%3Cpath%20style%3D%22fill%3Anone%3Bstroke%3A%2300ff0d%3Bstroke-width%3A5%3Bstroke-linecap%3Asquare%3Bstroke-linejoin%3Amiter%3Bstroke-dasharray%3Anone%3Bstroke-opacity%3A1%22%20d%3D%22M4.1%2014.72%2016%2026.31%2028.38%205.09%22%2F%3E%3C%2Fsvg%3E\\"","<html>\\n <head>\\n <link rel=\\"icon\\" href=\\"./icon.svg\\">\\n <!-- TODO: support for css imports are yet to be added (as simple assets or through a preprocessor-->\\n<!-- <link rel=\\"stylesheet\\" href=\\"./joker.css\\">-->\\n </head>\\n <body>\\n <!-- TODO: this shouldn\'t have been commented out, but our plugin fails if it is included (which shoudn\'t happen!!) -->\\n <!--<script src=\\"./batman.js\\" type=\\"module\\"></script>-->\\n </body>\\n</html>\\n","import html from \\"./index.html\\"\\n\\nexport function render(){\\n return html;\\n}\\n"],"names":[],"mappings":"AAAA,aAAe;;ACAf,MAAA,IAAA,GAAA,CAAA;AACA,+BAA+B,EAAwD,MAAA,CAAA;AACvF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,cAAa,CAAA;;ACRN,SAAS,MAAM,EAAE;AACxB,IAAI,OAAO,IAAI,CAAC;AAChB;;;;"}',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
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
|
||||||
|
},
|
||||||
|
}
|
||||||
90
test/jsx-web-app/test.js
Normal file
90
test/jsx-web-app/test.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import {join, dirname} from "node:path";
|
||||||
|
|
||||||
|
import test from "ava";
|
||||||
|
|
||||||
|
// Rollup * plugins
|
||||||
|
import { rollup } from "rollup";
|
||||||
|
import urlPlugin from "@rollup/plugin-url";
|
||||||
|
import nodeResolve from "@rollup/plugin-node-resolve";
|
||||||
|
import babelPlugin from "@rollup/plugin-babel";
|
||||||
|
import commonJsPlugin from "@rollup/plugin-commonjs";
|
||||||
|
import typescriptPlugin from "@rollup/plugin-typescript";
|
||||||
|
import replacePlugin from "@rollup/plugin-replace";
|
||||||
|
|
||||||
|
import html from "../../src/index.ts";
|
||||||
|
import serveTest from "../util/test-server.ts";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {OutputOptions}
|
||||||
|
*/
|
||||||
|
const output= {
|
||||||
|
dir: 'output', // Output all files
|
||||||
|
format: 'es', // iifi and cjs should be added to tests
|
||||||
|
sourcemap: true,// Test if #sourcemapUrl is not accidentally included in the html-output
|
||||||
|
chunkFileNames: '[name].js',
|
||||||
|
entryFileNames: '[name].[extname]',
|
||||||
|
assetFileNames: '[name].[extname]',
|
||||||
|
};
|
||||||
|
|
||||||
|
import {fileURLToPath} from "node:url";
|
||||||
|
import handlebars from "handlebars";
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
process.chdir(join(__dirname, 'fixtures'));
|
||||||
|
|
||||||
|
|
||||||
|
const defaultAssetInclude = [
|
||||||
|
'**/*.(png|jpg|jpeg|gif|ico|svg)',// images, svg
|
||||||
|
'**/*.(woff|woff2|eot|ttf|otf)',// fonts
|
||||||
|
'**/*.(webm|mp4)',// video
|
||||||
|
];
|
||||||
|
|
||||||
|
test.serial('web-bundle', async (t) => {
|
||||||
|
const bundle = await rollup({
|
||||||
|
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,
|
||||||
|
}),
|
||||||
|
|
||||||
|
serveTest({
|
||||||
|
path: 'index.html',
|
||||||
|
t,
|
||||||
|
})
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const generated = await bundle.generate(output);
|
||||||
|
});
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
155
test/util/run-browser-test.ts
Normal file
155
test/util/run-browser-test.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* 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 TestFilterOptions{
|
||||||
|
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 TestOptions {
|
||||||
|
page: string
|
||||||
|
cb: PageTestCallback
|
||||||
|
filterOutput: TestFilterOptions
|
||||||
|
replaceHost: boolean
|
||||||
|
replaceHostWith?: string
|
||||||
|
}
|
||||||
|
const defaultOptions: Partial<TestOptions> = {
|
||||||
|
page: 'index.html',
|
||||||
|
cb: async (page: Page)=>{
|
||||||
|
await page.waitForNetworkIdle({});
|
||||||
|
},
|
||||||
|
replaceHost: true,
|
||||||
|
replaceHostWith: `http://localhost`,
|
||||||
|
filterOutput:{
|
||||||
|
html: true,
|
||||||
|
console: ['log','error','warn'],// TODO: or warning? need to check what possible values are
|
||||||
|
errors: true, // again don't know possible values
|
||||||
|
responses: true, // interesting to see what other values were requested
|
||||||
|
requestsFailed: true, // will probably also be replicated into console errors, but helpful to have if imports werent found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 runTest(opts: Partial<TestOptions>, hostUrl: string){
|
||||||
|
const options : TestOptions = (<TestOptions>{
|
||||||
|
...defaultOptions,
|
||||||
|
...opts,
|
||||||
|
filterOutput: {
|
||||||
|
...defaultOptions.filterOutput,
|
||||||
|
...(opts?.filterOutput),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const {
|
||||||
|
page: path,
|
||||||
|
cb,
|
||||||
|
replaceHost,
|
||||||
|
replaceHostWith,
|
||||||
|
filterOutput
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: isInDebugMode()? false : 'new',
|
||||||
|
});
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
let output : TestOutput = {
|
||||||
|
console: [],
|
||||||
|
errors: [],
|
||||||
|
responses: [],
|
||||||
|
requestsFailed: []
|
||||||
|
};
|
||||||
|
|
||||||
|
try{
|
||||||
|
// Track requests, errors and console
|
||||||
|
page.on('console', message => {
|
||||||
|
let [type, text] = [message.type(), message.text()];
|
||||||
|
if(replaceHost){
|
||||||
|
text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
if((<any>filterOutput.console)?.includes?.(<any>type) ?? (filterOutput.console === true)){// TODO: add callback option
|
||||||
|
output.console?.push(`[${type}] ${text}`);
|
||||||
|
}
|
||||||
|
}).on('pageerror', ({ message }) => {
|
||||||
|
let text = message;
|
||||||
|
if(replaceHost){
|
||||||
|
text = text.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
if(filterOutput.errors === true) {// TODO add callback option
|
||||||
|
output.errors?.push(text)
|
||||||
|
}
|
||||||
|
}).on('response', response => {
|
||||||
|
let [status, url] = [response.status(), response.url()]
|
||||||
|
if(replaceHost){
|
||||||
|
url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
if(filterOutput.responses === true) {// TODO add callback option
|
||||||
|
output.responses?.push(`${status} ${url}`)
|
||||||
|
}
|
||||||
|
}).on('requestfailed', request => {
|
||||||
|
let [failure, url] = [request.failure()?.errorText, request.url()];
|
||||||
|
if(replaceHost){
|
||||||
|
failure = failure?.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
url = url.replaceAll(hostUrl, replaceHostWith!);
|
||||||
|
}
|
||||||
|
if(filterOutput.requestsFailed === true) {// TODO add callback option
|
||||||
|
output.requestsFailed?.push(`${failure} ${url}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const url = new URL(`${hostUrl}/${path??''}`);
|
||||||
|
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;
|
||||||
|
}finally{
|
||||||
|
if(isInDebugMode()){
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
@ -4,21 +4,20 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import puppeteer, {Page} from "puppeteer";
|
import {runTest, TestFilterOptions, PageTestCallback} from "./run-browser-test.ts";
|
||||||
import http from 'http';
|
import {isInDebugMode} from "./debug-mode.ts";
|
||||||
|
|
||||||
import {resolve, posix} from "node:path";
|
import {resolve, posix} from "node:path";
|
||||||
import {URL} from "node:url";
|
|
||||||
|
|
||||||
|
|
||||||
import { readFile } from 'fs'
|
|
||||||
import { createServer as createHttpsServer } from 'https'
|
import { createServer as createHttpsServer } from 'https'
|
||||||
import { createServer} from 'http'
|
import { createServer} from 'http'
|
||||||
|
|
||||||
import { Mime } from 'mime/lite'
|
import { Mime } from 'mime/lite'
|
||||||
import standardTypes from 'mime/types/standard.js'
|
import standardTypes from 'mime/types/standard.js'
|
||||||
import otherTypes from 'mime/types/other.js'
|
import otherTypes from 'mime/types/other.js'
|
||||||
|
|
||||||
|
|
||||||
import type {NormalizedOutputOptions, OutputAsset, OutputBundle, OutputChunk, Plugin} from 'rollup'
|
import type {NormalizedOutputOptions, OutputAsset, OutputBundle, OutputChunk, Plugin} from 'rollup';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
IncomingHttpHeaders, OutgoingHttpHeaders,
|
IncomingHttpHeaders, OutgoingHttpHeaders,
|
||||||
@ -26,21 +25,34 @@ import type {
|
|||||||
Server
|
Server
|
||||||
} from 'http'
|
} from 'http'
|
||||||
import type { ServerOptions } from 'https'
|
import type { ServerOptions } from 'https'
|
||||||
|
|
||||||
|
|
||||||
type TypeMap = {
|
type TypeMap = {
|
||||||
[key: string]: string[];
|
[key: string]: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ErrorCodeException = Error & {code: string};
|
type ErrorCodeException = Error & {code: string};
|
||||||
|
|
||||||
export interface RollupServeOptions {
|
export interface RollupServeTestOptions {
|
||||||
/**
|
/**
|
||||||
* Change the path to be opened when the test is started
|
* Change the path to be opened when the test is started
|
||||||
* Remember to start with a slash, e.g. `'/different/page'`
|
* Remember to start with a slash, e.g. `'/different/page'`
|
||||||
*/
|
*/
|
||||||
path?: string
|
path?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optionally specify what to filter from the output
|
||||||
|
*/
|
||||||
|
filterOutput?: TestFilterOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A callback to manually take control of the page and simulate user interactions
|
||||||
|
*/
|
||||||
cb?: PageTestCallback
|
cb?: PageTestCallback
|
||||||
t?: any
|
/**
|
||||||
|
* The AVA context used to test (ie t.snapshot(..) )
|
||||||
|
*/
|
||||||
|
t: any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set to `true` to return index.html (200) instead of error page (404)
|
* Set to `true` to return index.html (200) instead of error page (404)
|
||||||
@ -89,7 +101,7 @@ export interface RollupServeOptions {
|
|||||||
* Serve your rolled up bundle like webpack-dev-server
|
* Serve your rolled up bundle like webpack-dev-server
|
||||||
* @param {import('..').RollupServeOptions} options
|
* @param {import('..').RollupServeOptions} options
|
||||||
*/
|
*/
|
||||||
export default function serveTest (options: RollupServeOptions ): Plugin {
|
export default function serveTest (options: RollupServeTestOptions ): Plugin {
|
||||||
const mime = new Mime(standardTypes, otherTypes)
|
const mime = new Mime(standardTypes, otherTypes)
|
||||||
const testOptions = {
|
const testOptions = {
|
||||||
port: 0,
|
port: 0,
|
||||||
@ -135,36 +147,6 @@ export default function serveTest (options: RollupServeOptions ): Plugin {
|
|||||||
const content = (<OutputChunk>file).code || (<OutputAsset>file).source; // Todo might need to read a source file;
|
const content = (<OutputChunk>file).code || (<OutputAsset>file).source; // Todo might need to read a source file;
|
||||||
return found(response, mime.getType(filePath!), content);
|
return found(response, mime.getType(filePath!), content);
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// if(bundle[urlPath]){
|
|
||||||
// const fallbackPath = typeof testOptions.historyApiFallback === 'string' ? testOptions.historyApiFallback : '/index.html'
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// readFileFromContentBase(contentBase, urlPath, function (error, content, filePath) {
|
|
||||||
// if (!error) {
|
|
||||||
// return found(response, mime.getType(filePath!), content)
|
|
||||||
// }
|
|
||||||
// if ((<ErrorCodeException>error).code !== 'ENOENT') {
|
|
||||||
// response.writeHead(500)
|
|
||||||
// response.end('500 Internal Server Error' +
|
|
||||||
// '\n\n' + filePath +
|
|
||||||
// '\n\n' + Object.values(error).join('\n') +
|
|
||||||
// '\n\n(rollup-plugin-serve)', 'utf-8')
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if (testOptions.historyApiFallback) {
|
|
||||||
// const fallbackPath = typeof testOptions.historyApiFallback === 'string' ? testOptions.historyApiFallback : '/index.html'
|
|
||||||
// readFileFromContentBase(contentBase, fallbackPath, function (error, content, filePath) {
|
|
||||||
// if (error) {
|
|
||||||
// notFound(response, filePath)
|
|
||||||
// } else {
|
|
||||||
// found(response, mime.getType(filePath), content)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// } else {
|
|
||||||
// notFound(response, filePath)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -180,7 +162,6 @@ export default function serveTest (options: RollupServeOptions ): Plugin {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// release previous server instance if rollup is reloading configuration in watch mode
|
// release previous server instance if rollup is reloading configuration in watch mode
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (server) {
|
if (server) {
|
||||||
@ -229,6 +210,7 @@ export default function serveTest (options: RollupServeOptions ): Plugin {
|
|||||||
const testOutput = await runTest({
|
const testOutput = await runTest({
|
||||||
page: testOptions.path!,
|
page: testOptions.path!,
|
||||||
cb: testOptions.cb,
|
cb: testOptions.cb,
|
||||||
|
filterOutput: testOptions.filterOutput,
|
||||||
}, url)
|
}, url)
|
||||||
testOptions.t?.snapshot?.(testOutput);
|
testOptions.t?.snapshot?.(testOutput);
|
||||||
}
|
}
|
||||||
@ -257,128 +239,3 @@ function found (response: ServerResponse, mimeType: string|null, content: any) {
|
|||||||
function green (text: string) {
|
function green (text: string) {
|
||||||
return '\u001b[1m\u001b[32m' + text + '\u001b[39m\u001b[22m'
|
return '\u001b[1m\u001b[32m' + text + '\u001b[39m\u001b[22m'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export type PageTestCallback = (page: Page)=>Promise<void>;
|
|
||||||
export interface TestFilterOptions{
|
|
||||||
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 TestOptions {
|
|
||||||
page: string
|
|
||||||
cb: PageTestCallback
|
|
||||||
filterOutput: TestFilterOptions
|
|
||||||
replaceHost: boolean
|
|
||||||
replaceHostWith?: string
|
|
||||||
}
|
|
||||||
const defaultOptions: Partial<TestOptions> = {
|
|
||||||
page: 'index.html',
|
|
||||||
cb: async (page: Page)=>{
|
|
||||||
await page.waitForNetworkIdle({});
|
|
||||||
},
|
|
||||||
replaceHost: true,
|
|
||||||
replaceHostWith: `http://localhost`,
|
|
||||||
filterOutput:{
|
|
||||||
html: true,
|
|
||||||
console: ['log','error','warn'],// TODO: or warning? need to check what possible values are
|
|
||||||
errors: true, // again don't know possible values
|
|
||||||
responses: true, // interesting to see what other values were requested
|
|
||||||
requestsFailed: true, // will probably also be replicated into console errors, but helpful to have if imports werent found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export interface TestOutput{
|
|
||||||
html?: string,
|
|
||||||
console?: string[],
|
|
||||||
errors?: string[],
|
|
||||||
responses?: string[],
|
|
||||||
requestsFailed?: string[],
|
|
||||||
}
|
|
||||||
export async function runTest(opts: Partial<TestOptions>, hostUrl: string){
|
|
||||||
const options : TestOptions = (<TestOptions>{
|
|
||||||
...defaultOptions,
|
|
||||||
...opts,
|
|
||||||
filterOutput: {
|
|
||||||
...defaultOptions.filterOutput,
|
|
||||||
...(opts?.filterOutput),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const {
|
|
||||||
page: path,
|
|
||||||
cb,
|
|
||||||
replaceHost,
|
|
||||||
replaceHostWith,
|
|
||||||
filterOutput
|
|
||||||
} = options;
|
|
||||||
|
|
||||||
const browser = await puppeteer.launch({
|
|
||||||
headless: 'new',
|
|
||||||
});
|
|
||||||
const page = await browser.newPage();
|
|
||||||
|
|
||||||
let output : TestOutput = {
|
|
||||||
console: [],
|
|
||||||
errors: [],
|
|
||||||
responses: [],
|
|
||||||
requestsFailed: []
|
|
||||||
};
|
|
||||||
|
|
||||||
try{
|
|
||||||
// Track requests, errors and console
|
|
||||||
page.on('console', message => {
|
|
||||||
let [type, text] = [message.type(), message.text()];
|
|
||||||
if(replaceHost){
|
|
||||||
text = text.replaceAll(hostUrl, replaceHostWith!);
|
|
||||||
}
|
|
||||||
if((<any>filterOutput.console)?.includes?.(<any>type) ?? (filterOutput.console === true)){// TODO: add callback option
|
|
||||||
output.console?.push(`[${type}] ${text}`);
|
|
||||||
}
|
|
||||||
}).on('pageerror', ({ message }) => {
|
|
||||||
let text = message;
|
|
||||||
if(replaceHost){
|
|
||||||
text = text.replaceAll(hostUrl, replaceHostWith!);
|
|
||||||
}
|
|
||||||
if(filterOutput.errors === true) {// TODO add callback option
|
|
||||||
output.errors?.push(text)
|
|
||||||
}
|
|
||||||
}).on('response', response => {
|
|
||||||
let [status, url] = [response.status(), response.url()]
|
|
||||||
if(replaceHost){
|
|
||||||
url = url.replaceAll(hostUrl, replaceHostWith!);
|
|
||||||
}
|
|
||||||
if(filterOutput.responses === true) {// TODO add callback option
|
|
||||||
output.responses?.push(`${status} ${url}`)
|
|
||||||
}
|
|
||||||
}).on('requestfailed', request => {
|
|
||||||
let [failure, url] = [request.failure()?.errorText, request.url()];
|
|
||||||
if(replaceHost){
|
|
||||||
failure = failure?.replaceAll(hostUrl, replaceHostWith!);
|
|
||||||
url = url.replaceAll(hostUrl, replaceHostWith!);
|
|
||||||
}
|
|
||||||
if(filterOutput.requestsFailed === true) {// TODO add callback option
|
|
||||||
output.requestsFailed?.push(`${failure} ${url}`)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const url = new URL(`${hostUrl}/${path??''}`);
|
|
||||||
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;
|
|
||||||
}finally{
|
|
||||||
await page.close();
|
|
||||||
await browser.close();
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user