Skip to content

Commit 270d4a2

Browse files
authored
✨ add declarative shadow dom and docs (#18)
1 parent c7d6db0 commit 270d4a2

17 files changed

+553
-179
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"cem": "custom-elements-manifest analyze --config 'lib/custom-elements-manifest.config.js'",
88
"storybook": "storybook dev -p 6006",
99
"build-storybook": "storybook build",
10-
"preview-storybook": "storybook preview",
10+
"preview-storybook": "storybook dev",
1111
"test-storybook": "test-storybook --coverage",
1212
"predist": "npm run cem && npm run build-storybook",
1313
"dist": "node lib/esbuild.config.js",

src/devto/README.md

Lines changed: 47 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,46 +23,6 @@ see README at root of repo for usage details
2323

2424
---
2525

26-
## Members
27-
28-
<dl>
29-
<dt><a href="#DEV-Post-Declarative-Shadow-DOM">DEV-Post-Declarative-Shadow-DOM</a> ⇒ <code>string</code></dt>
30-
<dd></dd>
31-
<dt><a href="#DEV-Declarative-Shadow-DOM">DEV-Declarative-Shadow-DOM</a> ⇒ <code>string</code></dt>
32-
<dd></dd>
33-
</dl>
34-
35-
## Objects
36-
37-
<dl>
38-
<dt><a href="#DEVUtils">DEVUtils</a> : <code>object</code></dt>
39-
<dd><p>Utility functions for fetching and parsing dev.to api data, getting
40-
styles and generating HTML for dev.to profile UIs</p>
41-
</dd>
42-
</dl>
43-
44-
<a name="DEV-Post-Declarative-Shadow-DOM"></a>
45-
46-
## DEV-Post-Declarative-Shadow-DOM ⇒ <code>string</code>
47-
**Kind**: global variable
48-
**Returns**: <code>string</code> - DEV post HTML wrapped in a `template`
49-
50-
| Param | Type | Description |
51-
| --- | --- | --- |
52-
| content | <code>ForemPostHTML</code> | Content about one post by dev.to (or Forem) user |
53-
| fetch | <code>boolean</code> | |
54-
55-
<a name="DEV-Declarative-Shadow-DOM"></a>
56-
57-
## DEV-Declarative-Shadow-DOM ⇒ <code>string</code>
58-
**Kind**: global variable
59-
**Returns**: <code>string</code> - DEV HTML wrapped in a `template`
60-
61-
| Param | Type | Description |
62-
| --- | --- | --- |
63-
| content | <code>ForemUserHTML</code> | a content object representing a DEV user |
64-
| fetch | <code>boolean</code> | |
65-
6626
<a name="DEVUtils"></a>
6727

6828
## DEVUtils : <code>object</code>
@@ -74,6 +34,7 @@ Utility functions for fetching and parsing dev.to api data, getting
7434

7535
* [DEVUtils](#DEVUtils) : <code>object</code>
7636
* [.post](#DEVUtils.post) : <code>object</code>
37+
* [.dsd](#DEVUtils.post.dsd) ⇒ <code>string</code>
7738
* [.html(content)](#DEVUtils.post.html) ⇒ <code>string</code>
7839
* [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ <code>ForemPost</code> \| <code>ForemError</code>
7940
* [.ForemPost](#DEVUtils.post.ForemPost) : <code>Object</code>
@@ -82,6 +43,7 @@ Utility functions for fetching and parsing dev.to api data, getting
8243
* [.styles](#DEVUtils.user.styles)
8344
* [.html(content)](#DEVUtils.user.html) ⇒ <code>string</code>
8445
* [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ <code>ForemUserHTML</code>
46+
* [.dsd(content, fetch)](#DEVUtils.user.dsd) ⇒ <code>string</code>
8547
* [.ForemUser](#DEVUtils.user.ForemUser) : <code>Object</code>
8648
* [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : <code>ForemUser</code>
8749

@@ -91,6 +53,27 @@ Utility functions for fetching and parsing dev.to api data, getting
9153
Utility functions for a post
9254

9355
**Kind**: static namespace of [<code>DEVUtils</code>](#DEVUtils)
56+
57+
* [.post](#DEVUtils.post) : <code>object</code>
58+
* [.dsd](#DEVUtils.post.dsd) ⇒ <code>string</code>
59+
* [.html(content)](#DEVUtils.post.html) ⇒ <code>string</code>
60+
* [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ <code>ForemPost</code> \| <code>ForemError</code>
61+
* [.ForemPost](#DEVUtils.post.ForemPost) : <code>Object</code>
62+
* [.ForemPostHTML](#DEVUtils.post.ForemPostHTML) : <code>ForemPost</code>
63+
64+
<a name="DEVUtils.post.dsd"></a>
65+
66+
#### post.dsd ⇒ <code>string</code>
67+
Generate a `template` element with a shadowdom with a Post in it
68+
69+
**Kind**: static namespace of [<code>post</code>](#DEVUtils.post)
70+
**Returns**: <code>string</code> - DEV post HTML wrapped in a `template`
71+
72+
| Param | Type | Description |
73+
| --- | --- | --- |
74+
| content | <code>ForemPostHTML</code> | Content about one post by dev.to (or Forem) user |
75+
| fetch | <code>boolean</code> | |
76+
9477
**Example** *(Server side rendering a post with Declarative Shadow Dom)*
9578
```js
9679
<devto-post></devto-post>
@@ -101,13 +84,6 @@ const dsdHTML = post.dsd({id: '12345'}, true);
10184
document.querySelector('devto-post').innerHTML = dsdHTML;
10285
</script>
10386
```
104-
105-
* [.post](#DEVUtils.post) : <code>object</code>
106-
* [.html(content)](#DEVUtils.post.html) ⇒ <code>string</code>
107-
* [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ <code>ForemPost</code> \| <code>ForemError</code>
108-
* [.ForemPost](#DEVUtils.post.ForemPost) : <code>Object</code>
109-
* [.ForemPostHTML](#DEVUtils.post.ForemPostHTML) : <code>ForemPost</code>
110-
11187
<a name="DEVUtils.post.html"></a>
11288

11389
#### post.html(content) ⇒ <code>string</code>
@@ -168,21 +144,12 @@ Forem post content, ready for HTML
168144
Utility functions for a user
169145

170146
**Kind**: static namespace of [<code>DEVUtils</code>](#DEVUtils)
171-
**Example** *(Server side rendering with Declarative Shadow Dom)*
172-
```js
173-
<devto-user></devto-user>
174-
175-
<script type="module">
176-
import {dsd} from 'profile-components/devto-utils';
177-
const dsdHTML = dsd({username: 'scottnath'}, true);
178-
document.querySelector('devto-user').innerHTML = dsdHTML;
179-
</script>
180-
```
181147

182148
* [.user](#DEVUtils.user) : <code>object</code>
183149
* [.styles](#DEVUtils.user.styles)
184150
* [.html(content)](#DEVUtils.user.html) ⇒ <code>string</code>
185151
* [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ <code>ForemUserHTML</code>
152+
* [.dsd(content, fetch)](#DEVUtils.user.dsd) ⇒ <code>string</code>
186153
* [.ForemUser](#DEVUtils.user.ForemUser) : <code>Object</code>
187154
* [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : <code>ForemUser</code>
188155

@@ -217,6 +184,29 @@ Generates an object of content for the user HTML
217184
| content | <code>ForemUserHTML</code> |
218185
| [fetch] | <code>boolean</code> |
219186

187+
<a name="DEVUtils.user.dsd"></a>
188+
189+
#### user.dsd(content, fetch) ⇒ <code>string</code>
190+
Generate a `template` element with shadowrootmode with a User in it
191+
192+
**Kind**: static method of [<code>user</code>](#DEVUtils.user)
193+
**Returns**: <code>string</code> - DEV HTML wrapped in a `template`
194+
195+
| Param | Type | Description |
196+
| --- | --- | --- |
197+
| content | <code>ForemUserHTML</code> | a content object representing a DEV user |
198+
| fetch | <code>boolean</code> | |
199+
200+
**Example** *(Server side rendering with Declarative Shadow Dom)*
201+
```js
202+
<devto-user></devto-user>
203+
204+
<script type="module">
205+
import {dsd} from 'profile-components/devto-utils';
206+
const dsdHTML = dsd({username: 'scottnath'}, true);
207+
document.querySelector('devto-user').innerHTML = dsdHTML;
208+
</script>
209+
```
220210
<a name="DEVUtils.user.ForemUser"></a>
221211

222212
#### user.ForemUser : <code>Object</code>

src/devto/dsd.docs.mdx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Meta, Title, Source } from '@storybook/blocks';
2+
3+
<Meta isTemplate />
4+
5+
<Title />
6+
7+
Both DEV components can be implemented via Declarative Shadow DOM using methods exported from the `devto-utils.js` file.
8+
9+
10+
## Server Side Rendering HTML in Node.js
11+
12+
<Source code={`
13+
// import from npm module
14+
import { dsd } from 'profile-components/devto-utils';
15+
16+
const generatedTemplate = await dsd({
17+
username: 'scottnath',
18+
},true);
19+
20+
/**
21+
generatedTemplate contains:
22+
<template shadowrootmode="open">
23+
<styles>(...css styles for DEV component)</styles>
24+
<section (...rest of generated HTML)</section>
25+
</template>
26+
*/
27+
28+
const componentHTML = \`<devto-user>\${generatedTemplate}</devto-user>\`;
29+
`} language='js' />
30+
31+
## Server side render in an Astro component
32+
33+
<Source code={`
34+
---
35+
import {dsd} from 'profile-components/devto-utils';
36+
37+
const declaredDOM = await dsd({
38+
username: 'scottnath',
39+
},true)
40+
---
41+
42+
<devto-user
43+
data-theme="light_high_contrast"
44+
set:html={declaredDOM}>
45+
</devto-user>
46+
`} language='jsx' />
47+
48+
## Client side rendering via unpkg
49+
50+
<Source code={`
51+
52+
<!-- add empty elements to HTML -->
53+
<devto-post></devto-post>
54+
<hr />
55+
<devto-user></devto-user>
56+
57+
<script type="module">
58+
// import from unpkg
59+
import {
60+
user,
61+
post,
62+
} from 'https://unpkg.com/profile-components/dist/devto-utils.js';
63+
64+
// post has it's own DSD method:
65+
const dsdPost = post.dsd;
66+
67+
/**
68+
* Polyfill for Declarative Shadow DOM which, when triggered, converts
69+
* the template element into actual shadow DOM.
70+
* This is only needed when injecting _after_ page is loaded
71+
* @see https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
72+
*/
73+
const triggerAttachShadowRoots = () => {
74+
(function attachShadowRoots(root) {
75+
root
76+
.querySelectorAll('template[shadowrootmode]')
77+
.forEach((template) => {
78+
const mode = template.getAttribute('shadowrootmode');
79+
const shadowRoot = template.parentNode.attachShadow({ mode });
80+
shadowRoot.appendChild(template.content);
81+
template.remove();
82+
attachShadowRoots(shadowRoot);
83+
});
84+
})(document);
85+
};
86+
87+
/**
88+
* Uses the "dsd" method to generate DSD, add the string of DSD content
89+
* to the element, then trigger the polyfill to convert the template
90+
*/
91+
const injectDSD = async () => {
92+
const dsdHTML = await dsd({ username: 'scottnath' }, true);
93+
document.querySelector('devto-user').innerHTML = dsdHTML;
94+
// now that the HTML is async-created, the polyfill can convert it
95+
triggerAttachShadowRoots();
96+
};
97+
injectDSD();
98+
99+
/**
100+
* Uses the "dsdPost" method to generate DSD, add the string of DSD content
101+
* to the element, then trigger the polyfill to convert the template
102+
*/
103+
const injectPostDSD = async () => {
104+
const dsdHTML = await dsdPost(
105+
{ full_name: 'scottnath/profile-components' },
106+
true
107+
);
108+
document.querySelector('devto-post').innerHTML = dsdHTML;
109+
// now that the HTML is async-created, the polyfill can convert it
110+
triggerAttachShadowRoots();
111+
};
112+
injectPostDSD();
113+
</script>
114+
`} language='html' />

src/devto/dsd.stories.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
2+
import { parseFetchedPost } from './post/content.js';
3+
import { parseFetchedUser } from './user/content.js';
4+
import { default as userScottnath } from './fixtures/generated/user--scottnath.json';
5+
import { default as postProfileComponents } from './fixtures/generated/post--profile-components.json';
6+
import { default as postDependabot } from './fixtures/generated/post--dependabot.json';
7+
import { default as postBugfix } from './fixtures/generated/post--bugfix-multi-vite.json';
8+
import { post, dsd } from './index.js';
9+
import docs from './dsd.docs.mdx';
10+
11+
12+
export default {
13+
title: 'DevTo/Declarative Shadow DOM',
14+
parameters: {
15+
docs: {
16+
page: docs
17+
},
18+
},
19+
tags: ['autodocs'],
20+
decorators: [(story) => `${story()}
21+
<script>
22+
23+
(function attachShadowRoots(root) {
24+
root.querySelectorAll("template[shadowrootmode]").forEach(template => {
25+
const mode = template.getAttribute("shadowrootmode");
26+
const shadowRoot = template.parentNode.attachShadow({ mode });
27+
shadowRoot.appendChild(template.content);
28+
template.remove();
29+
attachShadowRoots(shadowRoot);
30+
});
31+
})(document);
32+
</script>
33+
`],
34+
};
35+
36+
export const Post = {
37+
loaders: [
38+
async ({args}) => ({
39+
dsdOutput: await (await post.dsd(args)),
40+
}),
41+
],
42+
render: (args, { loaded: { dsdOutput } }) => {
43+
return `
44+
<devto-post-dsd>${dsdOutput}</devto-post-dsd>
45+
46+
`;
47+
},
48+
args: {
49+
...parseFetchedPost(postProfileComponents)
50+
},
51+
}
52+
53+
export const User = {
54+
loaders: [
55+
async ({args}) => ({
56+
dsdOutput: await (await dsd(args)),
57+
}),
58+
],
59+
render: (args, { loaded: { dsdOutput } }) => {
60+
return `
61+
<devto-user-dsd data-theme="dark">${dsdOutput}</devto-user-dsd>
62+
63+
`;
64+
},
65+
args: {
66+
...parseFetchedUser(userScottnath),
67+
latest_post: stringify(parseFetchedPost(postDependabot)),
68+
popular_post: stringify(parseFetchedPost(postBugfix)),
69+
},
70+
}

src/devto/fixtures/generated/post--bugfix-multi-vite.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"id": 1466138,
44
"title": "Bugfix: Multiple Vite Storybooks from Same node_modules",
55
"description": "tl;dr Encountering Failed to fetch dynamically imported module or ENOTEMPTY: directory not...",
6-
"readable_publish_date": "May 12 2023",
6+
"readable_publish_date": "May 12 '23",
77
"slug": "bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1",
88
"path": "/scottnath/bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1",
99
"url": "https://dev.to/scottnath/bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1",
@@ -40,4 +40,4 @@
4040
"profile_image": "https://media.dev.to/cdn-cgi/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg",
4141
"profile_image_90": "https://media.dev.to/cdn-cgi/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg"
4242
}
43-
}
43+
}

src/devto/fixtures/generated/post--dependabot.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"id": 1568661,
44
"title": "A crazy-simple way to bulk-update NPM dependencies with GitHub's Dependabot",
55
"description": "This is the simplest way I've found to keep your NPM dependencies up-to-date. This will update all...",
6-
"readable_publish_date": "Aug 15 2023",
6+
"readable_publish_date": "Aug 15 '23",
77
"slug": "a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o",
88
"path": "/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o",
99
"url": "https://dev.to/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o",
@@ -40,4 +40,4 @@
4040
"profile_image": "https://media.dev.to/cdn-cgi/image/width=640,height=640,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg",
4141
"profile_image_90": "https://media.dev.to/cdn-cgi/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1055555%2F4d0bf90a-bec7-4228-b1ca-d663fa40adeb.jpeg"
4242
}
43-
}
43+
}

0 commit comments

Comments
 (0)