mirror of
https://github.com/modernweb-dev/rocket.git
synced 2026-03-10 08:51:24 +00:00
fix(engine): if a component has an error be sure to always show it
This commit is contained in:
@@ -112,6 +112,9 @@ export class Engine {
|
||||
const result = await this.renderFile({ sourceFilePath, rocketHeader });
|
||||
await pageTree.add(result.sourceRelativeFilePath);
|
||||
await cleanupAutoGeneratedFiles(result);
|
||||
if (result.passOnError) {
|
||||
throw result.passOnError;
|
||||
}
|
||||
}
|
||||
await pageTree.save();
|
||||
|
||||
@@ -431,10 +434,15 @@ export class Engine {
|
||||
inputDir: this.docsDir,
|
||||
outputDir: this.outputDir,
|
||||
renderMode: this.options.renderMode || 'development',
|
||||
needsLoader: rocketHeader ? rocketHeader.needsLoader : false,
|
||||
throwOnError,
|
||||
};
|
||||
let result = await renderViaWorker(renderOptions);
|
||||
if (result.passOnError) {
|
||||
if (throwOnError) {
|
||||
throw result.passOnError;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (rocketHeader) {
|
||||
const { needsAnotherRenderingPass } = await rocketHeader.syncComponents({
|
||||
@@ -444,7 +452,6 @@ export class Engine {
|
||||
openGraphHtml: result.openGraphHtml,
|
||||
});
|
||||
if (needsAnotherRenderingPass) {
|
||||
renderOptions.needsLoader = rocketHeader.needsLoader;
|
||||
renderOptions.throwOnError = true;
|
||||
result = await renderViaWorker(renderOptions);
|
||||
} else if (result.passOnError) {
|
||||
|
||||
@@ -81,7 +81,6 @@ export class RocketHeader {
|
||||
/** @type {String[]} */
|
||||
componentDefinitions = [];
|
||||
sourceFileContent = '';
|
||||
needsLoader = false;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -123,12 +122,6 @@ export class RocketHeader {
|
||||
dataCascade.push(line);
|
||||
}
|
||||
if (captureComponents === true) {
|
||||
if (
|
||||
line.trim() === '// hydrate-able components' ||
|
||||
line.trim() === '// client-only components'
|
||||
) {
|
||||
this.needsLoader = true;
|
||||
}
|
||||
componentDefinitions.push(line);
|
||||
}
|
||||
|
||||
@@ -483,6 +476,9 @@ export class RocketHeader {
|
||||
clientComponents.unshift(' // client-only components');
|
||||
}
|
||||
|
||||
const needsLoader = hydrateAbleComponents.length > 0 || clientComponents.length > 0;
|
||||
const needsLoaderExport = needsLoader ? ['export const needsLoader = true;'] : [];
|
||||
|
||||
if (
|
||||
serverOnlyComponents.length > 0 ||
|
||||
serverOnlyOpenGraphOnlyComponents.length > 0 ||
|
||||
@@ -496,6 +492,7 @@ export class RocketHeader {
|
||||
...hydrateAbleComponents,
|
||||
...clientComponents,
|
||||
'}',
|
||||
...needsLoaderExport,
|
||||
];
|
||||
} else {
|
||||
this.componentDefinitions = [];
|
||||
@@ -503,12 +500,11 @@ export class RocketHeader {
|
||||
|
||||
await this.set();
|
||||
const result = await this.save();
|
||||
if (hydrateAbleComponents.length > 0 || clientComponents.length > 0) {
|
||||
this.needsLoader = true;
|
||||
|
||||
if (needsLoader) {
|
||||
await this.saveHydrationFile({ outputFilePath, componentsWithStrategy });
|
||||
} else {
|
||||
this.needsLoader = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ let isRendering = '';
|
||||
* @param {string} options.outputDir
|
||||
* @param {string} options.inputDir
|
||||
* @param {string} options.renderMode
|
||||
* @param {boolean} options.needsLoader
|
||||
* @param {boolean} options.throwOnError
|
||||
* @returns {Promise<import('../../types/main.js').renderWorkerResult>}
|
||||
*/
|
||||
@@ -44,7 +43,6 @@ export function renderViaWorker({
|
||||
inputDir,
|
||||
outputDir,
|
||||
renderMode = 'development',
|
||||
needsLoader = false,
|
||||
throwOnError = false,
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -92,7 +90,6 @@ export function renderViaWorker({
|
||||
outputDir,
|
||||
inputDir,
|
||||
renderMode,
|
||||
needsLoader,
|
||||
throwOnError,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,7 +24,6 @@ import { validateComponentImportString } from '../file-header/validateComponentI
|
||||
* @param {string} options.sourceFilePath
|
||||
* @param {string} options.outputDir
|
||||
* @param {string} options.inputDir
|
||||
* @param {Boolean} options.needsLoader
|
||||
* @param {Boolean} options.throwOnError
|
||||
* @param {'development'|'production'} options.renderMode
|
||||
*/
|
||||
@@ -33,7 +32,6 @@ async function renderFile({
|
||||
outputDir,
|
||||
inputDir,
|
||||
renderMode = 'development',
|
||||
needsLoader = false,
|
||||
throwOnError = false,
|
||||
}) {
|
||||
let fileContent = '';
|
||||
@@ -144,7 +142,7 @@ async function renderFile({
|
||||
sourceRelativeFilePath,
|
||||
outputRelativeFilePath,
|
||||
url,
|
||||
needsLoader,
|
||||
needsLoader: !!data.needsLoader,
|
||||
});
|
||||
|
||||
fileContent = fileContent.trim();
|
||||
@@ -181,7 +179,7 @@ async function renderFile({
|
||||
sourceRelativeFilePath,
|
||||
outputRelativeFilePath: layoutData.openGraphOutputRelativeFilePath,
|
||||
url: layoutData.openGraphUrl,
|
||||
needsLoader,
|
||||
needsLoader: false, // open graph is always only server side rendered
|
||||
});
|
||||
|
||||
openGraphHtml = openGraphHtml.trim();
|
||||
@@ -223,7 +221,7 @@ async function renderFile({
|
||||
|
||||
parentPort?.on('message', message => {
|
||||
if (message.action === 'renderFile') {
|
||||
const { sourceFilePath, outputDir, renderMode, inputDir, needsLoader, throwOnError } = message;
|
||||
renderFile({ sourceFilePath, outputDir, renderMode, inputDir, needsLoader, throwOnError });
|
||||
const { sourceFilePath, outputDir, renderMode, inputDir, throwOnError } = message;
|
||||
renderFile({ sourceFilePath, outputDir, renderMode, inputDir, throwOnError });
|
||||
}
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ describe('Components', () => {
|
||||
' // hydrate-able components',
|
||||
" customElements.define('my-el', await import('@test/components').then(m => m.MyEl));",
|
||||
'}',
|
||||
'export const needsLoader = true;',
|
||||
'/* END - Rocket auto generated - do not touch */',
|
||||
'',
|
||||
'export default () => html`<my-el loading="hydrate"></my-el>`;',
|
||||
@@ -102,6 +103,7 @@ describe('Components', () => {
|
||||
' // client-only components',
|
||||
" // 'my-el2': () => import('@test/components').then(m => m.MyEl2),",
|
||||
'}',
|
||||
'export const needsLoader = true;',
|
||||
'/* END - Rocket auto generated - do not touch */',
|
||||
'',
|
||||
'export default () => html`<my-el2 loading="client"></my-el2>`;',
|
||||
@@ -303,6 +305,7 @@ describe('Components', () => {
|
||||
' // hydrate-able components',
|
||||
" customElements.define('my-el', await import('@test/components').then(m => m.MyEl));",
|
||||
'}',
|
||||
'export const needsLoader = true;',
|
||||
'/* END - Rocket auto generated - do not touch */',
|
||||
'',
|
||||
'export default () => html`<my-el loading="hydrate:onClick"></my-el>`;',
|
||||
@@ -484,4 +487,112 @@ describe('Components', () => {
|
||||
].join('\n'),
|
||||
);
|
||||
});
|
||||
|
||||
it('13: during start it handles errors in components', async () => {
|
||||
const {
|
||||
writeSource,
|
||||
cleanup,
|
||||
engine,
|
||||
anEngineEvent,
|
||||
readOutput,
|
||||
adjustSource,
|
||||
setAsOpenedInBrowser,
|
||||
readSource,
|
||||
} = await setupTestEngine('fixtures/14-components/13-start-error-in-component/docs');
|
||||
|
||||
// working component
|
||||
await writeSource(
|
||||
'../src/MyEl.js',
|
||||
[
|
||||
"import { LitElement, html } from 'lit';",
|
||||
'',
|
||||
'export class MyEl extends LitElement {',
|
||||
' render() {',
|
||||
' return html`<p>Hello World</p>`;',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n'),
|
||||
);
|
||||
await adjustSource(
|
||||
'index.rocket.js',
|
||||
/<my-el loading="hydrate:onVisible">.*?<\/my-el>/,
|
||||
'<my-el loading="hydrate:onVisible">0</my-el>',
|
||||
);
|
||||
await engine.start();
|
||||
setAsOpenedInBrowser('index.rocket.js');
|
||||
|
||||
// trigger render
|
||||
await adjustSource(
|
||||
'index.rocket.js',
|
||||
/<my-el loading="hydrate:onVisible">.*?<\/my-el>/,
|
||||
'<my-el loading="hydrate:onVisible">1</my-el>',
|
||||
);
|
||||
await anEngineEvent('rocketUpdated');
|
||||
expect(readOutput('index.html')).to.equal(
|
||||
[
|
||||
'<my-el loading="hydrate:onVisible"',
|
||||
' ><template shadowroot="open"><p>Hello World</p></template>1</my-el',
|
||||
'>',
|
||||
'<script type="module" src="index-loader-generated.js"></script>',
|
||||
].join('\n'),
|
||||
);
|
||||
expect(readSource('index.rocket.js')).to.equal(
|
||||
[
|
||||
'/* START - Rocket auto generated - do not touch */',
|
||||
"export const sourceRelativeFilePath = 'index.rocket.js';",
|
||||
"import { html, components } from './recursive.data.js';",
|
||||
'export { html, components };',
|
||||
'export async function registerCustomElements() {',
|
||||
' // hydrate-able components',
|
||||
" customElements.define('my-el', await import('@test/components').then(m => m.MyEl));",
|
||||
'}',
|
||||
'export const needsLoader = true;',
|
||||
'/* END - Rocket auto generated - do not touch */',
|
||||
'',
|
||||
'export default () => html`<my-el loading="hydrate:onVisible">1</my-el>`;',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
// introduce error in component
|
||||
await writeSource(
|
||||
'../src/MyEl.js',
|
||||
[
|
||||
"import { LitElement, html } from 'lit';",
|
||||
'',
|
||||
'export class MyEl extends LitElement {',
|
||||
' render() {',
|
||||
' let foo = this.does.not.exist;', // <-- this is the error
|
||||
' return html`<p>Hello World</p>`;',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n'),
|
||||
);
|
||||
await anEngineEvent('rocketUpdated');
|
||||
expect(readOutput('index.html')).to.include('<p>🛑 Server Error 🛑</p>');
|
||||
|
||||
// correct error
|
||||
await writeSource(
|
||||
'../src/MyEl.js',
|
||||
[
|
||||
"import { LitElement, html } from 'lit';",
|
||||
'',
|
||||
'export class MyEl extends LitElement {',
|
||||
' render() {',
|
||||
' return html`<p>Hello World</p>`;',
|
||||
' }',
|
||||
'}',
|
||||
].join('\n'),
|
||||
);
|
||||
await anEngineEvent('rocketUpdated');
|
||||
expect(readOutput('index.html')).to.equal(
|
||||
[
|
||||
'<my-el loading="hydrate:onVisible"',
|
||||
' ><template shadowroot="open"><p>Hello World</p></template>1</my-el',
|
||||
'>',
|
||||
'<script type="module" src="index-loader-generated.js"></script>',
|
||||
].join('\n'),
|
||||
);
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
"url": "/",
|
||||
"outputRelativeFilePath": "index.html",
|
||||
"sourceRelativeFilePath": "index.rocket.js",
|
||||
"level": 0
|
||||
"level": 0,
|
||||
"title": "Unexpected identifier",
|
||||
"h1": "Unexpected identifier"
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`<my-el loading="hydrate"></my-el>`;
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"url": "/",
|
||||
"outputRelativeFilePath": "index.html",
|
||||
"sourceRelativeFilePath": "index.rocket.js",
|
||||
"level": 0
|
||||
"level": 0,
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// client-only components
|
||||
// 'my-el2': () => import('@test/components').then(m => m.MyEl2),
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`<my-el2 loading="client"></my-el2>`;
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"url": "/",
|
||||
"outputRelativeFilePath": "index.html",
|
||||
"sourceRelativeFilePath": "index.rocket.js",
|
||||
"level": 0
|
||||
"level": 0,
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export async function registerCustomElements() {
|
||||
// client-only components
|
||||
// 'my-only': () => import('@test/components/MyOnly').then(m => m.MyOnly),
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"url": "/",
|
||||
"outputRelativeFilePath": "index.html",
|
||||
"sourceRelativeFilePath": "index.rocket.js",
|
||||
"level": 0
|
||||
"level": 0,
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`<my-el loading="hydrate:onClick"></my-el>`;
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
"url": "/",
|
||||
"outputRelativeFilePath": "index.html",
|
||||
"sourceRelativeFilePath": "index.rocket.js",
|
||||
"level": 0
|
||||
"level": 0,
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -4,5 +4,6 @@
|
||||
"url": "/",
|
||||
"outputRelativeFilePath": "index.html",
|
||||
"sourceRelativeFilePath": "index.rocket.js",
|
||||
"level": 0
|
||||
"level": 0,
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* START - Rocket auto generated - do not touch */
|
||||
export const sourceRelativeFilePath = 'index.rocket.js';
|
||||
import { html, components } from './recursive.data.js';
|
||||
export { html, components };
|
||||
export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`<my-el loading="hydrate:onVisible">1</my-el>`;
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "index.rocket.js",
|
||||
"menuLinkText": "index.rocket.js",
|
||||
"url": "/",
|
||||
"outputRelativeFilePath": "index.html",
|
||||
"sourceRelativeFilePath": "index.rocket.js",
|
||||
"level": 0,
|
||||
"title": "Cannot read properties of undefined (reading 'not')",
|
||||
"h1": "Cannot read properties of undefined (reading 'not')",
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export { html } from 'lit';
|
||||
|
||||
export const components = {
|
||||
'my-el': '@test/components::MyEl',
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "@test/components",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/MyEl.js"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
|
||||
export class MyEl extends LitElement {
|
||||
render() {
|
||||
return html`<p>Hello World</p>`;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`<my-el loading="hydrate:onClientLoad"></my-el>`;
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`<my-el loading="hydrate:onClick"></my-el>`;
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html` <my-el loading="hydrate:onMedia('(max-width: 320px)')"></my-el> `;
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html`
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export async function registerCustomElements() {
|
||||
// hydrate-able components
|
||||
customElements.define('my-el', await import('@test/components').then(m => m.MyEl));
|
||||
}
|
||||
export const needsLoader = true;
|
||||
/* END - Rocket auto generated - do not touch */
|
||||
|
||||
export default () => html` <my-el loading="hydrate:onFocus"></my-el> `;
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
"level": 0,
|
||||
"components": {
|
||||
"my-el": "() => import('@test/components').then(m => m.MyEl)"
|
||||
}
|
||||
},
|
||||
"needsLoader": true
|
||||
}
|
||||
Reference in New Issue
Block a user