Skip to content

Commit 0a8ccf7

Browse files
feat: add support for serializable option in shadow DOM (#118)
* feat: add support for serializable option in shadow DOM * docs: document `serializable` option for declarative shadow DOM and SSR * docs: fix code fence * Update src/index.js Co-authored-by: Ryan Christian <[email protected]> * docs: clarify serializable option wording in README * docs: correct wording for serializable option description in README * test: extend serializable option tests to cover non-serializable/undefined cases and verify serialization output * refactor: PR suggestions --------- Co-authored-by: Ryan Christian <[email protected]> Co-authored-by: Ryan Christian <[email protected]>
1 parent 4711c23 commit 0a8ccf7

File tree

4 files changed

+53
-6
lines changed

4 files changed

+53
-6
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ const Greeting = ({ name = 'World' }) => (
1414
<p>Hello, {name}!</p>
1515
);
1616

17-
register(Greeting, 'x-greeting', ['name'], { shadow: true, mode: 'open', adoptedStyleSheets: [] });
18-
// ^ ^ ^ ^ ^ ^
19-
// | HTML tag name | use shadow-dom | use adoptedStyleSheets
20-
// Component definition Observed attributes Encapsulation mode for the shadow DOM tree
17+
register(Greeting, 'x-greeting', ['name'], { shadow: true, mode: 'open', adoptedStyleSheets: [], serializable: true });
18+
// ^ ^ ^ ^ ^ ^ ^
19+
// | HTML tag name | use shadow-dom | use adoptedStyleSheets |
20+
// Component definition Observed attributes Encapsulation mode for the shadow DOM tree Root is serializable
2121
```
2222

2323
> _**\* Note:** as per the [Custom Elements specification](https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name), the tag name must contain a hyphen._
@@ -81,7 +81,7 @@ register(FullName, 'full-name');
8181

8282
### Passing slots as props
8383

84-
The `register()` function also accepts an optional fourth parameter, an options bag. At present, it allows you to opt-in to using shadow DOM for your custom element by setting the `shadow` property to `true`, and if so, you can also specify the encapsulation mode with `mode`, which can be either `'open'` or `'closed'`.
84+
The `register()` function also accepts an optional fourth parameter, an options bag. At present, it allows you to opt-in to using shadow DOM for your custom element by setting the `shadow` property to `true`, and if so, you can also specify the encapsulation mode with `mode`, which can be either `'open'` or `'closed'`. Additionally, you may mark the shadow root as being serializable with the boolean `serializable` property.
8585

8686
When using shadow DOM, you can make use of named `<slot>` elements in your component to forward the custom element's children into specific places in the shadow tree.
8787

src/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ type Options =
88
shadow: true;
99
mode?: 'open' | 'closed';
1010
adoptedStyleSheets?: CSSStyleSheet[];
11+
serializable?: boolean;
1112
};
1213

1314
/**

src/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ export default function register(Component, tagName, propNames, options) {
1515
inst._vdomComponent = Component;
1616

1717
if (options && options.shadow) {
18-
inst._root = inst.attachShadow({ mode: options.mode || 'open' });
18+
inst._root = inst.attachShadow({
19+
mode: options.mode || 'open',
20+
serializable: options.serializable ?? false,
21+
});
1922

2023
if (options.adoptedStyleSheets) {
2124
inst._root.adoptedStyleSheets = options.adoptedStyleSheets;

test/index.test.jsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,4 +421,47 @@ describe('web components', () => {
421421
'<h1>Light DOM Children</h1><div><slot><p>Child 1</p><p>Child 2</p></slot></div>'
422422
);
423423
});
424+
425+
it('supports the `serializable` option', async () => {
426+
function SerializableComponent() {
427+
return <div>Serializable Shadow DOM</div>;
428+
}
429+
430+
function NonSerializableComponent() {
431+
return <div>Non-serializable Shadow DOM</div>;
432+
}
433+
434+
registerElement(SerializableComponent, 'x-serializable', [], {
435+
shadow: true,
436+
serializable: true,
437+
});
438+
439+
registerElement(NonSerializableComponent, 'x-non-serializable', [], {
440+
shadow: true,
441+
});
442+
443+
root.innerHTML = `
444+
<x-serializable></x-serializable>
445+
<x-non-serializable></x-non-serializable>
446+
`;
447+
448+
const serializableEl = document.querySelector('x-serializable');
449+
const nonSerializableEl = document.querySelector('x-non-serializable');
450+
451+
assert.isTrue(serializableEl.shadowRoot.serializable);
452+
assert.isFalse(nonSerializableEl.shadowRoot.serializable);
453+
454+
const serializableHtml = serializableEl.getHTML({
455+
serializableShadowRoots: true,
456+
});
457+
const nonSerializableHtml = nonSerializableEl.getHTML({
458+
serializableShadowRoots: true,
459+
});
460+
461+
assert.equal(
462+
serializableHtml,
463+
'<template shadowrootmode="open" shadowrootserializable=""><div>Serializable Shadow DOM</div></template>'
464+
);
465+
assert.isEmpty(nonSerializableHtml);
466+
});
424467
});

0 commit comments

Comments
 (0)