mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 04:09:12 +00:00
Initial SSR implementation (#157)
* continued ssr work * continued ssr work * prettier * all components now rendering * everything finally works * fix type issues * working on breadcrumb * working on breadcrumb * radio group * convert all tests to ssr * prettier * test suite finally passing * add layout stuff * add changelog * fix TS issue * fix tests * fixing deploy stuff * get QR code displaying * fix tests * fix tests * prettier * condense hydration stuff * prettier * comment out range test * fixing issues * use base fixtures * fixing examples * dont vendor * fix import of hydration support * adding notes * add notesg * add ssr loader * fix build * prettier * add notes * add notes * prettier * fixing bundled stuff * remove cdn * remove cdn * prettier * fiixng tests * prettier * split jobs?? * prettier * fix build stuff * add reset mouse and await aTimeout * prettier * fix improper tests * prettier * bail on first * fix linting * only test form with client * redundancy on ssr-loader?? * maybe this will work * prettier * try callout now * fix form.test.ts * fix form.test.ts * prettier * fix forms * fix forms * try again * prettier * add some awaits * prettier * comment out broken SSR tests * prettier * comment out broken SSR tests * prettier * dont skip in CI * upgrade playwright to beta * prettier * try some trickery * try some trickery * await updateComplete * try to fix form.test.ts * import hydrateable elements 1 time * prettier * fix input defaultValue issues * fix form controls to behave like their native counterpartS * add changelog entry * prettier * fix unexpected behavior with range / button
This commit is contained in:
128
docs/assets/scripts/hydration-errors.js
Normal file
128
docs/assets/scripts/hydration-errors.js
Normal file
@@ -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 = `<style>${finalStyles.join('\n')}</style>`;
|
||||
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 = `
|
||||
<button class="diff-dialog-toggle">
|
||||
Show Hydration Mismatch
|
||||
</button>
|
||||
<wa-dialog class="diff-dialog" with-header light-dismiss>
|
||||
<div class="diff-grid">
|
||||
<div>
|
||||
<div>Server</div>
|
||||
<pre class="diff-server"><code></code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<div>Client</div>
|
||||
<pre class="diff-client"><code></code></pre>
|
||||
</div>
|
||||
<div>
|
||||
<div>Diff</div>
|
||||
<pre class="diff-viewer"><code></code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</wa-dialog>
|
||||
`;
|
||||
|
||||
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);
|
||||
})();
|
||||
@@ -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 `<template>` elements hanging around in the lightdom.
|
||||
// https://github.com/hotwired/turbo/issues/1292
|
||||
['turbo:before-render', 'turbo:before-stream-render', 'turbo:before-frame-render'].forEach(eventName => {
|
||||
document.addEventListener(eventName, fixDSD);
|
||||
});
|
||||
|
||||
window.addEventListener('turbo:before-cache', saveScrollPosition);
|
||||
window.addEventListener('turbo:before-render', restoreScrollPosition);
|
||||
window.addEventListener('turbo:render', restoreScrollPosition);
|
||||
|
||||
Reference in New Issue
Block a user