diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index ff8b44e35..dbc3a46d1 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -1,5 +1,36 @@ -# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions +# # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions + +# name: Node.js CI + +# on: +# push: +# branches: [next] +# pull_request: +# branches: [next] + +# jobs: +# build: +# runs-on: ubuntu-latest + +# strategy: +# matrix: +# node-version: [20.x] +# # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + +# steps: +# - uses: actions/checkout@v4 +# - name: Use Node.js ${{ matrix.node-version }} +# uses: actions/setup-node@v4 +# with: +# node-version: ${{ matrix.node-version }} +# cache: 'npm' +# - run: npm ci +# - run: npx playwright install-deps +# - run: npm run verify + +# # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node +# # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions name: Node.js CI @@ -10,21 +41,61 @@ on: branches: [next] jobs: - build: + lint: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x] + node-version: [20.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: 'npm' - - run: npx playwright install-deps - run: npm ci - - run: npm run verify + - run: npm run prettier && npm run lint + + test_client: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npx playwright uninstall --all && npx playwright install --force chromium firefox webkit --with-deps + - run: npm run build + # --bail to fail on first failing test. + - run: CSR_ONLY="true" npm run test -- --bail + + test_ssr: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + - run: npm ci + - run: npx playwright uninstall --all && npx playwright install --force chromium firefox webkit --with-deps + - run: npm run build + - run: SSR_ONLY="true" npm run test -- --bail diff --git a/.gitignore b/.gitignore index aa943f40d..b5bb02845 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,10 @@ _site .DS_Store package.json package-lock.json -dist +dist/ +dist-cdn/ docs/public/pagefind node_modules src/react -cdn .astro +cdn/ diff --git a/custom-elements-manifest.js b/custom-elements-manifest.js index cdf80b9d6..2c1853295 100644 --- a/custom-elements-manifest.js +++ b/custom-elements-manifest.js @@ -7,7 +7,7 @@ import fs from 'fs'; const packageData = JSON.parse(fs.readFileSync('./package.json', 'utf8')); const { name, description, version, author, homepage, license } = packageData; -const outdir = 'dist'; +const outdir = 'dist-cdn'; function replace(string, terms) { terms.forEach(({ from, to }) => { @@ -162,7 +162,7 @@ export default { }), customElementJetBrainsPlugin({ - outdir: './dist', + outdir: './dist-cdn', excludeCss: true, packageJson: false, referencesTemplate: (_, tag) => { diff --git a/docs/.eleventy.js b/docs/.eleventy.js index fe807b788..1630652d7 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -11,6 +11,8 @@ import { searchPlugin } from './_utils/search.js'; import { readFile } from 'fs/promises'; import { outlinePlugin } from './_utils/outline.js'; import { getComponents } from './_utils/manifest.js'; +import litPlugin from '@lit-labs/eleventy-plugin-lit'; + import process from 'process'; const packageData = JSON.parse(await readFile('./package.json', 'utf-8')); @@ -106,6 +108,26 @@ export default function (eleventyConfig) { ]) ); + const omittedModules = []; + + // problematic components: + // animation (breaks on navigation + ssr with Turbo) + // mutation-observer (why SSR this?) + // resize-observer (why SSR this?) + // tooltip (why SSR this?) + + const componentModules = getComponents() + // .filter(component => !omittedModules.includes(component.tagName.split(/wa-/)[1])) + .map(component => { + const name = component.tagName.split(/wa-/)[1]; + return `./dist/components/${name}/${name}.js`; + }); + + eleventyConfig.addPlugin(litPlugin, { + mode: 'worker', + componentModules + }); + // Build the search index eleventyConfig.addPlugin( searchPlugin({ diff --git a/docs/_includes/base.njk b/docs/_includes/base.njk index 95c2722ae..6ecbedfb2 100644 --- a/docs/_includes/base.njk +++ b/docs/_includes/base.njk @@ -13,6 +13,9 @@ {# Scripts #} + {# Hydration stuff #} + + @@ -26,7 +29,7 @@ {# Web Awesome #} - + @@ -128,5 +131,6 @@ {% include 'search.njk' %} + diff --git a/docs/assets/scripts/hydration-errors.js b/docs/assets/scripts/hydration-errors.js new file mode 100644 index 000000000..c6def88a8 --- /dev/null +++ b/docs/assets/scripts/hydration-errors.js @@ -0,0 +1,128 @@ +/** TODO: This should probably get abstracted into an actual package. This is listens to the "lit-hydration-error" and then will add a button to show a dialog of the diff. */ +(async () => { + const hostname = new URL(document.baseURI).hostname; + + // Only diff on localhost. We dont need to show hydration errors on main site. Only locally. + if (hostname !== 'localhost') { + return; + } + + const { diffLines } = await import('https://cdn.jsdelivr.net/npm/diff@5.2.0/+esm'); + const { getDiffableHTML } = await import( + 'https://cdn.jsdelivr.net/npm/@open-wc/semantic-dom-diff@0.20.1/get-diffable-html.js/+esm' + ); + + function wrap(el, wrapper) { + el.parentNode.insertBefore(wrapper, el); + wrapper.appendChild(el); + } + + function handleLitHydrationError(e) { + const element = e.target; + const scratch = document.createElement('div'); + const node = element.cloneNode(true); + scratch.append(node); + document.body.append(scratch); + customElements.upgrade(node); + node.updateComplete.then(() => { + // Render styles. + const elementStyles = element.constructor.elementStyles; + const finalStyles = []; + if (elementStyles !== undefined && elementStyles.length > 0) { + for (const style of elementStyles) { + finalStyles.push(style.cssText); + } + } + + let innerHTML = scratch.firstElementChild?.shadowRoot.innerHTML; + + if (finalStyles?.length) { + const styleTag = ``; + innerHTML = styleTag + '\n' + innerHTML; + } + + const clientHTML = getDiffableHTML(innerHTML); + const serverHTML = getDiffableHTML(element.shadowRoot?.innerHTML); + + const diffDebugger = document.createElement('div'); + diffDebugger.className = 'diff-debugger'; + + diffDebugger.innerHTML = ` + + +
+
+
Server
+
+
+
+
Client
+
+
+
+
Diff
+
+
+
+
+ `; + + element.focus(); + wrap(element, diffDebugger); + + diffDebugger.querySelector('.diff-server > code').textContent = serverHTML; + diffDebugger.querySelector('.diff-client > code').textContent = clientHTML; + const diffViewer = diffDebugger.querySelector('.diff-viewer > code'); + diffViewer.innerHTML = ''; + diffViewer.appendChild( + createDiff({ + serverHTML, + clientHTML + }) + ); + }); + } + + function createDiff({ serverHTML, clientHTML }) { + const diff = diffLines(serverHTML, clientHTML, { + ignoreWhitespace: false, + newLineIsToken: true + }); + const fragment = document.createDocumentFragment(); + for (var i = 0; i < diff.length; i++) { + if (diff[i].added && diff[i + 1] && diff[i + 1].removed) { + var swap = diff[i]; + diff[i] = diff[i + 1]; + diff[i + 1] = swap; + } + + var node; + if (diff[i].removed) { + node = document.createElement('del'); + node.appendChild(document.createTextNode(diff[i].value)); + } else if (diff[i].added) { + node = document.createElement('ins'); + node.appendChild(document.createTextNode(diff[i].value)); + } else { + node = document.createTextNode(diff[i].value); + } + fragment.appendChild(node); + } + + return fragment; + } + + function handleDialogToggle(e) { + const button = e.composedPath().find(el => { + return el.classList && el.classList.contains('diff-dialog-toggle'); + }); + + if (button) { + button.parentElement.querySelector('.diff-dialog').open = true; + } + } + document.addEventListener('lit-hydration-error', handleLitHydrationError); + document.addEventListener('click', handleDialogToggle); +})(); diff --git a/docs/assets/scripts/turbo.js b/docs/assets/scripts/turbo.js index 9de36c966..ad76069be 100644 --- a/docs/assets/scripts/turbo.js +++ b/docs/assets/scripts/turbo.js @@ -43,6 +43,30 @@ function restoreScrollPosition(event) { }); } +function fixDSD(e) { + const newElement = e.detail.newBody || e.detail.newFrame || e.detail.newStream; + if (!newElement) { + return; + } + + // https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill + (function attachShadowRoots(root) { + root.querySelectorAll('template[shadowrootmode]').forEach(template => { + const mode = template.getAttribute('shadowrootmode'); + const shadowRoot = template.parentNode.attachShadow({ mode }); + shadowRoot.appendChild(template.content); + template.remove(); + attachShadowRoots(shadowRoot); + }); + })(newElement); +} + +// Fixes an issue with DSD keeping the `