From c3cf8fab799c6ead79abb14cff27aaa0393d70db Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sun, 29 May 2022 14:38:14 -0400 Subject: [PATCH 01/15] initial working local development example with wc-compiler for SSR native HTMLElement --- packages/cli/package.json | 3 +- packages/cli/src/lib/ssr-route-worker.js | 26 +++++++++----- www/components/card/card.native.js | 43 ++++++++++++++++++++++++ www/pages/artists.js | 33 ++++++++++++++++++ yarn.lock | 19 +++++++++++ 5 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 www/components/card/card.native.js create mode 100644 www/pages/artists.js diff --git a/packages/cli/package.json b/packages/cli/package.json index acaafa2c4..ad2c35d55 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,7 +55,8 @@ "remark-rehype": "^7.0.0", "rollup": "^2.58.0", "rollup-plugin-terser": "^7.0.0", - "unified": "^9.2.0" + "unified": "^9.2.0", + "wc-compiler": "^0.2.1" }, "devDependencies": { "@babel/runtime": "^7.10.4", diff --git a/packages/cli/src/lib/ssr-route-worker.js b/packages/cli/src/lib/ssr-route-worker.js index 9f859575b..0a7af699e 100644 --- a/packages/cli/src/lib/ssr-route-worker.js +++ b/packages/cli/src/lib/ssr-route-worker.js @@ -1,9 +1,11 @@ // https://github.com/nodejs/modules/issues/307#issuecomment-858729422 import { pathToFileURL } from 'url'; import { workerData, parentPort } from 'worker_threads'; +import { renderToString } from 'wc-compiler'; async function executeRouteModule({ modulePath, compilation, route, label, id }) { - const { getTemplate = null, getBody = null, getFrontmatter = null } = await import(pathToFileURL(modulePath)).then(module => module); + const module = await import(pathToFileURL(modulePath)).then(module => module); + const { getFrontmatter = null, getBody = null, getTemplate = null } = module; const parsedCompilation = JSON.parse(compilation); const data = { template: null, @@ -11,16 +13,24 @@ async function executeRouteModule({ modulePath, compilation, route, label, id }) frontmatter: null }; - if (getTemplate) { - data.template = await getTemplate(parsedCompilation, route); + if (getFrontmatter) { + data.frontmatter = await getFrontmatter(parsedCompilation, route, label, id); } - if (getBody) { - data.body = await getBody(parsedCompilation, route); - } + if (module.default) { + console.debug('exporting a native custom element', modulePath); + const { html, metadata } = await renderToString(new URL(modulePath, import.meta.url)); + console.debug({ metadata }); - if (getFrontmatter) { - data.frontmatter = await getFrontmatter(parsedCompilation, route, label, id); + data.body = html; + } else { + if (getTemplate) { + data.template = await getTemplate(parsedCompilation, route); + } + + if (getBody) { + data.body = await getBody(parsedCompilation, route); + } } parentPort.postMessage(data); diff --git a/www/components/card/card.native.js b/www/components/card/card.native.js new file mode 100644 index 000000000..7a23830df --- /dev/null +++ b/www/components/card/card.native.js @@ -0,0 +1,43 @@ +const template = document.createElement('template'); + +template.innerHTML = ` + + +
+ My default title + +
+
+`; + +class Card extends HTMLElement { + connectedCallback() { + if (!this.shadowRoot) { + this.attachShadow({ mode: 'open' }); + this.shadowRoot.appendChild(template.content.cloneNode(true)); + } + } +} + +export default Card; + +customElements.define('wc-card', Card); \ No newline at end of file diff --git a/www/pages/artists.js b/www/pages/artists.js new file mode 100644 index 000000000..bd89e273d --- /dev/null +++ b/www/pages/artists.js @@ -0,0 +1,33 @@ +import '../components/card/card.native.js'; +import fetch from 'node-fetch'; + +export default class ArtistsPage extends HTMLElement { + async connectedCallback() { + if (!this.shadowRoot) { + const artists = await fetch('https://www.analogstudios.net/api/artists').then(resp => resp.json()); + const html = artists.map(artist => { + return ` + +

${artist.name}

+ ${artist.name} +
+ `; + }).join(''); + + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = html; + } + } +} + +async function getFrontmatter(compilation, route) { + console.debug({ route }); + return { + menu: 'navigation', + index: 7 + }; +} + +export { + getFrontmatter +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 982adbee0..bc1b694ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2955,6 +2955,11 @@ acorn-walk@^8.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.0.0.tgz#56ae4c0f434a45fff4a125e7ea95fa9c98f67a16" integrity sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA== +acorn-walk@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^6.0.1, acorn@^6.0.4: version "6.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474" @@ -2970,6 +2975,11 @@ acorn@^8.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.1.tgz#d7e8eca9b71d5840db0e7e415b3b2b20e250f938" integrity sha512-dmKn4pqZ29iQl2Pvze1zTrps2luvls2PBY//neO2WJ0s10B3AxJXshN+Ph7B4GrhfGhHXrl4dnUwyNNXQcnWGQ== +acorn@^8.7.0: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + agent-base@4, agent-base@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" @@ -12208,6 +12218,15 @@ w3c-xmlserializer@^1.1.2: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" +wc-compiler@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.2.1.tgz#65ab955f80af3f880938b76dda59a2ef98b5ab49" + integrity sha512-s5tLEzFbVYyad0LG4lzzR65iSr6fcTgs53wvGyllsFVt68K4qVMGOANj+LgsCjuMz/JrxkqAiql+YAl8+vV5Ow== + dependencies: + acorn "^8.7.0" + acorn-walk "^8.2.0" + parse5 "^6.0.1" + wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" From 39bcf536de686e868edda50625ccfbe6823b5898 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 4 Jun 2022 13:46:48 -0400 Subject: [PATCH 02/15] restore getTemplate to original location in SSR route worker --- packages/cli/src/lib/ssr-route-worker.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/ssr-route-worker.js b/packages/cli/src/lib/ssr-route-worker.js index 0a7af699e..0fcf2afe9 100644 --- a/packages/cli/src/lib/ssr-route-worker.js +++ b/packages/cli/src/lib/ssr-route-worker.js @@ -17,6 +17,10 @@ async function executeRouteModule({ modulePath, compilation, route, label, id }) data.frontmatter = await getFrontmatter(parsedCompilation, route, label, id); } + if (getTemplate) { + data.template = await getTemplate(parsedCompilation, route); + } + if (module.default) { console.debug('exporting a native custom element', modulePath); const { html, metadata } = await renderToString(new URL(modulePath, import.meta.url)); @@ -24,10 +28,6 @@ async function executeRouteModule({ modulePath, compilation, route, label, id }) data.body = html; } else { - if (getTemplate) { - data.template = await getTemplate(parsedCompilation, route); - } - if (getBody) { data.body = await getBody(parsedCompilation, route); } From 5895c824c25242eef2a7f6a94dbf8cb1745f9470 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Fri, 10 Jun 2022 09:03:08 -0400 Subject: [PATCH 03/15] bump latest version of wcc --- packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ad2c35d55..f23845c58 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,7 +56,7 @@ "rollup": "^2.58.0", "rollup-plugin-terser": "^7.0.0", "unified": "^9.2.0", - "wc-compiler": "^0.2.1" + "wc-compiler": "~0.3.0" }, "devDependencies": { "@babel/runtime": "^7.10.4", diff --git a/yarn.lock b/yarn.lock index bc1b694ca..c48625092 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12218,10 +12218,10 @@ w3c-xmlserializer@^1.1.2: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" -wc-compiler@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.2.1.tgz#65ab955f80af3f880938b76dda59a2ef98b5ab49" - integrity sha512-s5tLEzFbVYyad0LG4lzzR65iSr6fcTgs53wvGyllsFVt68K4qVMGOANj+LgsCjuMz/JrxkqAiql+YAl8+vV5Ow== +wc-compiler@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.3.0.tgz#e0882e95a1062d12b6a4639ff4b078cb899f83ee" + integrity sha512-mIERPgfFyM+LJsrh9FhmX55H5cuqMH1jKgQ3Ki415ufGQxEF6xNANXm7PrwZD4+YD6GE+KX1dOeeFxR7RYGolg== dependencies: acorn "^8.7.0" acorn-walk "^8.2.0" From b2d35f275994c1d9ed3d8a1c17b358e864b1e72f Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Tue, 14 Jun 2022 19:06:08 -0400 Subject: [PATCH 04/15] ensure passing of URLs to wcc --- packages/cli/src/lib/ssr-route-worker.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/lib/ssr-route-worker.js b/packages/cli/src/lib/ssr-route-worker.js index 0fcf2afe9..3bab71c46 100644 --- a/packages/cli/src/lib/ssr-route-worker.js +++ b/packages/cli/src/lib/ssr-route-worker.js @@ -4,7 +4,8 @@ import { workerData, parentPort } from 'worker_threads'; import { renderToString } from 'wc-compiler'; async function executeRouteModule({ modulePath, compilation, route, label, id }) { - const module = await import(pathToFileURL(modulePath)).then(module => module); + const moduleURL = pathToFileURL(modulePath); + const module = await import(moduleURL).then(module => module); const { getFrontmatter = null, getBody = null, getTemplate = null } = module; const parsedCompilation = JSON.parse(compilation); const data = { @@ -22,8 +23,7 @@ async function executeRouteModule({ modulePath, compilation, route, label, id }) } if (module.default) { - console.debug('exporting a native custom element', modulePath); - const { html, metadata } = await renderToString(new URL(modulePath, import.meta.url)); + const { html, metadata } = await renderToString(moduleURL); console.debug({ metadata }); data.body = html; From 95d4fb6242e2ceaed50fc8f39270a2543461a093 Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 18 Jun 2022 11:54:54 -0400 Subject: [PATCH 05/15] upgrade WCC --- packages/cli/package.json | 2 +- www/components/card/card.native.js | 2 +- www/pages/artists.js | 6 +++++- yarn.lock | 8 ++++---- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index f23845c58..216057b34 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -56,7 +56,7 @@ "rollup": "^2.58.0", "rollup-plugin-terser": "^7.0.0", "unified": "^9.2.0", - "wc-compiler": "~0.3.0" + "wc-compiler": "~0.3.1" }, "devDependencies": { "@babel/runtime": "^7.10.4", diff --git a/www/components/card/card.native.js b/www/components/card/card.native.js index 7a23830df..21456531a 100644 --- a/www/components/card/card.native.js +++ b/www/components/card/card.native.js @@ -10,7 +10,7 @@ template.innerHTML = ` } [name="title"] { - color: green; + color: red; } ::slotted(img) { diff --git a/www/pages/artists.js b/www/pages/artists.js index bd89e273d..b0a5622f9 100644 --- a/www/pages/artists.js +++ b/www/pages/artists.js @@ -15,7 +15,11 @@ export default class ArtistsPage extends HTMLElement { }).join(''); this.attachShadow({ mode: 'open' }); - this.shadowRoot.innerHTML = html; + this.shadowRoot.innerHTML = ` + + `; } } } diff --git a/yarn.lock b/yarn.lock index c48625092..f119a21f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12218,10 +12218,10 @@ w3c-xmlserializer@^1.1.2: webidl-conversions "^4.0.2" xml-name-validator "^3.0.0" -wc-compiler@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.3.0.tgz#e0882e95a1062d12b6a4639ff4b078cb899f83ee" - integrity sha512-mIERPgfFyM+LJsrh9FhmX55H5cuqMH1jKgQ3Ki415ufGQxEF6xNANXm7PrwZD4+YD6GE+KX1dOeeFxR7RYGolg== +wc-compiler@~0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/wc-compiler/-/wc-compiler-0.3.1.tgz#fedd4249947092ef8d30c41156911a2891c7cef4" + integrity sha512-hY72m2EX6WW9k8+kXzrOCxyJetaTRKmGODnW3Ftl2u1d5pOw6mXSZkBI6/5SEw07jbQK/CLs/Tem/SF2PBO0mQ== dependencies: acorn "^8.7.0" acorn-walk "^8.2.0" From 598e76d4656e698d8c52bd9f9a8263aa2383f26c Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 18 Jun 2022 14:38:56 -0400 Subject: [PATCH 06/15] add support for prerendering SSR routes with default renderer WCC in favor of puppeteer --- packages/cli/src/commands/build.js | 12 +- packages/cli/src/lifecycles/bundle.js | 2 +- packages/cli/src/lifecycles/prerender.js | 29 ++++ ...r-string.js => plugin-renderer-default.js} | 6 +- .../build.default.ssr.spec.js | 126 ++++++------------ .../test/cases/build.default.ssr/package.json | 6 - .../build.default.ssr/src/components/card.js | 43 ++++++ .../src/components/footer.js | 49 ------- .../build.default.ssr/src/pages/users.js | 33 +++++ .../build.default.ssr/src/templates/app.html | 2 - 10 files changed, 161 insertions(+), 147 deletions(-) rename packages/cli/src/plugins/renderer/{plugin-renderer-string.js => plugin-renderer-default.js} (54%) delete mode 100644 packages/cli/test/cases/build.default.ssr/package.json create mode 100644 packages/cli/test/cases/build.default.ssr/src/components/card.js delete mode 100644 packages/cli/test/cases/build.default.ssr/src/components/footer.js create mode 100644 packages/cli/test/cases/build.default.ssr/src/pages/users.js diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index 7181ffe96..df110a5bf 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -2,7 +2,7 @@ import { bundleCompilation } from '../lifecycles/bundle.js'; import { copyAssets } from '../lifecycles/copy.js'; import { getDevServer } from '../lifecycles/serve.js'; import fs from 'fs'; -import { preRenderCompilationCustom, preRenderCompilationDefault, staticRenderCompilation } from '../lifecycles/prerender.js'; +import { preRenderCompilationCustom, preRenderCompilationDefault, preRenderCompilationPuppeteer, staticRenderCompilation } from '../lifecycles/prerender.js'; import { ServerInterface } from '../lib/server-interface.js'; const runProductionBuild = async (compilation) => { @@ -16,15 +16,21 @@ const runProductionBuild = async (compilation) => { const customPrerender = (compilation.config.plugins.filter(plugin => plugin.type === 'renderer' && !plugin.isGreenwoodDefaultPlugin) || []).length === 1 ? compilation.config.plugins.filter(plugin => plugin.type === 'renderer')[0].provider(compilation) : {}; + const hasServerExports = compilation.graph.filter(page => page.isSSR && page.data.prerender).length > 0; if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir); } - if (prerender || customPrerender.prerender) { + if (prerender || customPrerender.prerender || hasServerExports) { if (customPrerender.prerender) { await preRenderCompilationCustom(compilation, customPrerender); } else { + console.debug('default prerender'); + if (hasServerExports) { + await preRenderCompilationDefault(compilation); + } + await new Promise(async (resolve, reject) => { try { (await getDevServer(compilation)).listen(port, async () => { @@ -48,7 +54,7 @@ const runProductionBuild = async (compilation) => { return Promise.resolve(server); })); - await preRenderCompilationDefault(compilation); + await preRenderCompilationPuppeteer(compilation); resolve(); }); diff --git a/packages/cli/src/lifecycles/bundle.js b/packages/cli/src/lifecycles/bundle.js index 3326d9df2..36a321054 100644 --- a/packages/cli/src/lifecycles/bundle.js +++ b/packages/cli/src/lifecycles/bundle.js @@ -5,7 +5,7 @@ const bundleCompilation = async (compilation) => { return new Promise(async (resolve, reject) => { try { - compilation.graph = compilation.graph.filter(page => !page.isSSR); + compilation.graph = compilation.graph.filter(page => !page.isSSR || page.isSSR && page.data.prerender); // https://rollupjs.org/guide/en/#differences-to-the-javascript-api if (compilation.graph.length > 0) { diff --git a/packages/cli/src/lifecycles/prerender.js b/packages/cli/src/lifecycles/prerender.js index ea19dbbd7..70fb9ccb2 100644 --- a/packages/cli/src/lifecycles/prerender.js +++ b/packages/cli/src/lifecycles/prerender.js @@ -124,6 +124,34 @@ async function preRenderCompilationCustom(compilation, customPrerender) { } async function preRenderCompilationDefault(compilation) { + const pages = compilation.graph.filter(page => page.isSSR && page.data.prerender); + const outputDir = compilation.context.scratchDir; + + for (const page of pages) { + const { outputPath, route } = page; + const outputPathDir = path.join(outputDir, route); + const htmlResource = compilation.config.plugins.filter((plugin) => { + return plugin.name === 'plugin-standard-html'; + }).map((plugin) => { + return plugin.provider(compilation); + })[0]; + let html; + + html = (await htmlResource.serve(page.route)).body; + html = (await interceptPage(compilation, html, route)).body; + html = await optimizePage(compilation, html, route, outputPath, outputDir); + + if (!fs.existsSync(outputPathDir)) { + fs.mkdirSync(outputPathDir, { + recursive: true + }); + } + + await fs.promises.writeFile(path.join(outputDir, outputPath), html); + } +} + +async function preRenderCompilationPuppeteer(compilation) { const BrowserRunner = (await import('../lib/browser.js')).BrowserRunner; const browserRunner = new BrowserRunner(); @@ -220,5 +248,6 @@ async function staticRenderCompilation(compilation) { export { preRenderCompilationCustom, preRenderCompilationDefault, + preRenderCompilationPuppeteer, staticRenderCompilation }; \ No newline at end of file diff --git a/packages/cli/src/plugins/renderer/plugin-renderer-string.js b/packages/cli/src/plugins/renderer/plugin-renderer-default.js similarity index 54% rename from packages/cli/src/plugins/renderer/plugin-renderer-string.js rename to packages/cli/src/plugins/renderer/plugin-renderer-default.js index df120d32a..16203beb9 100644 --- a/packages/cli/src/plugins/renderer/plugin-renderer-string.js +++ b/packages/cli/src/plugins/renderer/plugin-renderer-default.js @@ -1,6 +1,6 @@ -const greenwoodPluginRendererString = { +const greenwoodPluginRendererDefault = { type: 'renderer', - name: 'plugin-renderer-string', + name: 'plugin-renderer-default', provider: () => { return { workerUrl: new URL('../../lib/ssr-route-worker.js', import.meta.url) @@ -8,4 +8,4 @@ const greenwoodPluginRendererString = { } }; -export { greenwoodPluginRendererString }; \ No newline at end of file +export { greenwoodPluginRendererDefault }; \ No newline at end of file diff --git a/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js b/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js index c8adae81a..599f62764 100644 --- a/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js +++ b/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js @@ -24,7 +24,7 @@ import chai from 'chai'; import fs from 'fs'; import { JSDOM } from 'jsdom'; import path from 'path'; -import { getSetupFiles, getDependencyFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; +import { getSetupFiles, getOutputTeardownFiles } from '../../../../../test/utils.js'; import request from 'request'; import { runSmokeTest } from '../../../../../test/smoke-test.js'; import { Runner } from 'gallinago'; @@ -43,88 +43,13 @@ describe('Build Greenwood With: ', function() { this.context = { publicDir: path.join(outputPath, 'public') }; - runner = new Runner(); + runner = new Runner(true); }); 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 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 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/` - ); - - await runner.setup(outputPath, [ - ...getSetupFiles(outputPath), - ...lit, - ...litPackageJson, - ...litDirectives, - ...litDecorators, - ...litElementPackageJson, - ...litElement, - ...litElementDecorators, - ...litHtmlPackageJson, - ...litHtml, - ...litHtmlDirectives, - ...trustedTypes, - ...litReactiveElement, - ...litReactiveElementDecorators, - ...litReactiveElementPackageJson - ]); + await runner.setup(outputPath, getSetupFiles(outputPath)); return new Promise(async (resolve) => { setTimeout(() => { @@ -139,11 +64,13 @@ describe('Build Greenwood With: ', function() { let response = {}; let dom; + let usersPageDom; let artistsPageGraphData; before(async function() { const graph = JSON.parse(await fs.promises.readFile(path.join(outputPath, 'public/graph.json'), 'utf-8')); - + const usersPageHtml = await fs.promises.readFile(path.join(outputPath, 'public/users/index.html')); + artistsPageGraphData = graph.filter(page => page.route === '/artists/')[0]; return new Promise((resolve, reject) => { @@ -155,13 +82,14 @@ describe('Build Greenwood With: ', function() { response = res; response.body = body; dom = new JSDOM(body); + usersPageDom = new JSDOM(usersPageHtml); resolve(); }); }); }); - describe('Serve command with HTML route response', function() { + describe('Serve command with HTML route response for page using "get" functions', function() { it('should return a 200 status', function(done) { expect(response.statusCode).to.equal(200); @@ -189,10 +117,10 @@ describe('Build Greenwood With: ', function() { expect(styles.length).to.equal(1); }); - it('should have three script tags', function() { + it('should have the expected number of - \ No newline at end of file From d386a2ea9377152f9e887959ca47a3afed74defe Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 18 Jun 2022 15:20:09 -0400 Subject: [PATCH 07/15] document default export --- www/pages/docs/server-rendering.md | 34 +++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/www/pages/docs/server-rendering.md b/www/pages/docs/server-rendering.md index 16d4a2513..16ecf225a 100644 --- a/www/pages/docs/server-rendering.md +++ b/www/pages/docs/server-rendering.md @@ -27,7 +27,8 @@ The above would serve content in a browser at `/users/`. ### API -In your _[page].js_ file, Greenwood supports three functions you can `export` for providing server rendered configuration and content: +In your _[page].js_ file, Greenwood supports the following functions you can `export` for providing server rendered configuration and content: +- `default`: Use a custom element to render your page content. Will take precedence over `getBody`. - `getFrontmatter`: Static [frontmatter](/docs/front-matter/), useful in conjunction with [menus](/docs/menus/) or otherwise static configuration / meta data. - `getBody`: Effectively anything that you could put into a [``](/docs/layouts/#page-templates). - `getTemplate`: Effectively the same as a [page template](/docs/layouts/#page-templates). @@ -45,6 +46,11 @@ async function getTemplate(compilation, route) { return `/* some HTML here */`; } +export default MyComponent extends HTMLElement { + constructor() { } + connectedCallback() { } +} + export { getFrontmatter, getBody, @@ -52,6 +58,31 @@ export { } ``` +#### Custom Element (default) + +When using `export default`, Greenwood supports providing a custom element as the export for your page content. It uses [**WCC**](https://github.com/ProjectEvergreen/wcc) by default which also includes support for rendering [Declarative Shadow DOM](https://web.dev/declarative-shadow-dom/). + +```js +import '../components/card/card.js'; // +import fetch from 'node-fetch'; + +export default class UsersPage extends HTMLElement { + async connectedCallback() { + const users = await fetch('https://www.example.com/api/users').then(resp => resp.json()); + const html = users.map(user => { + return ` + +

