-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into docs/table-best-practices
- Loading branch information
Showing
49 changed files
with
1,465 additions
and
422 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
v22.7.0 | ||
v23.2.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { transform } from '@pwrs/lit-css'; | ||
import { readFile } from 'node:fs/promises'; | ||
import { fileURLToPath } from 'node:url'; | ||
|
||
interface HookContext { | ||
source: string; | ||
format: 'module' | 'commonjs' | 'wasm' | 'json'; | ||
} | ||
|
||
type LoadFunction = (url: string, context: HookContext) => Promise<HookContext>; | ||
|
||
const cache = new Map<string, string>(); | ||
|
||
export async function load(url: string, context: HookContext, nextLoad: LoadFunction) { | ||
if (url.endsWith('.css')) { | ||
if (!cache.has(url)) { | ||
const filePath = fileURLToPath(new URL(url)); | ||
const css = await readFile(filePath, 'utf8'); | ||
cache.set(url, await transform({ css, filePath })); | ||
} | ||
const format = 'module'; | ||
const source = cache.get(url); | ||
return { source, shortCircuit: true, format }; | ||
} else { | ||
return nextLoad(url, context); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,113 +1,103 @@ | ||
/** | ||
* @license based on code from eleventy-plugin-lit | ||
* Copyright 2021 Google LLC | ||
* SPDX-License-Identifier: BSD-3-Clause | ||
*/ | ||
|
||
import type { EleventyPage } from '@11ty/eleventy/src/UserConfig.js'; | ||
import type { UserConfig } from '@11ty/eleventy'; | ||
import { dirname, resolve } from 'node:path'; | ||
import { fileURLToPath, pathToFileURL } from 'node:url'; | ||
import { Worker } from 'node:worker_threads'; | ||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
import { readFile, writeFile } from 'node:fs/promises'; | ||
|
||
interface Options { | ||
componentModules?: string[]; | ||
} | ||
import { Piscina } from 'piscina'; | ||
import tsBlankSpace from 'ts-blank-space'; | ||
import chalk from 'chalk'; | ||
|
||
// Lit SSR includes comment markers to track the outer template from | ||
// the template we've generated here, but it's not possible for this | ||
// outer template to be hydrated, so they serve no purpose. | ||
function trimOuterMarkers(renderedContent: string) { | ||
return renderedContent | ||
.replace(/^((<!--[^<>]*-->)|(<\?>)|\s)+/, '') | ||
.replace(/((<!--[^<>]*-->)|(<\?>)|\s)+$/, ''); | ||
} | ||
import { register } from 'node:module'; | ||
|
||
export default function(eleventyConfig: UserConfig, opts?: Options) { | ||
const { componentModules } = opts ?? {}; | ||
if (componentModules === undefined || componentModules.length === 0) { | ||
// If there are no component modules, we could never have anything to | ||
// render. | ||
return; | ||
} | ||
export interface RenderRequestMessage { | ||
content: string; | ||
page: Pick<EleventyPage, 'inputPath' | 'outputPath'>; | ||
} | ||
|
||
const resolvedComponentModules = componentModules.map(module => | ||
pathToFileURL(resolve(process.cwd(), module)).href); | ||
export interface RenderResponseMessage { | ||
page: Pick<EleventyPage, 'inputPath' | 'outputPath'>; | ||
rendered?: string; | ||
durationMs: number; | ||
} | ||
|
||
let worker: Worker; | ||
interface Options { | ||
componentModules?: string[]; | ||
/** path from project root to tsconfig */ | ||
tsconfig?: string; | ||
} | ||
|
||
const requestIdResolveMap = new Map(); | ||
let requestId = 0; | ||
async function redactTSFileInPlace(path: string) { | ||
const inURL = new URL(path, import.meta.url); | ||
const outURL = new URL(path.replace('.ts', '.js'), import.meta.url); | ||
await writeFile(outURL, tsBlankSpace(await readFile(inURL, 'utf8')), 'utf8'); | ||
} | ||
|
||
eleventyConfig.on('eleventy.before', async function() { | ||
worker = new Worker(resolve(__dirname, './worker/worker.js')); | ||
register('./lit-css-node.ts', import.meta.url); | ||
|
||
worker.on('error', err => { | ||
// eslint-disable-next-line no-console | ||
console.error('Unexpected error while rendering lit component in worker thread', err); | ||
throw err; | ||
/** | ||
* Eleventy plugin to server-render lit elements directly from typescript source | ||
* @param eleventyConfig | ||
* @param opts | ||
*/ | ||
export default async function(eleventyConfig: UserConfig, opts?: Options) { | ||
const imports = opts?.componentModules ?? []; | ||
const tsconfig = opts?.tsconfig ?? './tsconfig.json'; | ||
|
||
let pool: Piscina; | ||
|
||
// If there are no component modules, we could never have anything to | ||
// render. | ||
if (imports?.length) { | ||
eleventyConfig.on('eleventy.before', async function() { | ||
await redactTSFileInPlace('./worker.ts'); | ||
const filename = new URL('worker.js', import.meta.url).pathname; | ||
pool = new Piscina({ | ||
filename, | ||
workerData: { | ||
imports, | ||
tsconfig, | ||
}, | ||
}); | ||
}); | ||
|
||
let requestResolve: (v?: unknown) => void; | ||
const requestPromise = new Promise(_resolve => { | ||
requestResolve = _resolve; | ||
eleventyConfig.on('eleventy.after', async function() { | ||
return pool.close(); | ||
}); | ||
|
||
worker.on('message', message => { | ||
switch (message.type) { | ||
case 'initialize-response': { | ||
requestResolve(); | ||
break; | ||
} | ||
eleventyConfig.addTransform('render-lit', async function(this, content) { | ||
const { outputPath, inputPath } = this.page; | ||
|
||
case 'render-response': { | ||
const { id, rendered } = message; | ||
const _resolve = requestIdResolveMap.get(id); | ||
if (_resolve === undefined) { | ||
throw new Error( | ||
'@lit-labs/eleventy-plugin-lit received invalid render-response message' | ||
); | ||
} | ||
_resolve(rendered); | ||
requestIdResolveMap.delete(id); | ||
break; | ||
} | ||
if (!outputPath.endsWith('.html')) { | ||
return content; | ||
} | ||
}); | ||
|
||
const message = { | ||
type: 'initialize-request', | ||
imports: resolvedComponentModules, | ||
}; | ||
|
||
worker.postMessage(message); | ||
await requestPromise; | ||
}); | ||
|
||
eleventyConfig.on('eleventy.after', async () => { | ||
await worker.terminate(); | ||
}); | ||
|
||
eleventyConfig.addTransform('render-lit', async function(this, content) { | ||
const { outputPath, inputPath, fileSlug } = this.page; | ||
if (!outputPath.endsWith('.html')) { | ||
return content; | ||
} | ||
|
||
const renderedContent: string = await new Promise(_resolve => { | ||
requestIdResolveMap.set(requestId, _resolve); | ||
const message = { | ||
type: 'render-request', | ||
id: requestId++, | ||
content, | ||
page: JSON.parse(JSON.stringify({ outputPath, inputPath, fileSlug })), | ||
}; | ||
worker.postMessage(message); | ||
const page = { outputPath, inputPath }; | ||
const message = await pool.run({ page, content }); | ||
if (message.rendered) { | ||
const { durationMs, rendered, page } = message; | ||
if (durationMs > 1000) { | ||
const color = | ||
durationMs > 5000 ? chalk.red | ||
: durationMs > 1000 ? chalk.yellow | ||
: durationMs > 100 ? chalk.blue | ||
: chalk.green; | ||
// eslint-disable-next-line no-console | ||
console.log(`${color(durationMs.toFixed(2).padEnd(8))} Rendered ${page.outputPath} in`); | ||
} | ||
return trimOuterMarkers(rendered); | ||
} else { | ||
return content; | ||
} | ||
}); | ||
} | ||
} | ||
|
||
const outerMarkersTrimmed = trimOuterMarkers(renderedContent); | ||
return outerMarkersTrimmed; | ||
}); | ||
}; | ||
// Lit SSR includes comment markers to track the outer template from | ||
// the template we've generated here, but it's not possible for this | ||
// outer template to be hydrated, so they serve no purpose. | ||
function trimOuterMarkers(renderedContent: string) { | ||
return renderedContent | ||
.replace(/^((<!--[^<>]*-->)|(<\?>)|\s)+/, '') | ||
.replace(/((<!--[^<>]*-->)|(<\?>)|\s)+$/, ''); | ||
} | ||
|
Oops, something went wrong.