Skip to content

Commit

Permalink
✨ add declarative shadow dom and docs (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottnath authored Feb 21, 2024
1 parent c7d6db0 commit 270d4a2
Show file tree
Hide file tree
Showing 17 changed files with 553 additions and 179 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"cem": "custom-elements-manifest analyze --config 'lib/custom-elements-manifest.config.js'",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"preview-storybook": "storybook preview",
"preview-storybook": "storybook dev",
"test-storybook": "test-storybook --coverage",
"predist": "npm run cem && npm run build-storybook",
"dist": "node lib/esbuild.config.js",
Expand Down
104 changes: 47 additions & 57 deletions src/devto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,6 @@ see README at root of repo for usage details

---

## Members

<dl>
<dt><a href="#DEV-Post-Declarative-Shadow-DOM">DEV-Post-Declarative-Shadow-DOM</a> ⇒ <code>string</code></dt>
<dd></dd>
<dt><a href="#DEV-Declarative-Shadow-DOM">DEV-Declarative-Shadow-DOM</a> ⇒ <code>string</code></dt>
<dd></dd>
</dl>

## Objects

<dl>
<dt><a href="#DEVUtils">DEVUtils</a> : <code>object</code></dt>
<dd><p>Utility functions for fetching and parsing dev.to api data, getting
styles and generating HTML for dev.to profile UIs</p>
</dd>
</dl>

<a name="DEV-Post-Declarative-Shadow-DOM"></a>

## DEV-Post-Declarative-Shadow-DOM ⇒ <code>string</code>
**Kind**: global variable
**Returns**: <code>string</code> - DEV post HTML wrapped in a `template`

| Param | Type | Description |
| --- | --- | --- |
| content | <code>ForemPostHTML</code> | Content about one post by dev.to (or Forem) user |
| fetch | <code>boolean</code> | |

<a name="DEV-Declarative-Shadow-DOM"></a>

## DEV-Declarative-Shadow-DOM ⇒ <code>string</code>
**Kind**: global variable
**Returns**: <code>string</code> - DEV HTML wrapped in a `template`

| Param | Type | Description |
| --- | --- | --- |
| content | <code>ForemUserHTML</code> | a content object representing a DEV user |
| fetch | <code>boolean</code> | |

<a name="DEVUtils"></a>

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

* [DEVUtils](#DEVUtils) : <code>object</code>
* [.post](#DEVUtils.post) : <code>object</code>
* [.dsd](#DEVUtils.post.dsd) ⇒ <code>string</code>
* [.html(content)](#DEVUtils.post.html) ⇒ <code>string</code>
* [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ <code>ForemPost</code> \| <code>ForemError</code>
* [.ForemPost](#DEVUtils.post.ForemPost) : <code>Object</code>
Expand All @@ -82,6 +43,7 @@ Utility functions for fetching and parsing dev.to api data, getting
* [.styles](#DEVUtils.user.styles)
* [.html(content)](#DEVUtils.user.html) ⇒ <code>string</code>
* [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ <code>ForemUserHTML</code>
* [.dsd(content, fetch)](#DEVUtils.user.dsd) ⇒ <code>string</code>
* [.ForemUser](#DEVUtils.user.ForemUser) : <code>Object</code>
* [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : <code>ForemUser</code>

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

**Kind**: static namespace of [<code>DEVUtils</code>](#DEVUtils)

* [.post](#DEVUtils.post) : <code>object</code>
* [.dsd](#DEVUtils.post.dsd) ⇒ <code>string</code>
* [.html(content)](#DEVUtils.post.html) ⇒ <code>string</code>
* [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ <code>ForemPost</code> \| <code>ForemError</code>
* [.ForemPost](#DEVUtils.post.ForemPost) : <code>Object</code>
* [.ForemPostHTML](#DEVUtils.post.ForemPostHTML) : <code>ForemPost</code>

<a name="DEVUtils.post.dsd"></a>

#### post.dsd ⇒ <code>string</code>
Generate a `template` element with a shadowdom with a Post in it

**Kind**: static namespace of [<code>post</code>](#DEVUtils.post)
**Returns**: <code>string</code> - DEV post HTML wrapped in a `template`

| Param | Type | Description |
| --- | --- | --- |
| content | <code>ForemPostHTML</code> | Content about one post by dev.to (or Forem) user |
| fetch | <code>boolean</code> | |

**Example** *(Server side rendering a post with Declarative Shadow Dom)*
```js
<devto-post></devto-post>
Expand All @@ -101,13 +84,6 @@ const dsdHTML = post.dsd({id: '12345'}, true);
document.querySelector('devto-post').innerHTML = dsdHTML;
</script>
```

* [.post](#DEVUtils.post) : <code>object</code>
* [.html(content)](#DEVUtils.post.html) ⇒ <code>string</code>
* [.generateContent(content, [fetch])](#DEVUtils.post.generateContent) ⇒ <code>ForemPost</code> \| <code>ForemError</code>
* [.ForemPost](#DEVUtils.post.ForemPost) : <code>Object</code>
* [.ForemPostHTML](#DEVUtils.post.ForemPostHTML) : <code>ForemPost</code>

<a name="DEVUtils.post.html"></a>

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

**Kind**: static namespace of [<code>DEVUtils</code>](#DEVUtils)
**Example** *(Server side rendering with Declarative Shadow Dom)*
```js
<devto-user></devto-user>

<script type="module">
import {dsd} from 'profile-components/devto-utils';
const dsdHTML = dsd({username: 'scottnath'}, true);
document.querySelector('devto-user').innerHTML = dsdHTML;
</script>
```

* [.user](#DEVUtils.user) : <code>object</code>
* [.styles](#DEVUtils.user.styles)
* [.html(content)](#DEVUtils.user.html) ⇒ <code>string</code>
* [.generateContent(content, [fetch])](#DEVUtils.user.generateContent) ⇒ <code>ForemUserHTML</code>
* [.dsd(content, fetch)](#DEVUtils.user.dsd) ⇒ <code>string</code>
* [.ForemUser](#DEVUtils.user.ForemUser) : <code>Object</code>
* [.ForemUserHTML](#DEVUtils.user.ForemUserHTML) : <code>ForemUser</code>

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

<a name="DEVUtils.user.dsd"></a>

#### user.dsd(content, fetch) ⇒ <code>string</code>
Generate a `template` element with shadowrootmode with a User in it

**Kind**: static method of [<code>user</code>](#DEVUtils.user)
**Returns**: <code>string</code> - DEV HTML wrapped in a `template`

| Param | Type | Description |
| --- | --- | --- |
| content | <code>ForemUserHTML</code> | a content object representing a DEV user |
| fetch | <code>boolean</code> | |

**Example** *(Server side rendering with Declarative Shadow Dom)*
```js
<devto-user></devto-user>

<script type="module">
import {dsd} from 'profile-components/devto-utils';
const dsdHTML = dsd({username: 'scottnath'}, true);
document.querySelector('devto-user').innerHTML = dsdHTML;
</script>
```
<a name="DEVUtils.user.ForemUser"></a>

#### user.ForemUser : <code>Object</code>
Expand Down
114 changes: 114 additions & 0 deletions src/devto/dsd.docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { Meta, Title, Source } from '@storybook/blocks';

<Meta isTemplate />

<Title />

Both DEV components can be implemented via Declarative Shadow DOM using methods exported from the `devto-utils.js` file.


## Server Side Rendering HTML in Node.js

<Source code={`
// import from npm module
import { dsd } from 'profile-components/devto-utils';
const generatedTemplate = await dsd({
username: 'scottnath',
},true);
/**
generatedTemplate contains:
<template shadowrootmode="open">
<styles>(...css styles for DEV component)</styles>
<section (...rest of generated HTML)</section>
</template>
*/
const componentHTML = \`<devto-user>\${generatedTemplate}</devto-user>\`;
`} language='js' />

## Server side render in an Astro component

<Source code={`
---
import {dsd} from 'profile-components/devto-utils';
const declaredDOM = await dsd({
username: 'scottnath',
},true)
---
<devto-user
data-theme="light_high_contrast"
set:html={declaredDOM}>
</devto-user>
`} language='jsx' />

## Client side rendering via unpkg

<Source code={`
<!-- add empty elements to HTML -->
<devto-post></devto-post>
<hr />
<devto-user></devto-user>
<script type="module">
// import from unpkg
import {
user,
post,
} from 'https://unpkg.com/profile-components/dist/devto-utils.js';
// post has it's own DSD method:
const dsdPost = post.dsd;
/**
* Polyfill for Declarative Shadow DOM which, when triggered, converts
* the template element into actual shadow DOM.
* This is only needed when injecting _after_ page is loaded
* @see https://developer.chrome.com/docs/css-ui/declarative-shadow-dom#polyfill
*/
const triggerAttachShadowRoots = () => {
(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);
});
})(document);
};
/**
* Uses the "dsd" method to generate DSD, add the string of DSD content
* to the element, then trigger the polyfill to convert the template
*/
const injectDSD = async () => {
const dsdHTML = await dsd({ username: 'scottnath' }, true);
document.querySelector('devto-user').innerHTML = dsdHTML;
// now that the HTML is async-created, the polyfill can convert it
triggerAttachShadowRoots();
};
injectDSD();
/**
* Uses the "dsdPost" method to generate DSD, add the string of DSD content
* to the element, then trigger the polyfill to convert the template
*/
const injectPostDSD = async () => {
const dsdHTML = await dsdPost(
{ full_name: 'scottnath/profile-components' },
true
);
document.querySelector('devto-post').innerHTML = dsdHTML;
// now that the HTML is async-created, the polyfill can convert it
triggerAttachShadowRoots();
};
injectPostDSD();
</script>
`} language='html' />
70 changes: 70 additions & 0 deletions src/devto/dsd.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@

import { parseFetchedPost } from './post/content.js';
import { parseFetchedUser } from './user/content.js';
import { default as userScottnath } from './fixtures/generated/user--scottnath.json';
import { default as postProfileComponents } from './fixtures/generated/post--profile-components.json';
import { default as postDependabot } from './fixtures/generated/post--dependabot.json';
import { default as postBugfix } from './fixtures/generated/post--bugfix-multi-vite.json';
import { post, dsd } from './index.js';
import docs from './dsd.docs.mdx';


export default {
title: 'DevTo/Declarative Shadow DOM',
parameters: {
docs: {
page: docs
},
},
tags: ['autodocs'],
decorators: [(story) => `${story()}
<script>
(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);
});
})(document);
</script>
`],
};

export const Post = {
loaders: [
async ({args}) => ({
dsdOutput: await (await post.dsd(args)),
}),
],
render: (args, { loaded: { dsdOutput } }) => {
return `
<devto-post-dsd>${dsdOutput}</devto-post-dsd>
`;
},
args: {
...parseFetchedPost(postProfileComponents)
},
}

export const User = {
loaders: [
async ({args}) => ({
dsdOutput: await (await dsd(args)),
}),
],
render: (args, { loaded: { dsdOutput } }) => {
return `
<devto-user-dsd data-theme="dark">${dsdOutput}</devto-user-dsd>
`;
},
args: {
...parseFetchedUser(userScottnath),
latest_post: stringify(parseFetchedPost(postDependabot)),
popular_post: stringify(parseFetchedPost(postBugfix)),
},
}
4 changes: 2 additions & 2 deletions src/devto/fixtures/generated/post--bugfix-multi-vite.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"id": 1466138,
"title": "Bugfix: Multiple Vite Storybooks from Same node_modules",
"description": "tl;dr Encountering Failed to fetch dynamically imported module or ENOTEMPTY: directory not...",
"readable_publish_date": "May 12 2023",
"readable_publish_date": "May 12 '23",
"slug": "bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1",
"path": "/scottnath/bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1",
"url": "https://dev.to/scottnath/bugfix-multiple-vite-storybooks-from-same-nodemodules-4mg1",
Expand Down Expand Up @@ -40,4 +40,4 @@
"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",
"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"
}
}
}
4 changes: 2 additions & 2 deletions src/devto/fixtures/generated/post--dependabot.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"id": 1568661,
"title": "A crazy-simple way to bulk-update NPM dependencies with GitHub's Dependabot",
"description": "This is the simplest way I've found to keep your NPM dependencies up-to-date. This will update all...",
"readable_publish_date": "Aug 15 2023",
"readable_publish_date": "Aug 15 '23",
"slug": "a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o",
"path": "/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o",
"url": "https://dev.to/scottnath/a-crazy-simple-way-to-bulk-update-npm-dependencies-with-githubs-dependabot-3e2o",
Expand Down Expand Up @@ -40,4 +40,4 @@
"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",
"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"
}
}
}
Loading

0 comments on commit 270d4a2

Please sign in to comment.