feat: add automatic customElements.define/@customElement detection for lit
This commit is contained in:
parent
2efee75989
commit
5d96f98564
4 changed files with 118 additions and 1 deletions
6
.changeset/gorgeous-parents-build.md
Normal file
6
.changeset/gorgeous-parents-build.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'@astrojs/renderer-lit': minor
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Add support for automatically detecting @customElement("my-element")`and`customElements.define("my-element", MyElement)` when using Lit SSR.
|
|
@ -41,7 +41,7 @@ class Metadata {
|
||||||
const id = specifier.startsWith('.') ? new URL(specifier, this.fileURL).pathname : specifier;
|
const id = specifier.startsWith('.') ? new URL(specifier, this.fileURL).pathname : specifier;
|
||||||
for (const [key, value] of Object.entries(module)) {
|
for (const [key, value] of Object.entries(module)) {
|
||||||
if (isCustomElement) {
|
if (isCustomElement) {
|
||||||
if (key === 'tagName' && Component === value) {
|
if ((key === 'tagName' || key === '__astroTagName') && Component === value) {
|
||||||
return {
|
return {
|
||||||
componentExport: key,
|
componentExport: key,
|
||||||
componentUrl: id,
|
componentUrl: id,
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import pluginLit from './vite-plugin-lit.js';
|
||||||
|
|
||||||
// NOTE: @lit-labs/ssr uses syntax incompatible with anything < Node v13.9.0.
|
// NOTE: @lit-labs/ssr uses syntax incompatible with anything < Node v13.9.0.
|
||||||
// Throw an error if using that Node version.
|
// Throw an error if using that Node version.
|
||||||
|
|
||||||
|
@ -13,6 +15,9 @@ export default {
|
||||||
hydrationPolyfills: ['./hydration-support.js'],
|
hydrationPolyfills: ['./hydration-support.js'],
|
||||||
viteConfig() {
|
viteConfig() {
|
||||||
return {
|
return {
|
||||||
|
plugins: [
|
||||||
|
pluginLit(),
|
||||||
|
],
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: [
|
include: [
|
||||||
'@astrojs/renderer-lit/client-shim.js',
|
'@astrojs/renderer-lit/client-shim.js',
|
||||||
|
|
106
packages/renderers/renderer-lit/vite-plugin-lit.js
Normal file
106
packages/renderers/renderer-lit/vite-plugin-lit.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import { parse } from 'acorn';
|
||||||
|
import { walk } from 'estree-walker';
|
||||||
|
import MagicString from 'magic-string';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if Vite is in SSR mode based on options
|
||||||
|
* https://github.com/vitejs/vite/discussions/5109#discussioncomment-1450726
|
||||||
|
*
|
||||||
|
* @param options boolean | { ssr: boolean }
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
function isSSR(options) {
|
||||||
|
if (options === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof options === 'boolean') {
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
if (typeof options == 'object') {
|
||||||
|
return !!options.ssr;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This matches any JS-like file (that we know of)
|
||||||
|
// See https://regex101.com/r/Cgofir/1
|
||||||
|
const SUPPORTED_FILES = /\.([cm]?js|jsx|[cm]?ts|tsx)$/;
|
||||||
|
const IGNORED_MODULES = [/astro\/dist\/runtime\/server/, /\/renderer-lit\/server/, /\/@lit\//];
|
||||||
|
|
||||||
|
function scanForTagName(code) {
|
||||||
|
const ast = parse(code, {
|
||||||
|
sourceType: 'module'
|
||||||
|
})
|
||||||
|
|
||||||
|
let tagName;
|
||||||
|
walk(ast, {
|
||||||
|
enter(node, parent) {
|
||||||
|
if (tagName) {
|
||||||
|
return this.skip();
|
||||||
|
}
|
||||||
|
// Matches `customElement("my-component")`, which is Lit's @customElement("my-component") decorator
|
||||||
|
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 'customElement') {
|
||||||
|
const arg = node.arguments[0];
|
||||||
|
if (arg.type === 'Literal') {
|
||||||
|
tagName = arg.raw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Matches `customElements.define("my-component", thing)`
|
||||||
|
if (node.type === 'MemberExpression' && node.object.name === 'customElements' && node.property.name === 'define') {
|
||||||
|
const arg = parent.arguments[0];
|
||||||
|
if (arg.type === 'Literal') {
|
||||||
|
tagName = arg.raw
|
||||||
|
} else {
|
||||||
|
tagName = arg.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return tagName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {import('vite').Plugin}
|
||||||
|
*/
|
||||||
|
export default function pluginLit() {
|
||||||
|
return {
|
||||||
|
name: '@astrojs/vite-plugin-lit',
|
||||||
|
enforce: 'post',
|
||||||
|
async transform(code, id, opts) {
|
||||||
|
const ssr = isSSR(opts);
|
||||||
|
// If this isn't an SSR pass, `fetch` will already be available!
|
||||||
|
if (!ssr) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Only transform JS-like files
|
||||||
|
if (!id.match(SUPPORTED_FILES)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Optimization: only run on probable matches
|
||||||
|
if (!code.includes('customElement') && !code.includes('lit')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Ignore specific modules
|
||||||
|
for (const ignored of IGNORED_MODULES) {
|
||||||
|
if (id.match(ignored)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tagName = scanForTagName(code);
|
||||||
|
if (!tagName) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = new MagicString(code);
|
||||||
|
s.append(`export const __astroTagName = ${tagName};`);
|
||||||
|
const result = s.toString();
|
||||||
|
const map = s.generateMap({
|
||||||
|
source: id,
|
||||||
|
includeContent: true,
|
||||||
|
});
|
||||||
|
return { code: result, map };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue