From c64e720b4e96f89d7f8b8992b535baccf2a1185e Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Fri, 25 Oct 2024 15:00:50 -0400 Subject: [PATCH 1/2] ensure Lit hydration script loads after importmaps --- packages/plugin-renderer-lit/src/index.js | 41 +++- .../develop.default/develop.default.spec.js | 227 ++++++++++++++++++ .../cases/develop.default/greenwood.config.js | 7 + .../test/cases/develop.default/package.json | 7 + .../cases/develop.default/src/pages/index.js | 5 + .../cases/serve.default/serve.default.spec.js | 4 +- test/smoke-test.js | 6 + 7 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 packages/plugin-renderer-lit/test/cases/develop.default/develop.default.spec.js create mode 100644 packages/plugin-renderer-lit/test/cases/develop.default/greenwood.config.js create mode 100644 packages/plugin-renderer-lit/test/cases/develop.default/package.json create mode 100644 packages/plugin-renderer-lit/test/cases/develop.default/src/pages/index.js diff --git a/packages/plugin-renderer-lit/src/index.js b/packages/plugin-renderer-lit/src/index.js index ca77f421c..31406220c 100755 --- a/packages/plugin-renderer-lit/src/index.js +++ b/packages/plugin-renderer-lit/src/index.js @@ -14,16 +14,47 @@ class LitHydrationResource extends ResourceInterface { async intercept(url, request, response) { const { importMaps } = this.compilation.config.polyfills; - const importMapType = process.env.__GWD_COMMAND__ === 'develop' && importMaps // eslint-disable-line no-underscore-dangle + const isDevelopment = process.env.__GWD_COMMAND__ === 'develop'; // eslint-disable-line no-underscore-dangle + const importType = isDevelopment && importMaps ? 'module-shim' : 'module'; + const importMapType = isDevelopment && importMaps + ? 'importmap-shim' + : 'importmap'; + const headSelector = isDevelopment ? ` - `); + // but before any import maps + if (isDevelopment) { + // quick way to find the ending position of the importmap + + ${body.slice(importMapEndPos + 9)} + `; + } else { + body = body.replace(headSelector, ` + ${headSelector} + + `); + } return new Response(body); } diff --git a/packages/plugin-renderer-lit/test/cases/develop.default/develop.default.spec.js b/packages/plugin-renderer-lit/test/cases/develop.default/develop.default.spec.js new file mode 100644 index 000000000..4bf4aff3b --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/develop.default/develop.default.spec.js @@ -0,0 +1,227 @@ +/* + * Use Case + * Run Greenwood for development with lit renderer plugin. + * + * User Result + * Should serve a bare bones Greenwood build for developing with Lit+SSR. + * + * User Command + * greenwood develop + * + * User Config + * import { greenwoodPluginRendererLit } from '@greenwood/plugin-renderer-lit'; + * + * { + * plugins: [{ + * greenwoodPluginRendererLit() + * }] + * } + * + * User Workspace + * src/ + * pages/ + * index.js + */ +import chai from 'chai'; +import { JSDOM } from 'jsdom'; +import path from 'path'; +import { getSetupFiles, getDependencyFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { Runner } from 'gallinago'; +import { fileURLToPath, URL } from 'url'; + +const expect = chai.expect; + +describe('Develop Greenwood With: ', function() { + const LABEL = 'Lit Renderer'; + const cliPath = path.join(process.cwd(), 'packages/cli/src/index.js'); + const outputPath = fileURLToPath(new URL('.', import.meta.url)); + const hostname = 'http://127.0.0.1:1984'; + let runner; + + before(function() { + this.context = { + publicDir: path.join(outputPath, 'public') + }; + runner = new Runner(); + }); + + describe(LABEL, function() { + + before(async function() { + const lit = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/*.js`, + `${outputPath}/node_modules/lit/` + ); + const litDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/decorators/*.js`, + `${outputPath}/node_modules/lit/decorators/` + ); + const litDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/directives/*.js`, + `${outputPath}/node_modules/lit/directives/` + ); + const litPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit/package.json`, + `${outputPath}/node_modules/lit/` + ); + const litElement = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/*.js`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/package.json`, + `${outputPath}/node_modules/lit-element/` + ); + const litElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-element/decorators/*.js`, + `${outputPath}/node_modules/lit-element/decorators/` + ); + const litHtml = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/*.js`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlNode = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/node/*.js`, + `${outputPath}/node_modules/lit-html/node/` + ); + const litHtmlNodeDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/node/directives/*.js`, + `${outputPath}/node_modules/lit-html/node/directives/` + ); + const litHtmlPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/package.json`, + `${outputPath}/node_modules/lit-html/` + ); + const litHtmlDirectives = await getDependencyFiles( + `${process.cwd()}/node_modules/lit-html/directives/*.js`, + `${outputPath}/node_modules/lit-html/directives/` + ); + // lit-html has a dependency on this + // https://github.com/lit/lit/blob/main/packages/lit-html/package.json#L82 + const trustedTypes = await getDependencyFiles( + `${process.cwd()}/node_modules/@types/trusted-types/package.json`, + `${outputPath}/node_modules/@types/trusted-types/` + ); + const litReactiveElement = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); + const litReactiveElementNode = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/node/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/node/` + ); + const litReactiveElementDecorators = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/decorators/*.js`, + `${outputPath}/node_modules/@lit/reactive-element/decorators/` + ); + const litReactiveElementPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit/reactive-element/package.json`, + `${outputPath}/node_modules/@lit/reactive-element/` + ); + const litSsrElementHydrationSupport = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-client/lit-element-hydrate-support.js`, + `${outputPath}/node_modules/@lit-labs/ssr-client/` + ); + const litSsrHtmlHydrationSupport = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-client/lib/*.js`, + `${outputPath}/node_modules/@lit-labs/ssr-client/lib/` + ); + const litSsrDomShimPackageJson = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/package.json`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); + const litSsrDomShim = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/*.js`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/` + ); + const litSsrDomShimLibs = await getDependencyFiles( + `${process.cwd()}/node_modules/@lit-labs/ssr-dom-shim/lib/*.js`, + `${outputPath}/node_modules/@lit-labs/ssr-dom-shim/lib/` + ); + + runner.setup(outputPath, [ + ...getSetupFiles(outputPath), + ...lit, + ...litPackageJson, + ...litDirectives, + ...litDecorators, + ...litElementPackageJson, + ...litElement, + ...litElementDecorators, + ...litHtmlPackageJson, + ...litHtml, + ...litHtmlNode, + ...litHtmlDirectives, + ...litHtmlNodeDirectives, + ...trustedTypes, + ...litReactiveElement, + ...litReactiveElementNode, + ...litReactiveElementDecorators, + ...litReactiveElementPackageJson, + ...litSsrElementHydrationSupport, + ...litSsrHtmlHydrationSupport, + ...litSsrDomShim, + ...litSsrDomShimPackageJson, + ...litSsrDomShimLibs + ]); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, 10000); + + runner.runCommand(cliPath, 'develop', { async: true }); + }); + }); + + describe('Develop command with expected HTML for the / route', function() { + let response = {}; + let dom; + let body; + + before(async function() { + response = await fetch(`${hostname}/`); + body = await response.text(); + dom = new JSDOM(body); + }); + + it('should return a 200 status', function(done) { + expect(response.status).to.equal(200); + done(); + }); + + it('should return the correct content type', function(done) { + expect(response.headers.get('content-type')).to.equal('text/html'); + done(); + }); + + it('should return a response body', function(done) { + expect(body).to.not.be.undefined; + done(); + }); + + it('the response body should be valid HTML from JSDOM', function(done) { + expect(dom).to.not.be.undefined; + done(); + }); + + it('should have the expected lit hydration script in the ', function() { + const scripts = Array.from(dom.window.document.querySelectorAll('script[src*="lit-element-hydrate-support"]')); + + expect(scripts.length).to.equal(1); + }); + + it('should have the expected lit hydration script _after_ any importmaps in the ', function() { + // make sure this does NOT come before an importmap + const scripts = Array.from(dom.window.document.querySelectorAll('script[src*="lit-element-hydrate-support"] + script[type="importmap"')); + + expect(scripts.length).to.equal(0); + }); + }); + }); + + after(function() { + runner.teardown(getOutputTeardownFiles(outputPath)); + runner.stopCommand(); + }); +}); \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/develop.default/greenwood.config.js b/packages/plugin-renderer-lit/test/cases/develop.default/greenwood.config.js new file mode 100644 index 000000000..24dafff94 --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/develop.default/greenwood.config.js @@ -0,0 +1,7 @@ +import { greenwoodPluginRendererLit } from '../../../src/index.js'; + +export default { + plugins: [ + greenwoodPluginRendererLit() + ] +}; \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/develop.default/package.json b/packages/plugin-renderer-lit/test/cases/develop.default/package.json new file mode 100644 index 000000000..44ff116c6 --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/develop.default/package.json @@ -0,0 +1,7 @@ +{ + "name": "plugin-renderer-lit-develop-default", + "type": "module", + "dependencies": { + "lit": "^3.1.0" + } +} \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/develop.default/src/pages/index.js b/packages/plugin-renderer-lit/test/cases/develop.default/src/pages/index.js new file mode 100644 index 000000000..59cdb613c --- /dev/null +++ b/packages/plugin-renderer-lit/test/cases/develop.default/src/pages/index.js @@ -0,0 +1,5 @@ +import { html } from 'lit'; + +export async function getBody() { + return html`

Home Page

`; +} \ No newline at end of file diff --git a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js index d90ffc1cf..b5143b658 100644 --- a/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js +++ b/packages/plugin-renderer-lit/test/cases/serve.default/serve.default.spec.js @@ -13,9 +13,7 @@ * * { * plugins: [{ - * greenwoodPluginRendererLit({ - * prerender: true - * }) + * greenwoodPluginRendererLit() * }] * } * diff --git a/test/smoke-test.js b/test/smoke-test.js index 2474d770a..4c886c7f4 100644 --- a/test/smoke-test.js +++ b/test/smoke-test.js @@ -106,6 +106,12 @@ function commonIndexSpecs(dom, html, label) { it('should not have any optimization markers left in the HTML', function() { expect(html.match(/data-gwd-opt=".*[a-z]"/)).to.be.equal(null); }); + + it('should not have any module based