From 463f5c3aa87c82065f70f918930ed4157822c62a Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Wed, 20 Jul 2022 11:41:38 -0700 Subject: [PATCH 01/10] Add ability to create node build in rollup config --- rollup-common.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/rollup-common.js b/rollup-common.js index b255e4ea35..1f09145c34 100644 --- a/rollup-common.js +++ b/rollup-common.js @@ -255,6 +255,7 @@ export function litProdConfig({ packageName, outputDir = './', copyHtmlTests = true, + nodeBuild = false, // eslint-disable-next-line no-undef } = options) { const classPropertyPrefix = PACKAGE_CLASS_PREFIXES[packageName]; @@ -348,6 +349,7 @@ export function litProdConfig({ skipBundleOutput, ], }, + // Production build { input: entryPoints.map((name) => `development/${name}.js`), output: { @@ -423,6 +425,58 @@ export function litProdConfig({ : []), ], }, + // Node build + ...(nodeBuild + ? [ + { + input: entryPoints.map((name) => `development/${name}.js`), + output: { + dir: `${outputDir}/node`, + format: 'esm', + preserveModules: true, + sourcemap: !CHECKSIZE, + }, + external, + plugins: [ + replace({ + preventAssignment: true, + values: { + // Setting NODE_MODE to true enables node-specific behaviors, + // i.e. using globalThis instead of window, and shimming APIs + // needed for Lit bootup. + 'const NODE_MODE = false': 'const NODE_MODE = true', + // Other variables should behave like prod mode. + 'const DEV_MODE = true': 'const DEV_MODE = false', + 'const ENABLE_EXTRA_SECURITY_HOOKS = true': + 'const ENABLE_EXTRA_SECURITY_HOOKS = false', + 'const ENABLE_SHADYDOM_NOPATCH = true': + 'const ENABLE_SHADYDOM_NOPATCH = false', + }, + }), + sourcemaps(), + // We want the Node build to be minified because: + // + // 1. It should be very slightly faster, even in Node where bytes + // are not as important as in the browser. + // + // 2. It means we don't need a Node build for lit-element. There + // is no Node-specific logic needed in lit-element. However, + // lit-element and reactive-element must be consistently + // minified or unminified together, because lit-element + // references properties from reactive-element which will + // otherwise have different names. The default export that + // lit-element will use is minified. + terser(terserOptions), + summary({ + showBrotliSize: true, + showGzippedSize: true, + }), + ...(CHECKSIZE ? [skipBundleOutput] : []), + ], + }, + ] + : []), + // CDN bundles ...bundled.map(({file, output, name, format, sourcemapPathTransform}) => litMonoBundleConfig({ file, From 951d379d832898493ef60c59c7f758eabdc8a86a Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Wed, 20 Jul 2022 11:43:03 -0700 Subject: [PATCH 02/10] Add node build to lit-html --- .eslintignore | 1 + .prettierignore | 1 + packages/lit-html/.gitignore | 1 + packages/lit-html/package.json | 47 ++++++++++++++++++++-- packages/lit-html/rollup.config.js | 1 + packages/lit-html/src/lit-html.ts | 36 ++++++++++------- packages/lit-html/src/test/node-imports.ts | 37 +++++++++++++++++ 7 files changed, 107 insertions(+), 17 deletions(-) create mode 100644 packages/lit-html/src/test/node-imports.ts diff --git a/.eslintignore b/.eslintignore index 4ec7c50df3..1b792b00a5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -62,6 +62,7 @@ packages/lit-element/polyfill-support.* packages/lit-element/private-ssr-support.* packages/lit-html/development/ +packages/lit-html/node/ packages/lit-html/version-stability-build/ packages/lit-html/directives/ packages/lit-html/node_modules/ diff --git a/.prettierignore b/.prettierignore index 58d33fa037..625bc70dfc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -62,6 +62,7 @@ packages/lit-element/polyfill-support.* packages/lit-element/private-ssr-support.* packages/lit-html/development/ +packages/lit-html/node/ packages/lit-html/version-stability-build/ packages/lit-html/directives/ packages/lit-html/node_modules/ diff --git a/packages/lit-html/.gitignore b/packages/lit-html/.gitignore index 049a253061..843b4373d0 100644 --- a/packages/lit-html/.gitignore +++ b/packages/lit-html/.gitignore @@ -1,4 +1,5 @@ /development/ +/node/ /version-stability-build/ /directives/ /node_modules/ diff --git a/packages/lit-html/package.json b/packages/lit-html/package.json index b8c6fe4c44..460afc2e76 100644 --- a/packages/lit-html/package.json +++ b/packages/lit-html/package.json @@ -15,141 +15,169 @@ "exports": { ".": { "types": "./development/lit-html.d.ts", + "node": "./node/lit-html.js", "development": "./development/lit-html.js", "default": "./lit-html.js" }, "./async-directive.js": { "types": "./development/async-directive.d.ts", + "node": "./node/async-directive.js", "development": "./development/async-directive.js", "default": "./async-directive.js" }, "./directive-helpers.js": { "types": "./development/directive-helpers.d.ts", + "node": "./node/directive-helpers.js", "development": "./development/directive-helpers.js", "default": "./directive-helpers.js" }, "./directive.js": { "types": "./development/directive.d.ts", + "node": "./node/directive.js", "development": "./development/directive.js", "default": "./directive.js" }, "./directives/async-append.js": { "types": "./development/directives/async-append.d.ts", + "node": "./node/directives/async-append.js", "development": "./development/directives/async-append.js", "default": "./directives/async-append.js" }, "./directives/async-replace.js": { "types": "./development/directives/async-replace.d.ts", + "node": "./node/directives/async-replace.js", "development": "./development/directives/async-replace.js", "default": "./directives/async-replace.js" }, "./directives/cache.js": { "types": "./development/directives/cache.d.ts", + "node": "./node/directives/cache.js", "development": "./development/directives/cache.js", "default": "./directives/cache.js" }, "./directives/choose.js": { "types": "./development/directives/choose.d.ts", + "node": "./node/directives/choose.js", "development": "./development/directives/choose.js", "default": "./directives/choose.js" }, "./directives/class-map.js": { "types": "./development/directives/class-map.d.ts", + "node": "./node/directives/class-map.js", "development": "./development/directives/class-map.js", "default": "./directives/class-map.js" }, "./directives/guard.js": { "types": "./development/directives/guard.d.ts", + "node": "./node/directives/guard.js", "development": "./development/directives/guard.js", "default": "./directives/guard.js" }, "./directives/if-defined.js": { "types": "./development/directives/if-defined.d.ts", + "node": "./node/directives/if-defined.js", "development": "./development/directives/if-defined.js", "default": "./directives/if-defined.js" }, "./directives/join.js": { "types": "./development/directives/join.d.ts", + "node": "./node/directives/join.js", "development": "./development/directives/join.js", "default": "./directives/join.js" }, "./directives/keyed.js": { "types": "./development/directives/keyed.d.ts", + "node": "./node/directives/keyed.js", "development": "./development/directives/keyed.js", "default": "./directives/keyed.js" }, "./directives/live.js": { "types": "./development/directives/live.d.ts", + "node": "./node/directives/live.js", "development": "./development/directives/live.js", "default": "./directives/live.js" }, "./directives/map.js": { "types": "./development/directives/map.d.ts", + "node": "./node/directives/map.js", "development": "./development/directives/map.js", "default": "./directives/map.js" }, "./directives/range.js": { "types": "./development/directives/range.d.ts", + "node": "./node/directives/range.js", "development": "./development/directives/range.js", "default": "./directives/range.js" }, "./directives/ref.js": { "types": "./development/directives/ref.d.ts", + "node": "./node/directives/ref.js", "development": "./development/directives/ref.js", "default": "./directives/ref.js" }, "./directives/repeat.js": { "types": "./development/directives/repeat.d.ts", + "node": "./node/directives/repeat.js", "development": "./development/directives/repeat.js", "default": "./directives/repeat.js" }, "./directives/style-map.js": { "types": "./development/directives/style-map.d.ts", + "node": "./node/directives/style-map.js", "development": "./development/directives/style-map.js", "default": "./directives/style-map.js" }, "./directives/template-content.js": { "types": "./development/directives/template-content.d.ts", + "node": "./node/directives/template-content.js", "development": "./development/directives/template-content.js", "default": "./directives/template-content.js" }, "./directives/unsafe-html.js": { "types": "./development/directives/unsafe-html.d.ts", + "node": "./node/directives/unsafe-html.js", "development": "./development/directives/unsafe-html.js", "default": "./directives/unsafe-html.js" }, "./directives/unsafe-svg.js": { "types": "./development/directives/unsafe-svg.d.ts", + "node": "./node/directives/unsafe-svg.js", "development": "./development/directives/unsafe-svg.js", "default": "./directives/unsafe-svg.js" }, "./directives/until.js": { "types": "./development/directives/until.d.ts", + "node": "./node/directives/until.js", "development": "./development/directives/until.js", "default": "./directives/until.js" }, "./directives/when.js": { "types": "./development/directives/when.d.ts", + "node": "./node/directives/when.js", "development": "./development/directives/when.js", "default": "./directives/when.js" }, "./experimental-hydrate.js": { "types": "./development/experimental-hydrate.d.ts", + "node": "./node/experimental-hydrate.js", "development": "./development/experimental-hydrate.js", "default": "./experimental-hydrate.js" }, "./polyfill-support.js": { "types": "./development/polyfill-support.d.ts", + "node": "./node/polyfill-support.js", "development": "./development/polyfill-support.js", "default": "./polyfill-support.js" }, "./private-ssr-support.js": { "types": "./development/private-ssr-support.d.ts", + "node": "./node/private-ssr-support.js", "development": "./development/private-ssr-support.js", "default": "./private-ssr-support.js" }, "./static.js": { "types": "./development/static.d.ts", + "node": "./node/static.js", "development": "./development/static.js", "default": "./static.js" } @@ -165,7 +193,8 @@ "prepublishOnly": "npm run check-version", "test": "wireit", "test:dev": "wireit", - "test:prod": "wireit" + "test:prod": "wireit", + "test:node": "wireit" }, "files": [ "/async-directive.{d.ts,d.ts.map,js,js.map}", @@ -178,7 +207,8 @@ "/static.{d.ts,d.ts.map,js,js.map}", "/development/", "!/development/test/", - "/directives/" + "/directives/", + "/node/" ], "wireit": { "build": { @@ -240,7 +270,8 @@ "test/*_test.html", "development/test/*_test.html", "test/polyfill-support/*_test.html", - "development/test/polyfill-support/*_test.html" + "development/test/polyfill-support/*_test.html", + "node/" ] }, "build:version-stability-test": { @@ -281,6 +312,7 @@ "dependencies": [ "test:dev", "test:prod", + "test:node", "check-version" ] }, @@ -303,6 +335,15 @@ ], "files": [], "output": [] + }, + "test:node": { + "command": "node development/test/node-imports.js", + "dependencies": [ + "build:ts", + "build:rollup" + ], + "files": [], + "output": [] } }, "dependencies": { diff --git a/packages/lit-html/rollup.config.js b/packages/lit-html/rollup.config.js index a02cc8a721..46f8bd3b72 100644 --- a/packages/lit-html/rollup.config.js +++ b/packages/lit-html/rollup.config.js @@ -45,6 +45,7 @@ export const defaultConfig = (options = {}) => file: 'polyfill-support', }, ], + nodeBuild: true, ...options, }); diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 2056435e7a..26397feb9a 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -10,6 +10,8 @@ import type {Directive, DirectiveResult, PartInfo} from './directive.js'; const DEV_MODE = true; const ENABLE_EXTRA_SECURITY_HOOKS = true; const ENABLE_SHADYDOM_NOPATCH = true; +const NODE_MODE = false; +const global = NODE_MODE ? globalThis : window; /** * Contains types that are part of the unstable debug API. @@ -190,12 +192,12 @@ interface DebugLoggingWindow { */ const debugLogEvent = DEV_MODE ? (event: LitUnstable.DebugLog.Entry) => { - const shouldEmit = (window as unknown as DebugLoggingWindow) + const shouldEmit = (global as unknown as DebugLoggingWindow) .emitLitDebugLogEvents; if (!shouldEmit) { return; } - window.dispatchEvent( + global.dispatchEvent( new CustomEvent('lit-debug', { detail: event, }) @@ -210,16 +212,16 @@ let debugLogRenderId = 0; let issueWarning: (code: string, warning: string) => void; if (DEV_MODE) { - globalThis.litIssuedWarnings ??= new Set(); + global.litIssuedWarnings ??= new Set(); // Issue a warning, if we haven't already. issueWarning = (code: string, warning: string) => { warning += code ? ` See https://lit.dev/msg/${code} for more information.` : ''; - if (!globalThis.litIssuedWarnings!.has(warning)) { + if (!global.litIssuedWarnings!.has(warning)) { console.warn(warning); - globalThis.litIssuedWarnings!.add(warning); + global.litIssuedWarnings!.add(warning); } }; @@ -231,12 +233,12 @@ if (DEV_MODE) { const wrap = ENABLE_SHADYDOM_NOPATCH && - window.ShadyDOM?.inUse && - window.ShadyDOM?.noPatch === true - ? window.ShadyDOM!.wrap + global.ShadyDOM?.inUse && + global.ShadyDOM?.noPatch === true + ? global.ShadyDOM!.wrap : (node: Node) => node; -const trustedTypes = (globalThis as unknown as Partial).trustedTypes; +const trustedTypes = (global as unknown as Partial).trustedTypes; /** * Our TrustedTypePolicy for HTML which is declared using the html template @@ -341,7 +343,13 @@ const markerMatch = '?' + marker; // syntax because it's slightly smaller, but parses as a comment node. const nodeMarker = `<${markerMatch}>`; -const d = document; +const d = NODE_MODE + ? ({ + createTreeWalker() { + return {}; + }, + } as unknown as Document) + : document; // Creates a dynamic marker. We never have to search for these in the DOM. const createMarker = (v = '') => d.createComment(v); @@ -2147,14 +2155,14 @@ export const _$LH = { // Apply polyfills if available const polyfillSupport = DEV_MODE - ? window.litHtmlPolyfillSupportDevMode - : window.litHtmlPolyfillSupport; + ? global.litHtmlPolyfillSupportDevMode + : global.litHtmlPolyfillSupport; polyfillSupport?.(Template, ChildPart); // IMPORTANT: do not change the property name or the assignment expression. // This line will be used in regexes to search for lit-html usage. -(globalThis.litHtmlVersions ??= []).push('2.2.6'); -if (DEV_MODE && globalThis.litHtmlVersions.length > 1) { +(global.litHtmlVersions ??= []).push('2.2.6'); +if (DEV_MODE && global.litHtmlVersions.length > 1) { issueWarning!( 'multiple-versions', `Multiple versions of Lit loaded. ` + diff --git a/packages/lit-html/src/test/node-imports.ts b/packages/lit-html/src/test/node-imports.ts new file mode 100644 index 0000000000..2c3c953516 --- /dev/null +++ b/packages/lit-html/src/test/node-imports.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +// This file will be loaded by Node from the node:test script to verify that all +// exports of this package can be imported without crashing in Node. + +import 'lit-html'; +import 'lit-html/directives/async-append.js'; +import 'lit-html/directives/async-replace.js'; +import 'lit-html/directives/cache.js'; +import 'lit-html/directives/choose.js'; +import 'lit-html/directives/class-map.js'; +import 'lit-html/directives/guard.js'; +import 'lit-html/directives/if-defined.js'; +import 'lit-html/directives/join.js'; +import 'lit-html/directives/keyed.js'; +import 'lit-html/directives/live.js'; +import 'lit-html/directives/map.js'; +import 'lit-html/directives/range.js'; +import 'lit-html/directives/ref.js'; +import 'lit-html/directives/repeat.js'; +import 'lit-html/directives/style-map.js'; +import 'lit-html/directives/template-content.js'; +import 'lit-html/directives/unsafe-html.js'; +import 'lit-html/directives/unsafe-svg.js'; +import 'lit-html/directives/until.js'; +import 'lit-html/directives/when.js'; +import 'lit-html/directive.js'; +import 'lit-html/directive-helpers.js'; +import 'lit-html/async-directive.js'; +import 'lit-html/static.js'; +import 'lit-html/experimental-hydrate.js'; +import 'lit-html/private-ssr-support.js'; +import 'lit-html/polyfill-support.js'; From a2e074a66cba484ab88d3ea483aac88e5e80e5d6 Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Wed, 20 Jul 2022 11:58:31 -0700 Subject: [PATCH 03/10] Add node build to reactive-element --- .eslintignore | 1 + .prettierignore | 1 + packages/reactive-element/.gitignore | 1 + packages/reactive-element/package.json | 34 +++++++++++++++++-- packages/reactive-element/rollup.config.js | 1 + packages/reactive-element/src/css-tag.ts | 9 +++-- .../src/decorators/custom-element.ts | 4 +-- .../src/decorators/query-assigned-elements.ts | 5 ++- .../reactive-element/src/reactive-element.ts | 30 ++++++++++------ .../reactive-element/src/test/node-imports.ts | 29 ++++++++++++++++ 10 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 packages/reactive-element/src/test/node-imports.ts diff --git a/.eslintignore b/.eslintignore index 1b792b00a5..81d5ab79cf 100644 --- a/.eslintignore +++ b/.eslintignore @@ -119,6 +119,7 @@ packages/localize-tools/testdata/*/output/ packages/reactive-element/decorators/ packages/reactive-element/development/ +packages/reactive-element/node/ packages/reactive-element/test/ packages/reactive-element/css-tag.* packages/reactive-element/decorators.* diff --git a/.prettierignore b/.prettierignore index 625bc70dfc..1a9631ebbb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -105,6 +105,7 @@ packages/localize-tools/testdata/*/output/ packages/reactive-element/decorators/ packages/reactive-element/development/ +packages/reactive-element/node/ packages/reactive-element/test/ packages/reactive-element/css-tag.* packages/reactive-element/decorators.* diff --git a/packages/reactive-element/.gitignore b/packages/reactive-element/.gitignore index fd3572c58b..963243dc86 100644 --- a/packages/reactive-element/.gitignore +++ b/packages/reactive-element/.gitignore @@ -1,5 +1,6 @@ /decorators/ /development/ +/node/ /test/ /css-tag.* /decorators.* diff --git a/packages/reactive-element/package.json b/packages/reactive-element/package.json index 31981d88c8..5f8ff01cfd 100644 --- a/packages/reactive-element/package.json +++ b/packages/reactive-element/package.json @@ -19,76 +19,91 @@ "exports": { ".": { "types": "./development/reactive-element.d.ts", + "node": "./node/reactive-element.js", "development": "./development/reactive-element.js", "default": "./reactive-element.js" }, "./css-tag.js": { "types": "./development/css-tag.d.ts", + "node": "./node/css-tag.js", "development": "./development/css-tag.js", "default": "./css-tag.js" }, "./decorators.js": { "types": "./development/decorators.d.ts", + "node": "./node/decorators.js", "development": "./development/decorators.js", "default": "./decorators.js" }, "./decorators/base.js": { "types": "./development/decorators/base.d.ts", + "node": "./node/decorators/base.js", "development": "./development/decorators/base.js", "default": "./decorators/base.js" }, "./decorators/custom-element.js": { "types": "./development/decorators/custom-element.d.ts", + "node": "./node/decorators/custom-element.js", "development": "./development/decorators/custom-element.js", "default": "./decorators/custom-element.js" }, "./decorators/event-options.js": { "types": "./development/decorators/event-options.d.ts", + "node": "./node/decorators/event-options.js", "development": "./development/decorators/event-options.js", "default": "./decorators/event-options.js" }, "./decorators/property.js": { "types": "./development/decorators/property.d.ts", + "node": "./node/decorators/property.js", "development": "./development/decorators/property.js", "default": "./decorators/property.js" }, "./decorators/query-all.js": { "types": "./development/decorators/query-all.d.ts", + "node": "./node/decorators/query-all.js", "development": "./development/decorators/query-all.js", "default": "./decorators/query-all.js" }, "./decorators/query-assigned-elements.js": { "types": "./development/decorators/query-assigned-elements.d.ts", + "node": "./node/decorators/query-assigned-elements.js", "development": "./development/decorators/query-assigned-elements.js", "default": "./decorators/query-assigned-elements.js" }, "./decorators/query-assigned-nodes.js": { "types": "./development/decorators/query-assigned-nodes.d.ts", + "node": "./node/decorators/query-assigned-nodes.js", "development": "./development/decorators/query-assigned-nodes.js", "default": "./decorators/query-assigned-nodes.js" }, "./decorators/query-async.js": { "types": "./development/decorators/query-async.d.ts", + "node": "./node/decorators/query-async.js", "development": "./development/decorators/query-async.js", "default": "./decorators/query-async.js" }, "./decorators/query.js": { "types": "./development/decorators/query.d.ts", + "node": "./node/decorators/query.js", "development": "./development/decorators/query.js", "default": "./decorators/query.js" }, "./decorators/state.js": { "types": "./development/decorators/state.d.ts", + "node": "./node/decorators/state.js", "development": "./development/decorators/state.js", "default": "./decorators/state.js" }, "./polyfill-support.js": { "types": "./development/polyfill-support.d.ts", + "node": "./node/polyfill-support.js", "development": "./development/polyfill-support.js", "default": "./polyfill-support.js" }, "./reactive-controller.js": { "types": "./development/reactive-controller.d.ts", + "node": "./node/reactive-controller.js", "development": "./development/reactive-controller.js", "default": "./reactive-controller.js" } @@ -104,7 +119,8 @@ "prepublishOnly": "npm run check-version", "test": "wireit", "test:dev": "wireit", - "test:prod": "wireit" + "test:prod": "wireit", + "test:node": "wireit" }, "wireit": { "build": { @@ -164,7 +180,8 @@ "test/*_test.html", "development/test/*_test.html", "test/polyfill-support/*_test.html", - "development/test/polyfill-support/*_test.html" + "development/test/polyfill-support/*_test.html", + "node/" ] }, "build:babel": { @@ -200,6 +217,7 @@ "dependencies": [ "test:dev", "test:prod", + "test:node", "check-version" ] }, @@ -223,6 +241,15 @@ ], "files": [], "output": [] + }, + "test:node": { + "command": "node development/test/node-imports.js", + "dependencies": [ + "build:ts", + "build:rollup" + ], + "files": [], + "output": [] } }, "files": [ @@ -233,7 +260,8 @@ "/reactive-element.{d.ts,d.ts.map,js,js.map}", "/decorators/", "/development/", - "!/development/test/" + "!/development/test/", + "/node/" ], "devDependencies": { "@babel/cli": "^7.14.6", diff --git a/packages/reactive-element/rollup.config.js b/packages/reactive-element/rollup.config.js index 3121d5752f..c1f2224685 100644 --- a/packages/reactive-element/rollup.config.js +++ b/packages/reactive-element/rollup.config.js @@ -31,4 +31,5 @@ export default litProdConfig({ file: 'polyfill-support', }, ], + nodeBuild: true, }); diff --git a/packages/reactive-element/src/css-tag.ts b/packages/reactive-element/src/css-tag.ts index 7b763d7b2a..bbd00674a4 100644 --- a/packages/reactive-element/src/css-tag.ts +++ b/packages/reactive-element/src/css-tag.ts @@ -4,12 +4,15 @@ * SPDX-License-Identifier: BSD-3-Clause */ +const NODE_MODE = false; +const global = NODE_MODE ? globalThis : window; + /** * Whether the current browser supports `adoptedStyleSheets`. */ export const supportsAdoptingStyleSheets = - window.ShadowRoot && - (window.ShadyCSS === undefined || window.ShadyCSS.nativeShadow) && + global.ShadowRoot && + (global.ShadyCSS === undefined || global.ShadyCSS.nativeShadow) && 'adoptedStyleSheets' in Document.prototype && 'replace' in CSSStyleSheet.prototype; @@ -174,7 +177,7 @@ export const adoptStyles = ( styles.forEach((s) => { const style = document.createElement('style'); // eslint-disable-next-line @typescript-eslint/no-explicit-any - const nonce = (window as any)['litNonce']; + const nonce = (global as any)['litNonce']; if (nonce !== undefined) { style.setAttribute('nonce', nonce); } diff --git a/packages/reactive-element/src/decorators/custom-element.ts b/packages/reactive-element/src/decorators/custom-element.ts index e094bb31c4..d83a26d025 100644 --- a/packages/reactive-element/src/decorators/custom-element.ts +++ b/packages/reactive-element/src/decorators/custom-element.ts @@ -18,7 +18,7 @@ import {Constructor, ClassDescriptor} from './base.js'; type CustomElementClass = Omit; const legacyCustomElement = (tagName: string, clazz: CustomElementClass) => { - window.customElements.define(tagName, clazz as CustomElementConstructor); + customElements.define(tagName, clazz as CustomElementConstructor); // Cast as any because TS doesn't recognize the return type as being a // subtype of the decorated class when clazz is typed as // `Constructor` for some reason. @@ -38,7 +38,7 @@ const standardCustomElement = ( elements, // This callback is called once the class is otherwise fully defined finisher(clazz: Constructor) { - window.customElements.define(tagName, clazz); + customElements.define(tagName, clazz); }, }; }; diff --git a/packages/reactive-element/src/decorators/query-assigned-elements.ts b/packages/reactive-element/src/decorators/query-assigned-elements.ts index 25f5849f33..98a65c783b 100644 --- a/packages/reactive-element/src/decorators/query-assigned-elements.ts +++ b/packages/reactive-element/src/decorators/query-assigned-elements.ts @@ -16,11 +16,14 @@ import {decorateProperty} from './base.js'; import type {ReactiveElement} from '../reactive-element.js'; import type {QueryAssignedNodesOptions} from './query-assigned-nodes.js'; +const NODE_MODE = false; +const global = NODE_MODE ? globalThis : window; + /** * A tiny module scoped polyfill for HTMLSlotElement.assignedElements. */ const slotAssignedElements = - window.HTMLSlotElement?.prototype.assignedElements != null + global.HTMLSlotElement?.prototype.assignedElements != null ? (slot: HTMLSlotElement, opts?: AssignedNodesOptions) => slot.assignedElements(opts) : (slot: HTMLSlotElement, opts?: AssignedNodesOptions) => diff --git a/packages/reactive-element/src/reactive-element.ts b/packages/reactive-element/src/reactive-element.ts index a5d58f32f0..5ced1f4e75 100644 --- a/packages/reactive-element/src/reactive-element.ts +++ b/packages/reactive-element/src/reactive-element.ts @@ -27,6 +27,16 @@ export type { ReactiveControllerHost, } from './reactive-controller.js'; +const NODE_MODE = false; +const global = NODE_MODE ? globalThis : window; + +if (NODE_MODE) { + global.HTMLElement ??= class HTMLElement {} as unknown as typeof HTMLElement; + global.customElements ??= { + define() {}, + } as unknown as CustomElementRegistry; +} + const DEV_MODE = true; let requestUpdateThenable: (name: string) => { @@ -38,7 +48,7 @@ let requestUpdateThenable: (name: string) => { let issueWarning: (code: string, warning: string) => void; -const trustedTypes = (window as unknown as {trustedTypes?: {emptyScript: ''}}) +const trustedTypes = (global as unknown as {trustedTypes?: {emptyScript: ''}}) .trustedTypes; // Temporary workaround for https://crbug.com/993268 @@ -50,14 +60,14 @@ const emptyStringForBooleanAttribute = trustedTypes : ''; const polyfillSupport = DEV_MODE - ? window.reactiveElementPolyfillSupportDevMode - : window.reactiveElementPolyfillSupport; + ? global.reactiveElementPolyfillSupportDevMode + : global.reactiveElementPolyfillSupport; if (DEV_MODE) { // Ensure warnings are issued only 1x, even if multiple versions of Lit // are loaded. - const issuedWarnings: Set = - (globalThis.litIssuedWarnings ??= new Set()); + const issuedWarnings: Set = (global.litIssuedWarnings ??= + new Set()); // Issue a warning, if we haven't already. issueWarning = (code: string, warning: string) => { @@ -74,7 +84,7 @@ if (DEV_MODE) { ); // Issue polyfill support warning. - if (window.ShadyDOM?.inUse && polyfillSupport === undefined) { + if (global.ShadyDOM?.inUse && polyfillSupport === undefined) { issueWarning( 'polyfill-support-missing', `Shadow DOM is being polyfilled via \`ShadyDOM\` but ` + @@ -139,12 +149,12 @@ interface DebugLoggingWindow { */ const debugLogEvent = DEV_MODE ? (event: ReactiveUnstable.DebugLog.Entry) => { - const shouldEmit = (window as unknown as DebugLoggingWindow) + const shouldEmit = (global as unknown as DebugLoggingWindow) .emitLitDebugLogEvents; if (!shouldEmit) { return; } - window.dispatchEvent( + global.dispatchEvent( new CustomEvent('lit-debug', { detail: event, }) @@ -1521,8 +1531,8 @@ if (DEV_MODE) { // IMPORTANT: do not change the property name or the assignment expression. // This line will be used in regexes to search for ReactiveElement usage. -(globalThis.reactiveElementVersions ??= []).push('1.3.3'); -if (DEV_MODE && globalThis.reactiveElementVersions.length > 1) { +(global.reactiveElementVersions ??= []).push('1.3.3'); +if (DEV_MODE && global.reactiveElementVersions.length > 1) { issueWarning!( 'multiple-versions', `Multiple versions of Lit loaded. Loading multiple versions ` + diff --git a/packages/reactive-element/src/test/node-imports.ts b/packages/reactive-element/src/test/node-imports.ts new file mode 100644 index 0000000000..4a3df24398 --- /dev/null +++ b/packages/reactive-element/src/test/node-imports.ts @@ -0,0 +1,29 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +// This file will be loaded by Node from the node:test script to verify that all +// exports of this package can be imported without crashing in Node. + +import '@lit/reactive-element'; +import '@lit/reactive-element/reactive-controller.js'; +import '@lit/reactive-element/css-tag.js'; +import '@lit/reactive-element/decorators.js'; +import '@lit/reactive-element/decorators/base.js'; +import '@lit/reactive-element/decorators/custom-element.js'; +import '@lit/reactive-element/decorators/event-options.js'; +import '@lit/reactive-element/decorators/state.js'; +import '@lit/reactive-element/decorators/property.js'; +import '@lit/reactive-element/decorators/query.js'; +import '@lit/reactive-element/decorators/query-all.js'; +import '@lit/reactive-element/decorators/query-assigned-elements.js'; +import '@lit/reactive-element/decorators/query-assigned-nodes.js'; +import '@lit/reactive-element/decorators/query-async.js'; + +import {customElement} from '@lit/reactive-element/decorators.js'; +import {ReactiveElement} from '@lit/reactive-element'; + +@customElement('my-element') +export class MyElement extends ReactiveElement {} From 9515303300f3a5f62c37c54030dd900a33469587 Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Wed, 20 Jul 2022 12:01:27 -0700 Subject: [PATCH 04/10] Add node test to lit-element. Does not need a node build --- packages/lit-element/package.json | 13 +++++++- packages/lit-element/src/test/node-imports.ts | 32 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 packages/lit-element/src/test/node-imports.ts diff --git a/packages/lit-element/package.json b/packages/lit-element/package.json index 3c9361baca..3543b12175 100644 --- a/packages/lit-element/package.json +++ b/packages/lit-element/package.json @@ -104,7 +104,8 @@ "prepublishOnly": "npm run check-version", "test": "wireit", "test:dev": "wireit", - "test:prod": "wireit" + "test:prod": "wireit", + "test:node": "wireit" }, "wireit": { "build": { @@ -198,6 +199,7 @@ "dependencies": [ "test:dev", "test:prod", + "test:node", "check-version" ] }, @@ -219,6 +221,15 @@ ], "files": [], "output": [] + }, + "test:node": { + "command": "node development/test/node-imports.js", + "dependencies": [ + "build:ts", + "build:rollup" + ], + "files": [], + "output": [] } }, "files": [ diff --git a/packages/lit-element/src/test/node-imports.ts b/packages/lit-element/src/test/node-imports.ts new file mode 100644 index 0000000000..3f04ac951e --- /dev/null +++ b/packages/lit-element/src/test/node-imports.ts @@ -0,0 +1,32 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +// This file will be loaded by Node from the node:test script to verify that all +// exports of this package can be imported without crashing in Node. + +import 'lit-element'; +import 'lit-element/experimental-hydrate-support.js'; +import 'lit-element/private-ssr-support.js'; +import 'lit-element/decorators.js'; +import 'lit-element/decorators/custom-element.js'; +import 'lit-element/decorators/event-options.js'; +import 'lit-element/decorators/state.js'; +import 'lit-element/decorators/property.js'; +import 'lit-element/decorators/query.js'; +import 'lit-element/decorators/query-all.js'; +import 'lit-element/decorators/query-assigned-elements.js'; +import 'lit-element/decorators/query-assigned-nodes.js'; +import 'lit-element/decorators/query-async.js'; + +import {LitElement, html} from 'lit-element'; +import {customElement} from 'lit-element/decorators.js'; + +@customElement('my-element') +export class MyElement extends LitElement { + override render() { + return html`Hello World`; + } +} From e75965832093a0ec95467a9850e687925844a3e4 Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Wed, 20 Jul 2022 12:03:14 -0700 Subject: [PATCH 05/10] Add node test to lit. Does not need a node build --- packages/lit/package.json | 15 +++++++-- packages/lit/src/test/node-imports.ts | 47 +++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 packages/lit/src/test/node-imports.ts diff --git a/packages/lit/package.json b/packages/lit/package.json index 760e962389..520c456536 100644 --- a/packages/lit/package.json +++ b/packages/lit/package.json @@ -143,7 +143,8 @@ "checksize": "wireit", "test": "wireit", "test:dev": "wireit", - "test:prod": "wireit" + "test:prod": "wireit", + "test:node": "wireit" }, "wireit": { "build": { @@ -233,7 +234,8 @@ "test": { "dependencies": [ "test:dev", - "test:prod" + "test:prod", + "test:node" ] }, "test:dev": { @@ -258,6 +260,15 @@ ], "files": [], "output": [] + }, + "test:node": { + "command": "node development/test/node-imports.js", + "dependencies": [ + "build:ts", + "build:rollup" + ], + "files": [], + "output": [] } }, "files": [ diff --git a/packages/lit/src/test/node-imports.ts b/packages/lit/src/test/node-imports.ts new file mode 100644 index 0000000000..d443a495d1 --- /dev/null +++ b/packages/lit/src/test/node-imports.ts @@ -0,0 +1,47 @@ +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ + +// This file will be loaded by Node from the node:test script to verify that all +// exports of this package can be imported without crashing in Node. + +import 'lit'; +import 'lit/decorators.js'; +import 'lit/decorators/custom-element.js'; +import 'lit/decorators/event-options.js'; +import 'lit/decorators/property.js'; +import 'lit/decorators/query.js'; +import 'lit/decorators/query-all.js'; +import 'lit/decorators/query-assigned-elements.js'; +import 'lit/decorators/query-assigned-nodes.js'; +import 'lit/decorators/query-async.js'; +import 'lit/decorators/state.js'; +import 'lit/directive-helpers.js'; +import 'lit/directive.js'; +import 'lit/directives/async-append.js'; +import 'lit/directives/async-replace.js'; +import 'lit/directives/cache.js'; +import 'lit/directives/choose.js'; +import 'lit/directives/class-map.js'; +import 'lit/directives/guard.js'; +import 'lit/directives/if-defined.js'; +import 'lit/directives/join.js'; +import 'lit/directives/keyed.js'; +import 'lit/directives/live.js'; +import 'lit/directives/map.js'; +import 'lit/directives/range.js'; +import 'lit/directives/ref.js'; +import 'lit/directives/repeat.js'; +import 'lit/directives/style-map.js'; +import 'lit/directives/template-content.js'; +import 'lit/directives/unsafe-html.js'; +import 'lit/directives/unsafe-svg.js'; +import 'lit/directives/until.js'; +import 'lit/directives/when.js'; +import 'lit/async-directive.js'; +import 'lit/html.js'; +import 'lit/experimental-hydrate-support.js'; +import 'lit/experimental-hydrate.js'; +import 'lit/static-html.js'; From 6d6ec28605aa58e7c35d842b13ae161d417192c3 Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Wed, 20 Jul 2022 12:08:51 -0700 Subject: [PATCH 06/10] Changeset --- .changeset/tall-mirrors-notice.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/tall-mirrors-notice.md diff --git a/.changeset/tall-mirrors-notice.md b/.changeset/tall-mirrors-notice.md new file mode 100644 index 0000000000..974d0cd818 --- /dev/null +++ b/.changeset/tall-mirrors-notice.md @@ -0,0 +1,7 @@ +--- +'lit': minor +'lit-html': minor +'@lit/reactive-element': minor +--- + +Lit and its underlying libraries can now be imported directly from Node without crashing, without the need to load the @lit-labs/ssr dom-shim library. Note that actually rendering from a Node context still requires the @lit-labs/ssr dom-shim, and the appropriate integration between @lit-labs/ssr and your framework/tool. From 925b489774c5c87d617eb8dfdc23d071a70f7957 Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Wed, 20 Jul 2022 17:43:34 -0700 Subject: [PATCH 07/10] Comment about globalThis vs window in IE11 --- packages/lit-html/src/lit-html.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/lit-html/src/lit-html.ts b/packages/lit-html/src/lit-html.ts index 26397feb9a..929e63edaa 100644 --- a/packages/lit-html/src/lit-html.ts +++ b/packages/lit-html/src/lit-html.ts @@ -11,6 +11,7 @@ const DEV_MODE = true; const ENABLE_EXTRA_SECURITY_HOOKS = true; const ENABLE_SHADYDOM_NOPATCH = true; const NODE_MODE = false; +// Use window for browser builds because IE11 doesn't have globalThis. const global = NODE_MODE ? globalThis : window; /** From 9b05f30bcd855d0cbcc0faeb59d7d16571292f90 Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Thu, 21 Jul 2022 13:30:01 -0700 Subject: [PATCH 08/10] Address PR comments --- packages/lit-element/src/test/node-imports.ts | 7 +++++++ packages/lit-html/rollup.config.js | 2 +- packages/reactive-element/rollup.config.js | 2 +- packages/reactive-element/src/reactive-element.ts | 5 +++-- packages/reactive-element/src/test/node-imports.ts | 3 +++ rollup-common.js | 4 ++-- 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/lit-element/src/test/node-imports.ts b/packages/lit-element/src/test/node-imports.ts index 3f04ac951e..59f35e60a0 100644 --- a/packages/lit-element/src/test/node-imports.ts +++ b/packages/lit-element/src/test/node-imports.ts @@ -30,3 +30,10 @@ export class MyElement extends LitElement { return html`Hello World`; } } + +export class MyOtherElement extends LitElement { + override render() { + return html`Hello World`; + } +} +customElements.define('my-other-element', MyOtherElement); diff --git a/packages/lit-html/rollup.config.js b/packages/lit-html/rollup.config.js index 46f8bd3b72..1efc527298 100644 --- a/packages/lit-html/rollup.config.js +++ b/packages/lit-html/rollup.config.js @@ -45,7 +45,7 @@ export const defaultConfig = (options = {}) => file: 'polyfill-support', }, ], - nodeBuild: true, + includeNodeBuild: true, ...options, }); diff --git a/packages/reactive-element/rollup.config.js b/packages/reactive-element/rollup.config.js index c1f2224685..5ca253a77c 100644 --- a/packages/reactive-element/rollup.config.js +++ b/packages/reactive-element/rollup.config.js @@ -31,5 +31,5 @@ export default litProdConfig({ file: 'polyfill-support', }, ], - nodeBuild: true, + includeNodeBuild: true, }); diff --git a/packages/reactive-element/src/reactive-element.ts b/packages/reactive-element/src/reactive-element.ts index 5ced1f4e75..66ce6efd6d 100644 --- a/packages/reactive-element/src/reactive-element.ts +++ b/packages/reactive-element/src/reactive-element.ts @@ -31,7 +31,6 @@ const NODE_MODE = false; const global = NODE_MODE ? globalThis : window; if (NODE_MODE) { - global.HTMLElement ??= class HTMLElement {} as unknown as typeof HTMLElement; global.customElements ??= { define() {}, } as unknown as CustomElementRegistry; @@ -400,7 +399,9 @@ export type Initializer = (element: ReactiveElement) => void; * @noInheritDoc */ export abstract class ReactiveElement - extends HTMLElement + extends (NODE_MODE + ? (class HTMLElement {} as unknown as typeof HTMLElement) + : HTMLElement) implements ReactiveControllerHost { // Note: these are patched in only in DEV_MODE. diff --git a/packages/reactive-element/src/test/node-imports.ts b/packages/reactive-element/src/test/node-imports.ts index 4a3df24398..9d91df2af2 100644 --- a/packages/reactive-element/src/test/node-imports.ts +++ b/packages/reactive-element/src/test/node-imports.ts @@ -27,3 +27,6 @@ import {ReactiveElement} from '@lit/reactive-element'; @customElement('my-element') export class MyElement extends ReactiveElement {} + +export class MyOtherElement extends ReactiveElement {} +customElements.define('my-other-element', MyOtherElement); diff --git a/rollup-common.js b/rollup-common.js index 1f09145c34..ebe4b40631 100644 --- a/rollup-common.js +++ b/rollup-common.js @@ -255,7 +255,7 @@ export function litProdConfig({ packageName, outputDir = './', copyHtmlTests = true, - nodeBuild = false, + includeNodeBuild = false, // eslint-disable-next-line no-undef } = options) { const classPropertyPrefix = PACKAGE_CLASS_PREFIXES[packageName]; @@ -426,7 +426,7 @@ export function litProdConfig({ ], }, // Node build - ...(nodeBuild + ...(includeNodeBuild ? [ { input: entryPoints.map((name) => `development/${name}.js`), From 266af906c98cea7e21be3347c794f3375eff9f8c Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Thu, 21 Jul 2022 15:58:35 -0700 Subject: [PATCH 09/10] Temporarily shim HTMLElement in a sneakier way --- packages/reactive-element/src/reactive-element.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/reactive-element/src/reactive-element.ts b/packages/reactive-element/src/reactive-element.ts index 66ce6efd6d..36aa54ab46 100644 --- a/packages/reactive-element/src/reactive-element.ts +++ b/packages/reactive-element/src/reactive-element.ts @@ -392,6 +392,11 @@ export type WarningKind = 'change-in-update' | 'migration'; export type Initializer = (element: ReactiveElement) => void; +const htmlElementShimNeeded = NODE_MODE && global.HTMLElement === undefined; +if (htmlElementShimNeeded) { + global.HTMLElement = class HTMLElement {} as unknown as typeof HTMLElement; +} + /** * Base element class which manages element properties and attributes. When * properties change, the `update` method is asynchronously called. This method @@ -399,9 +404,7 @@ export type Initializer = (element: ReactiveElement) => void; * @noInheritDoc */ export abstract class ReactiveElement - extends (NODE_MODE - ? (class HTMLElement {} as unknown as typeof HTMLElement) - : HTMLElement) + extends HTMLElement implements ReactiveControllerHost { // Note: these are patched in only in DEV_MODE. @@ -1495,6 +1498,10 @@ export abstract class ReactiveElement protected firstUpdated(_changedProperties: PropertyValues) {} } +if (htmlElementShimNeeded) { + delete (global as Partial).HTMLElement; +} + // Apply polyfills if available polyfillSupport?.({ReactiveElement}); From 19a8df35d0b268e117319885dd8fbbec8d73b3e9 Mon Sep 17 00:00:00 2001 From: Alexander Marks Date: Mon, 25 Jul 2022 15:09:21 -0700 Subject: [PATCH 10/10] Update version string replacer script --- scripts/update-version-variables.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/update-version-variables.js b/scripts/update-version-variables.js index 1b978cc7f3..5ee56dd9d1 100644 --- a/scripts/update-version-variables.js +++ b/scripts/update-version-variables.js @@ -24,12 +24,12 @@ const updateVersionVariable = async (packageDir, sourcePath, variableName) => { // Replace version number const versionVarRegex = new RegExp( - `\\(globalThis\\.${variableName} \\?\\?= \\[\\]\\)\\.push\\('\\d+\\.\\d+\\.\\d+'\\)` + `(\\(global(?:This)?\\.${variableName} \\?\\?= \\[\\]\\)\\.push\\(')\\d+\\.\\d+\\.\\d+[^']*('\\))` ); let replaced = false; - const newSource = fileSource.replace(versionVarRegex, () => { + const newSource = fileSource.replace(versionVarRegex, (_, pre, post) => { replaced = true; - return `(globalThis.${variableName} ??= []).push('${version}')`; + return pre + version + post; }); if (!replaced) { throw new Error(`Version variable not found: ${filePath} ${variableName}`);