release: WRNexusJS 0.3.5
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/ssr",
|
||||
"version": "0.3.4",
|
||||
"version": "0.3.5",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"exports": {
|
||||
|
||||
@@ -31,6 +31,12 @@ export interface RenderOptions {
|
||||
extraBody?: string;
|
||||
/** Attributes for the `<html>` element, e.g. ` data-theme="dark"` (trusted). */
|
||||
htmlAttrs?: string;
|
||||
/**
|
||||
* Optional application-authored full document shell. It must contain
|
||||
* `<html>`, `<head>`, and `<body>`. Framework metadata, assets, and scripts
|
||||
* are merged into it instead of wrapping the rendered body again.
|
||||
*/
|
||||
documentTemplate?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,6 +65,25 @@ export function renderDocument(opts: RenderOptions): string {
|
||||
? (opts.htmlAttrs ?? "")
|
||||
: `${opts.htmlAttrs ?? ""} lang="en"`;
|
||||
|
||||
if (opts.documentTemplate) {
|
||||
let document = opts.documentTemplate.trim();
|
||||
if (!/^<!doctype\s+html>/i.test(document)) document = `<!doctype html>\n${document}`;
|
||||
document = document.replace(/<html([^>]*)>/i, (_match, existing: string) => {
|
||||
const language = /(?:^|\s)lang\s*=/.test(`${existing}${opts.htmlAttrs ?? ""}`)
|
||||
? ""
|
||||
: ' lang="en"';
|
||||
return `<html${existing}${opts.htmlAttrs ?? ""}${language}>`;
|
||||
});
|
||||
const headContent = `
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<title>${title}</title>${seoTags}${preloadTags}${extraHead}`;
|
||||
document = document.replace(/<head([^>]*)>/i, `<head$1>${headContent}`);
|
||||
document = document.replace(/<\/body>/i, `${scriptTags}${extraBody}\n </body>`);
|
||||
return `${document}\n`;
|
||||
}
|
||||
|
||||
return `<!doctype html>
|
||||
<html${htmlAttrs}>
|
||||
<head>
|
||||
|
||||
@@ -36,6 +36,24 @@ test("always emits a document language and preserves an explicit language", () =
|
||||
expect(explicit).not.toContain('lang="en"');
|
||||
});
|
||||
|
||||
test("merges framework head and scripts into an application document template", () => {
|
||||
const html = renderDocument({
|
||||
meta: { title: "Document layout" },
|
||||
body: "",
|
||||
htmlAttrs: ' data-theme="dark"',
|
||||
scripts: ["/page.js"],
|
||||
documentTemplate:
|
||||
'<html data-ui-font="system"><head><meta name="custom" content="yes" /></head><body><div id="app">Page</div></body></html>',
|
||||
});
|
||||
expect(html).toStartWith("<!doctype html>");
|
||||
expect(html).toContain('<html data-ui-font="system" data-theme="dark" lang="en">');
|
||||
expect(html).toContain("<title>Document layout</title>");
|
||||
expect(html).toContain('<meta name="custom" content="yes" />');
|
||||
expect(html).toContain('<script type="module" src="/page.js"></script>');
|
||||
expect(html.match(/<html/g)).toHaveLength(1);
|
||||
expect(html.match(/<body/g)).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("streams async body chunks inside the document shell", async () => {
|
||||
async function* body() {
|
||||
yield "<h1>Shell</h1>";
|
||||
|
||||
Reference in New Issue
Block a user