Skip to content

Commit

Permalink
experimental support for customizing html attributes (#680)
Browse files Browse the repository at this point in the history
and includes a small refactor in rscIndex plugin.
  • Loading branch information
dai-shi authored Apr 28, 2024
1 parent 61c0a53 commit 738ea78
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 37 deletions.
12 changes: 7 additions & 5 deletions packages/waku/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@ export interface Config {
* Defaults to "ssr".
*/
ssrDir?: string;
/**
* The index.html file for any directories.
* Defaults to "index.html".
*/
indexHtml?: string;
/**
* The client main file relative to srcDir.
* The extension should be `.js`,
Expand Down Expand Up @@ -78,6 +73,13 @@ export interface Config {
* Defaults to "RSC".
*/
rscPath?: string;
/**
* HTML attributes to inject.
* Defaults to ''
* An example is 'lang="en"'
* This is still experimental and might be changed in the future.
*/
htmlAttrs?: string;
/**
* HTML headers to inject.
* Defaults to:
Expand Down
4 changes: 2 additions & 2 deletions packages/waku/src/lib/builder/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ const emitHtmlFiles = async (
rootDir,
config.distDir,
config.publicDir,
config.indexHtml,
'index.html',
);
const publicIndexHtml = await readFile(publicIndexHtmlFile, {
encoding: 'utf8',
Expand Down Expand Up @@ -553,7 +553,7 @@ const emitHtmlFiles = async (
? pathname
: pathname === '/404'
? '404.html' // HACK special treatment for 404, better way?
: pathname + '/' + config.indexHtml,
: pathname + '/index.html',
);
const htmlReadable = await renderHtml({
config,
Expand Down
2 changes: 1 addition & 1 deletion packages/waku/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ export async function resolveConfig(config: Config) {
publicDir: 'public',
assetsDir: 'assets',
ssrDir: 'ssr',
indexHtml: 'index.html',
mainJs: 'main.js',
entriesJs: 'entries.js',
preserveModuleDirs: ['pages', 'templates', 'routes', 'components'],
privateDir: 'private',
serveJs: 'serve.js',
rscPath: 'RSC',
htmlAttrs: '',
htmlHead: DEFAULT_HTML_HEAD,
middleware: DEFAULT_MIDDLEWARE,
...config,
Expand Down
49 changes: 25 additions & 24 deletions packages/waku/src/lib/plugins/vite-plugin-rsc-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ export function rscIndexPlugin(opts: {
basePath: string;
srcDir: string;
mainJs: string;
htmlAttrs: string;
htmlHead: string;
indexHtml: string;
cssAssets?: string[];
}): Plugin {
const indexHtml = 'index.html';
const html = `
<!doctype html>
<html>
<html${opts.htmlAttrs ? ' ' + opts.htmlAttrs : ''}>
<head>
${opts.htmlHead}
</head>
Expand All @@ -23,24 +24,6 @@ ${opts.htmlHead}
`;
return {
name: 'rsc-index-plugin',
configureServer(server) {
return () => {
server.middlewares.use((req, res) => {
server
.transformIndexHtml(req.url || '', html)
.then((content) => {
res.statusCode = 200;
res.setHeader('content-type', 'text/html; charset=utf-8');
res.end(content);
})
.catch((err) => {
console.error('Error transforming index.html', err);
res.statusCode = 500;
res.end('Internal Server Error');
});
});
};
},
config() {
return {
optimizeDeps: {
Expand All @@ -58,18 +41,36 @@ ${opts.htmlHead}
return {
...options,
input: {
indexHtml: opts.indexHtml,
indexHtml,
...options.input,
},
};
},
configureServer(server) {
return () => {
server.middlewares.use((req, res) => {
server
.transformIndexHtml(req.url || '', html)
.then((content) => {
res.statusCode = 200;
res.setHeader('content-type', 'text/html; charset=utf-8');
res.end(content);
})
.catch((err) => {
console.error('Error transforming index.html', err);
res.statusCode = 500;
res.end('Internal Server Error');
});
});
};
},
resolveId(id) {
if (id === opts.indexHtml) {
return { id: opts.indexHtml, moduleSideEffects: true };
if (id === indexHtml) {
return { id: indexHtml, moduleSideEffects: true };
}
},
load(id) {
if (id === opts.indexHtml) {
if (id === indexHtml) {
return html;
}
},
Expand Down
4 changes: 0 additions & 4 deletions packages/waku/src/lib/plugins/vite-plugin-rsc-serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export function rscServePlugin(opts: {
serveJs: string;
distDir: string;
publicDir: string;
indexHtml: string;
entriesFile: string;
srcServeFile: string;
serve:
Expand All @@ -29,9 +28,6 @@ export function rscServePlugin(opts: {
'import.meta.env.WAKU_CONFIG_PUBLIC_DIR': JSON.stringify(
opts.publicDir,
),
'import.meta.env.WAKU_CONFIG_INDEX_HTML': JSON.stringify(
opts.indexHtml,
),
};
if (opts.serve === 'cloudflare' || opts.serve === 'partykit') {
viteConfig.build ||= {};
Expand Down
16 changes: 15 additions & 1 deletion packages/waku/src/lib/renderers/html-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,27 @@ const rectifyHtml = () => {
});
};

const parseHtmlAttrs = (attrs: string): Record<string, string> => {
// HACK this is very brittle
const result: Record<string, string> = {};
const kebab2camel = (s: string) =>
s.replace(/-./g, (m) => m[1]!.toUpperCase());
const matches = attrs.matchAll(/(?<=^|\s)([^\s=]+)="([^"]+)"(?=\s|$)/g);
for (const match of matches) {
result[kebab2camel(match[1]!)] = match[2]!;
}
return result;
};

const buildHtml = (
createElement: typeof createElementType,
attrs: string,
head: string,
body: ReactNode,
) =>
createElement(
'html',
null,
attrs ? parseHtmlAttrs(attrs) : null,
createElement('head', { dangerouslySetInnerHTML: { __html: head } }),
createElement('body', { 'data-hydrate': true }, body),
);
Expand Down Expand Up @@ -334,6 +347,7 @@ export const renderHtml = async (
await renderToReadableStream(
buildHtml(
createElement,
config.htmlAttrs,
htmlHead,
createElement(
ServerRoot as FunctionComponent<
Expand Down

0 comments on commit 738ea78

Please sign in to comment.