${user.name}

+ ${user.name} +
+ `; + }).join(''); + + this.innerHTML = html; + } +} +``` + #### Frontmatter Any Greenwood supported frontmatter can be returned here. _This is only run once when the server is started_ to populate the graph, which is helpful if you want your dynamic route to show up in a menu like in your header for navigation. @@ -154,6 +185,7 @@ async function getTemplate(compilation, route) { One of the great things about Greenwood is that you can seamlessly move from completely static to server rendered, without giving up either one! 💯 Given the following workspace of just pages + ```shell src/ pages/ From c7d910624da1753c9c919793322ca9421b38de1f Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 18 Jun 2022 15:20:17 -0400 Subject: [PATCH 08/15] clean up console logging --- packages/cli/src/lib/ssr-route-worker.js | 3 +-- www/pages/artists.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/ssr-route-worker.js b/packages/cli/src/lib/ssr-route-worker.js index 3bab71c46..3a30e0b8b 100644 --- a/packages/cli/src/lib/ssr-route-worker.js +++ b/packages/cli/src/lib/ssr-route-worker.js @@ -23,8 +23,7 @@ async function executeRouteModule({ modulePath, compilation, route, label, id }) } if (module.default) { - const { html, metadata } = await renderToString(moduleURL); - console.debug({ metadata }); + const { html } = await renderToString(moduleURL); data.body = html; } else { diff --git a/www/pages/artists.js b/www/pages/artists.js index b0a5622f9..0fe8d1bc9 100644 --- a/www/pages/artists.js +++ b/www/pages/artists.js @@ -24,8 +24,7 @@ export default class ArtistsPage extends HTMLElement { } } -async function getFrontmatter(compilation, route) { - console.debug({ route }); +async function getFrontmatter() { return { menu: 'navigation', index: 7 From e4d78c45b98aa11f99d111b3c3fe558156adb3ff Mon Sep 17 00:00:00 2001 From: Owen Buckley Date: Sat, 18 Jun 2022 20:40:08 -0400 Subject: [PATCH 09/15] rollback premature static export feature --- packages/cli/src/commands/build.js | 12 ++--- packages/cli/src/lifecycles/bundle.js | 7 +-- packages/cli/src/lifecycles/prerender.js | 29 ------------ packages/cli/src/lifecycles/serve.js | 7 +-- .../build.default.ssr.spec.js | 46 ++++++++++++------- .../build.default.ssr/src/pages/artists.js | 3 +- .../build.default.ssr/src/pages/users.js | 14 +----- 7 files changed, 43 insertions(+), 75 deletions(-) diff --git a/packages/cli/src/commands/build.js b/packages/cli/src/commands/build.js index df110a5bf..7181ffe96 100644 --- a/packages/cli/src/commands/build.js +++ b/packages/cli/src/commands/build.js @@ -2,7 +2,7 @@ import { bundleCompilation } from '../lifecycles/bundle.js'; import { copyAssets } from '../lifecycles/copy.js'; import { getDevServer } from '../lifecycles/serve.js'; import fs from 'fs'; -import { preRenderCompilationCustom, preRenderCompilationDefault, preRenderCompilationPuppeteer, staticRenderCompilation } from '../lifecycles/prerender.js'; +import { preRenderCompilationCustom, preRenderCompilationDefault, staticRenderCompilation } from '../lifecycles/prerender.js'; import { ServerInterface } from '../lib/server-interface.js'; const runProductionBuild = async (compilation) => { @@ -16,21 +16,15 @@ const runProductionBuild = async (compilation) => { const customPrerender = (compilation.config.plugins.filter(plugin => plugin.type === 'renderer' && !plugin.isGreenwoodDefaultPlugin) || []).length === 1 ? compilation.config.plugins.filter(plugin => plugin.type === 'renderer')[0].provider(compilation) : {}; - const hasServerExports = compilation.graph.filter(page => page.isSSR && page.data.prerender).length > 0; if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir); } - if (prerender || customPrerender.prerender || hasServerExports) { + if (prerender || customPrerender.prerender) { if (customPrerender.prerender) { await preRenderCompilationCustom(compilation, customPrerender); } else { - console.debug('default prerender'); - if (hasServerExports) { - await preRenderCompilationDefault(compilation); - } - await new Promise(async (resolve, reject) => { try { (await getDevServer(compilation)).listen(port, async () => { @@ -54,7 +48,7 @@ const runProductionBuild = async (compilation) => { return Promise.resolve(server); })); - await preRenderCompilationPuppeteer(compilation); + await preRenderCompilationDefault(compilation); resolve(); }); diff --git a/packages/cli/src/lifecycles/bundle.js b/packages/cli/src/lifecycles/bundle.js index 36a321054..4b4229b86 100644 --- a/packages/cli/src/lifecycles/bundle.js +++ b/packages/cli/src/lifecycles/bundle.js @@ -5,11 +5,12 @@ const bundleCompilation = async (compilation) => { return new Promise(async (resolve, reject) => { try { - compilation.graph = compilation.graph.filter(page => !page.isSSR || page.isSSR && page.data.prerender); - // https://rollupjs.org/guide/en/#differences-to-the-javascript-api if (compilation.graph.length > 0) { - const rollupConfigs = await getRollupConfig(compilation); + const rollupConfigs = await getRollupConfig({ + ...compilation, + graph: compilation.graph.filter(page => !page.isSSR) + }); const bundle = await rollup(rollupConfigs[0]); await bundle.write(rollupConfigs[0].output); diff --git a/packages/cli/src/lifecycles/prerender.js b/packages/cli/src/lifecycles/prerender.js index 70fb9ccb2..ea19dbbd7 100644 --- a/packages/cli/src/lifecycles/prerender.js +++ b/packages/cli/src/lifecycles/prerender.js @@ -124,34 +124,6 @@ async function preRenderCompilationCustom(compilation, customPrerender) { } async function preRenderCompilationDefault(compilation) { - const pages = compilation.graph.filter(page => page.isSSR && page.data.prerender); - const outputDir = compilation.context.scratchDir; - - for (const page of pages) { - const { outputPath, route } = page; - const outputPathDir = path.join(outputDir, route); - const htmlResource = compilation.config.plugins.filter((plugin) => { - return plugin.name === 'plugin-standard-html'; - }).map((plugin) => { - return plugin.provider(compilation); - })[0]; - let html; - - html = (await htmlResource.serve(page.route)).body; - html = (await interceptPage(compilation, html, route)).body; - html = await optimizePage(compilation, html, route, outputPath, outputDir); - - if (!fs.existsSync(outputPathDir)) { - fs.mkdirSync(outputPathDir, { - recursive: true - }); - } - - await fs.promises.writeFile(path.join(outputDir, outputPath), html); - } -} - -async function preRenderCompilationPuppeteer(compilation) { const BrowserRunner = (await import('../lib/browser.js')).BrowserRunner; const browserRunner = new BrowserRunner(); @@ -248,6 +220,5 @@ async function staticRenderCompilation(compilation) { export { preRenderCompilationCustom, preRenderCompilationDefault, - preRenderCompilationPuppeteer, staticRenderCompilation }; \ No newline at end of file diff --git a/packages/cli/src/lifecycles/serve.js b/packages/cli/src/lifecycles/serve.js index bf3e42528..9964c0a39 100644 --- a/packages/cli/src/lifecycles/serve.js +++ b/packages/cli/src/lifecycles/serve.js @@ -314,9 +314,10 @@ async function getHybridServer(compilation) { await fs.promises.mkdir(path.join(compilation.context.scratchDir, url), { recursive: true }); await fs.promises.writeFile(path.join(compilation.context.scratchDir, url, 'index.html'), body); - compilation.graph = compilation.graph.filter(page => page.isSSR && page.route === url); - - const rollupConfigs = await getRollupConfig(compilation); + const rollupConfigs = await getRollupConfig({ + ...compilation, + graph: [matchingRoute] + }); const bundle = await rollup(rollupConfigs[0]); await bundle.write(rollupConfigs[0].output); diff --git a/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js b/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js index 599f62764..8e6229f58 100644 --- a/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js +++ b/packages/cli/test/cases/build.default.ssr/build.default.ssr.spec.js @@ -17,6 +17,8 @@ * footer.js * pages/ * artists.js + * index.md + * users.js * templates/ * app.html */ @@ -43,7 +45,7 @@ describe('Build Greenwood With: ', function() { this.context = { publicDir: path.join(outputPath, 'public') }; - runner = new Runner(true); + runner = new Runner(); }); describe(LABEL, function() { @@ -54,7 +56,7 @@ describe('Build Greenwood With: ', function() { return new Promise(async (resolve) => { setTimeout(() => { resolve(); - }, 10000); + }, 15000); await runner.runCommand(cliPath, 'serve'); }); @@ -63,13 +65,12 @@ describe('Build Greenwood With: ', function() { runSmokeTest(['public', 'index'], LABEL); let response = {}; - let dom; + let artistsPageDom; let usersPageDom; let artistsPageGraphData; before(async function() { const graph = JSON.parse(await fs.promises.readFile(path.join(outputPath, 'public/graph.json'), 'utf-8')); - const usersPageHtml = await fs.promises.readFile(path.join(outputPath, 'public/users/index.html')); artistsPageGraphData = graph.filter(page => page.route === '/artists/')[0]; @@ -81,8 +82,21 @@ describe('Build Greenwood With: ', function() { response = res; response.body = body; - dom = new JSDOM(body); - usersPageDom = new JSDOM(usersPageHtml); + + artistsPageDom = new JSDOM(body); + + resolve(); + }); + }); + }); + + before(async function() { + return new Promise((resolve, reject) => { + request.get(`${hostname}/users/`, (err, res, body) => { + if (err) { + reject(); + } + usersPageDom = new JSDOM(body); resolve(); }); @@ -107,24 +121,24 @@ describe('Build Greenwood With: ', function() { }); it('the response body should be valid HTML from JSDOM', function(done) { - expect(dom).to.not.be.undefined; + expect(artistsPageDom).to.not.be.undefined; done(); }); it('should have one style tags', function() { - const styles = dom.window.document.querySelectorAll('head > style'); + const styles = artistsPageDom.window.document.querySelectorAll('head > style'); expect(styles.length).to.equal(1); }); it('should have the expected number of