@lit-labs/eleventy-plugin-lit
Advanced tools
Comparing version 0.1.1 to 0.2.0
@@ -6,8 +6,3 @@ /** | ||
*/ | ||
/// <reference types="node" /> | ||
declare const path: import("path").PlatformPath; | ||
declare type LitPluginOptions = { | ||
componentModules?: string[]; | ||
}; | ||
declare const iterableToString: (iterable: Iterable<string>) => string; | ||
export {}; | ||
//# sourceMappingURL=index.d.ts.map |
234
index.js
@@ -7,27 +7,96 @@ "use strict"; | ||
*/ | ||
// Note this file must be CommonJS for compatibility with Eleventy, but we can't | ||
// rely on TypeScript's CommonJS output mode, because that will also convert | ||
// dynamic import() calls to require() calls. That would be bad, because we need | ||
// to import ES modules from @lit-labs/ssr, which requires preserved import() | ||
// calls. | ||
// | ||
// So instead we use TypeScript's ESM output mode, but explicitly write | ||
// require() calls for the CommonJS modules we import. | ||
// | ||
// See https://github.com/microsoft/TypeScript/issues/43329 and | ||
// https://github.com/microsoft/TypeScript#22321 for more details. | ||
/* eslint-disable @typescript-eslint/no-var-requires */ | ||
const path = require('path'); | ||
module.exports = { | ||
configFunction: function ( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
eleventyConfig, { componentModules } = {}) { | ||
if (require('vm').Module === undefined) { | ||
// Show a more friendly error message if the --experimental-vm-modules | ||
// flag is missing. | ||
// TODO(aomarks) Rendering in a worker would remove the need for this flag. | ||
const red = '\u001b[31m'; | ||
const yellow = '\u001b[33m'; | ||
const reset = '\u001b[0m'; | ||
console.error(`${yellow} | ||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); | ||
}) : (function(o, m, k, k2) { | ||
if (k2 === undefined) k2 = k; | ||
o[k2] = m[k]; | ||
})); | ||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { | ||
Object.defineProperty(o, "default", { enumerable: true, value: v }); | ||
}) : function(o, v) { | ||
o["default"] = v; | ||
}); | ||
var __importStar = (this && this.__importStar) || function (mod) { | ||
if (mod && mod.__esModule) return mod; | ||
var result = {}; | ||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); | ||
__setModuleDefault(result, mod); | ||
return result; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const path = __importStar(require("path")); | ||
const worker_threads_1 = require("worker_threads"); | ||
function configureWorker( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
eleventyConfig, resolvedComponentModules) { | ||
let worker; | ||
const requestIdResolveMap = new Map(); | ||
let requestId = 0; | ||
eleventyConfig.on('eleventy.before', async () => { | ||
worker = new worker_threads_1.Worker(path.resolve(__dirname, './worker/worker.js')); | ||
worker.on('error', (err) => { | ||
console.error('Unexpected error while rendering lit component in worker thread', err); | ||
throw err; | ||
}); | ||
let requestResolve; | ||
const requestPromise = new Promise((resolve) => { | ||
requestResolve = resolve; | ||
}); | ||
worker.on('message', (message) => { | ||
switch (message.type) { | ||
case 'initialize-response': { | ||
requestResolve(); | ||
break; | ||
} | ||
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; | ||
} | ||
} | ||
}); | ||
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 (content, outputPath) => { | ||
if (!outputPath.endsWith('.html')) { | ||
return content; | ||
} | ||
const renderedContent = await new Promise((resolve) => { | ||
requestIdResolveMap.set(requestId, resolve); | ||
const message = { | ||
type: 'render-request', | ||
id: requestId++, | ||
content, | ||
}; | ||
worker.postMessage(message); | ||
}); | ||
const outerMarkersTrimmed = trimOuterMarkers(renderedContent); | ||
return outerMarkersTrimmed; | ||
}); | ||
} | ||
function configureVm( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
eleventyConfig, resolvedComponentModules) { | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
if (require('vm').Module === undefined) { | ||
// Show a more friendly error message if the --experimental-vm-modules | ||
// flag is missing. | ||
const red = '\u001b[31m'; | ||
const yellow = '\u001b[33m'; | ||
const reset = '\u001b[0m'; | ||
console.error(`${yellow} | ||
┌─────────────────────${red} ERROR ${yellow}─────────────────────┐ | ||
@@ -44,5 +113,53 @@ │${reset} ${yellow}│ | ||
${reset}`); | ||
throw new Error('@lit-labs/eleventy-plugin-lit requires that eleventy be launched ' + | ||
'with NODE_OPTIONS=--experimental-vm-modules'); | ||
throw new Error('@lit-labs/eleventy-plugin-lit requires that eleventy be launched ' + | ||
'with NODE_OPTIONS=--experimental-vm-modules'); | ||
} | ||
const renderModulePath = path.join(process.cwd(), 'arbitrary.js'); | ||
let contextifiedRender; | ||
let contextifiedUnsafeHTML; | ||
// Create a fresh context before each build, so that our module cache resets | ||
// on every --watch mode build. | ||
// TODO(aomarks) For better performance, we could re-use contexts between | ||
// build, but selectively invalidate its cache so that only the user's | ||
// modules are reloaded. | ||
eleventyConfig.on('eleventy.before', async () => { | ||
// Note this file must be CommonJS for compatibility with Eleventy, but | ||
// TypeScript's CommonJS output mode will also convert dynamic import() | ||
// calls to require() calls. That would be bad, because we need | ||
// to import ES modules from @lit-labs/ssr, which requires preserved | ||
// import() calls. | ||
// So we use eval(`import()`) instead | ||
// | ||
// See https://github.com/microsoft/TypeScript/issues/43329 for details | ||
const { getWindow } = (await eval(`import('@lit-labs/ssr/lib/dom-shim.js')`)); | ||
const { ModuleLoader } = (await eval(`import('@lit-labs/ssr/lib/module-loader.js')`)); | ||
const window = getWindow({ includeJSBuiltIns: true }); | ||
const loader = new ModuleLoader({ global: window }); | ||
// TODO(aomarks) Replace with concurrent Promise.all version once | ||
// https://github.com/lit/lit/issues/2549 has been addressed. | ||
for (const module of resolvedComponentModules) { | ||
await loader.importModule(module, renderModulePath); | ||
} | ||
contextifiedRender = (await loader.importModule('@lit-labs/ssr/lib/render-lit-html.js', renderModulePath)).module.namespace.render; | ||
// TOOD(aomarks) We could also directly synthesize an html TemplateResult | ||
// instead of doing so via the the unsafeHTML directive. The directive is | ||
// performing some extra validation that doesn't really apply to us. | ||
contextifiedUnsafeHTML = (await loader.importModule('lit/directives/unsafe-html.js', renderModulePath)).module.namespace.unsafeHTML; | ||
}); | ||
eleventyConfig.addTransform('render-lit', async (content, outputPath) => { | ||
if (!outputPath.endsWith('.html')) { | ||
return content; | ||
} | ||
// TODO(aomarks) Maybe we should provide a `renderUnsafeHtml` function | ||
// directly from SSR which does this. | ||
const iterator = contextifiedRender(contextifiedUnsafeHTML(content)); | ||
const concatenated = iterableToString(iterator); | ||
const outerMarkersTrimmed = trimOuterMarkers(concatenated); | ||
return outerMarkersTrimmed; | ||
}); | ||
} | ||
module.exports = { | ||
configFunction: function ( | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
eleventyConfig, { componentModules, mode = 'worker' } = {}) { | ||
if (componentModules === undefined || componentModules.length === 0) { | ||
@@ -53,48 +170,29 @@ // If there are no component modules, we could never have anything to | ||
} | ||
const renderModulePath = path.join(process.cwd(), 'arbitrary.js'); | ||
const resolvedComponentModules = componentModules.map((module) => path.resolve(process.cwd(), module)); | ||
let contextifiedRender; | ||
let contextifiedUnsafeHTML; | ||
// Create a fresh context before each build, so that our module cache resets | ||
// on every --watch mode build. | ||
// TODO(aomarks) For better performance, we could re-use contexts between | ||
// build, but selectively invalidate its cache so that only the user's | ||
// modules are reloaded. | ||
eleventyConfig.on('eleventy.before', async () => { | ||
const { getWindow } = await import('@lit-labs/ssr/lib/dom-shim.js'); | ||
const { ModuleLoader } = await import('@lit-labs/ssr/lib/module-loader.js'); | ||
const window = getWindow({ includeJSBuiltIns: true }); | ||
const loader = new ModuleLoader({ global: window }); | ||
// TODO(aomarks) Replace with concurrent Promise.all version once | ||
// https://github.com/lit/lit/issues/2549 has been addressed. | ||
for (const module of resolvedComponentModules) { | ||
await loader.importModule(module, renderModulePath); | ||
switch (mode) { | ||
case 'worker': { | ||
configureWorker(eleventyConfig, resolvedComponentModules); | ||
break; | ||
} | ||
contextifiedRender = (await loader.importModule('@lit-labs/ssr/lib/render-lit-html.js', renderModulePath)).module.namespace.render; | ||
// TOOD(aomarks) We could also directly synthesize an html TemplateResult | ||
// instead of doing so via the the unsafeHTML directive. The directive is | ||
// performing some extra validation that doesn't really apply to us. | ||
contextifiedUnsafeHTML = (await loader.importModule('lit/directives/unsafe-html.js', renderModulePath)).module.namespace.unsafeHTML; | ||
}); | ||
eleventyConfig.addTransform('render-lit', async (content, outputPath) => { | ||
if (!outputPath.endsWith('.html')) { | ||
return content; | ||
case 'vm': { | ||
configureVm(eleventyConfig, resolvedComponentModules); | ||
break; | ||
} | ||
// TODO(aomarks) Maybe we should provide a `renderUnsafeHtml` function | ||
// directly from SSR which does this. | ||
const iterator = contextifiedRender(contextifiedUnsafeHTML(content)); | ||
const concatenated = iterableToString(iterator); | ||
// 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. | ||
// TODO(aomarks) Maybe we should provide an option to SSR option to skip | ||
// outer markers (though note there are 2 layers of markers due to the | ||
// use of the unsafeHTML directive). | ||
const outerMarkersTrimmed = concatenated | ||
.replace(/^((<!--[^<>]*-->)|(<\?>)|\s)+/, '') | ||
.replace(/((<!--[^<>]*-->)|(<\?>)|\s)+$/, ''); | ||
return outerMarkersTrimmed; | ||
}); | ||
default: { | ||
throw new Error('Invalid mode provided for @lit-labs/eleventy-plugin-lit'); | ||
} | ||
} | ||
}, | ||
}; | ||
// 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. | ||
// TODO(aomarks) Maybe we should provide an option to SSR option to skip | ||
// outer markers (though note there are 2 layers of markers due to the | ||
// use of the unsafeHTML directive). | ||
function trimOuterMarkers(renderedContent) { | ||
return renderedContent | ||
.replace(/^((<!--[^<>]*-->)|(<\?>)|\s)+/, '') | ||
.replace(/((<!--[^<>]*-->)|(<\?>)|\s)+$/, ''); | ||
} | ||
// Assuming this is faster than Array.from(iter).join(); | ||
@@ -101,0 +199,0 @@ // TODO: perf test |
{ | ||
"name": "@lit-labs/eleventy-plugin-lit", | ||
"version": "0.1.1", | ||
"version": "0.2.0", | ||
"description": "Eleventy plugin for rendering Lit components.", | ||
@@ -24,3 +24,4 @@ "author": "Google LLC", | ||
"files": [ | ||
"/index.{d.ts,d.ts.map,js,js.map}" | ||
"/index.{d.ts,d.ts.map,js,js.map}", | ||
"/worker/worker.{d.ts,d.ts.map,js,js.map}" | ||
], | ||
@@ -36,3 +37,3 @@ "engines": { | ||
"build:demo:watch": "cd demo && NODE_OPTIONS=--experimental-vm-modules eleventy --watch", | ||
"clean": "rimraf \"index.{js,js.map,d.ts,d.ts.map}\" \"test/**/*.{js,js.map,d.ts,d.ts.map}\" demo/_site .tsbuildinfo", | ||
"clean": "rimraf \"index.{js,js.map,d.ts,d.ts.map}\" \"test/**/*.{js,js.map,d.ts,d.ts.map}\" \"worker/**/*.{js,js.map,d.ts,d.ts.map}\" demo/_site *.tsbuildinfo", | ||
"serve:demo": "wds --watch --node-resolve --open demo/_site/test", | ||
@@ -39,0 +40,0 @@ "test": "uvu test/ \".*-test.js$\"" |
@@ -57,2 +57,3 @@ # @lit-labs/eleventy-plugin-lit | ||
eleventyConfig.addPlugin(litPlugin, { | ||
mode: 'worker', | ||
componentModules: [ | ||
@@ -67,2 +68,20 @@ 'js/demo-greeter.js', | ||
### Configure mode | ||
Use the `mode` setting to tell the plugin which mode to use for rendering. | ||
The plugin currently supports either `'worker'` or `'vm'`. | ||
`'worker'` mode (default) utilizes | ||
[worker threads](https://nodejs.org/api/worker_threads.html#worker-threads) | ||
to render components in isolation. | ||
`'vm'` mode utilizes [`vm.Module`](https://nodejs.org/api/vm.html#class-vmmodule) | ||
for context isolation and therefore eleventy _must_ be executed with the | ||
`--experimental-vm-modules` Node flag enabled. This flag is available in | ||
Node versions `12.16.0` and above. | ||
```sh | ||
NODE_OPTIONS=--experimental-vm-modules eleventy | ||
``` | ||
### Configure component modules | ||
@@ -84,18 +103,20 @@ | ||
### Enable experimental VM modules | ||
Note that in `'worker'` mode, Node determines the module system | ||
[accordingly](https://nodejs.org/api/packages.html#determining-module-system), | ||
and as such care must be taken to ensure Node reads them as ESM files | ||
while still reading the eleventy config file as CommonJS. | ||
> 🚧 Note: Removing this requirement is on the [roadmap](#roadmap). Follow | ||
> [#2494](https://github.com/lit/lit/issues/2484) for progress and discussion. | ||
> 🚧 | ||
Some options are: | ||
Eleventy _must_ be executed with the | ||
[`--experimental-vm-modules`](https://nodejs.org/api/vm.html#class-vmmodule) | ||
Node flag enabled. This flag is available in Node versions `12.16.0` and above. | ||
This flag allows the lit plugin to render components without modifying any | ||
global state, and enables compatibility with Eleventy's `--watch` mode. | ||
1. Add `{"type": "module"}` to your base `package.json`, make sure the | ||
eleventy config file ends with the `.cjs` extension, and supply it as | ||
a command line argument to `eleventy`. | ||
```sh | ||
NODE_OPTIONS=--experimental-vm-modules eleventy | ||
``` | ||
```sh | ||
eleventy --config=.eleventy.cjs | ||
``` | ||
1. Put all component `.js` files in a subdirectory with a nested `package.json` with | ||
`{"type": "module"}`. | ||
### Watch mode | ||
@@ -406,7 +427,2 @@ | ||
- [[#2484](https://github.com/lit/lit/issues/2484)] Use [worker | ||
threads](https://nodejs.org/api/worker_threads.html) instead of [isolated VM | ||
modules](https://nodejs.org/api/vm.html#class-vmmodule) so that the | ||
`--experimental-vm-modules` flag is no longer required. | ||
- [[#2485](https://github.com/lit/lit/issues/2485)] Provide a mechanism for | ||
@@ -435,1 +451,5 @@ passing [Eleventy data](https://www.11ty.dev/docs/data/) to components as | ||
Please see [CONTRIBUTING.md](../../../CONTRIBUTING.md). | ||
### Testing environment variables: | ||
- `SHOW_TEST_OUTPUT`: Set to show all `stdout` and `stderr` from spawned eleventy invocations in test cases. |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
46236
11
255
451
4