-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
frontend: initial web components support
- Loading branch information
Showing
10 changed files
with
217 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# Web Components | ||
|
||
This handler module serves web components with the `.webc` extension. | ||
|
||
## Install | ||
|
||
`npm install @primate/frontend` | ||
|
||
## Configure | ||
|
||
Import and initialize the module in your configuration. | ||
|
||
```js caption=primate.config.js | ||
import { webc } from "@primate/frontend"; | ||
|
||
export default { | ||
modules: [ | ||
webc(), | ||
], | ||
}; | ||
``` | ||
|
||
## Use | ||
|
||
Create an web component in `components`. | ||
|
||
```html caption=components/post-index.webc | ||
<script> | ||
import post_link from "./post-link.webc"; | ||
export default ({posts}) => ` | ||
<h1>All posts</h1> | ||
${posts.map(post => post_link({post}))} | ||
`; | ||
export const mounted = root => { | ||
root.querySelector("h1").addEventListener("click", | ||
_ => console.log("title clicked!")); | ||
} | ||
</script> | ||
``` | ||
|
||
And another component for display post links. | ||
|
||
```html caption=components/post-link.webc | ||
<script> | ||
export default ({post}) => ` | ||
<h2><a href="/post/view/${post.id}">${post.title}</a></h2> | ||
`; | ||
</script> | ||
``` | ||
|
||
Create a route and serve the `post-index` component. | ||
|
||
```js caption=routes/webc.js | ||
import { view } from "primate"; | ||
|
||
const posts = [{ | ||
id: 1, | ||
title: "First post", | ||
}]; | ||
|
||
export default { | ||
get() { | ||
return view("post-index.webc", { posts }); | ||
}, | ||
}; | ||
``` | ||
|
||
Your rendered web component will be accessible at http://localhost:6161/webc. | ||
|
||
## Configuration options | ||
|
||
### extension | ||
|
||
Default `".webc"` | ||
|
||
The file extension associated with web components. | ||
|
||
## Resources | ||
|
||
* [Repository][repo] | ||
|
||
[repo]: https://github.com/primatejs/primate/tree/master/packages/frontend |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export default (name, props) => ` | ||
import * as components from "app"; | ||
globalThis.customElements.define("p-wrap-with", class extends HTMLElement { | ||
connectedCallback() { | ||
this.attachShadow({ mode: "open" }); | ||
const id = this.getAttribute("id"); | ||
const wrapped = globalThis.registry[id]; | ||
this.shadowRoot.appendChild(wrapped); | ||
wrapped.render(); | ||
delete globalThis.registry[id]; | ||
} | ||
}); | ||
globalThis.registry = {}; | ||
const element = globalThis.document.createElement("${name}"); | ||
element.props = ${JSON.stringify(props)}; | ||
globalThis.document.body.appendChild(element) | ||
element.render(); | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { default } from "./default.js"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export default name => `import * as impl from "./${name}.webc.impl.js"; | ||
globalThis.customElements.define("${name}", class extends HTMLElement { | ||
constructor() { | ||
super(); | ||
} | ||
connectedCallback() { | ||
this.attachShadow({ mode: "open" }); | ||
} | ||
render() { | ||
this.shadowRoot.innerHTML = impl.default(this.props); | ||
impl.mounted?.(this.shadowRoot); | ||
} | ||
}); | ||
export default props => { | ||
const element = globalThis.document.createElement("${name}"); | ||
const uuid = crypto.randomUUID(); | ||
element.props = props; | ||
globalThis.registry[uuid] = element; | ||
return \`<p-wrap-with id="\${uuid}"></p-wrap-with>\`; | ||
}`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import client from "./client/render.js"; | ||
|
||
const script_re = /(?<=<script)>(?<code>.*?)(?=<\/script>)/gus; | ||
|
||
const extension = ".webc"; | ||
|
||
const extensions = { | ||
from: extension, | ||
to: `${extension}.js`, | ||
}; | ||
|
||
export const compile = { | ||
async server(text, component, app) { | ||
const { location } = app.config; | ||
const source = app.path.components; | ||
const base = app.runpath(location.server, location.components); | ||
const path = base.join(`${component.path}.js.impl.js`.replace(source, "")); | ||
const script = await Promise.all([...text.matchAll(script_re)] | ||
.map(({ groups: { code } }) => code)); | ||
await path.write(script); | ||
return client(component.base); | ||
}, | ||
async client(text, component, app) { | ||
const { location } = app.config; | ||
const source = app.path.components; | ||
const base = app.runpath(location.client, location.components); | ||
const path = base.join(`${component.path}.js.impl.js`.replace(source, "")); | ||
const [script] = await Promise.all([...text.matchAll(script_re)] | ||
.map(({ groups: { code } }) => code)); | ||
await path.write(script.replaceAll(extensions.from, extensions.to)); | ||
return { js: client(component.base) }; | ||
}, | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { normalize, compile, respond } from "../common/exports.js"; | ||
import client from "./client/exports.js"; | ||
|
||
const handler = (name, props = {}, options = {}) => | ||
async app => { | ||
const [component] = name.split("."); | ||
const assets = [await app.inline(client(component, props), "module")]; | ||
const head = assets.map(asset => asset.head).join("\n"); | ||
const script = assets.map(asset => asset.csp).join(" "); | ||
const headers = { script }; | ||
const body = ""; | ||
|
||
return respond({ app, head, headers, body, options }); | ||
}; | ||
|
||
export default ({ | ||
extension = ".webc", | ||
} = {}) => { | ||
const name = "webc"; | ||
const rootname = name; | ||
let imports = {}; | ||
const normalized = normalize(name); | ||
|
||
return { | ||
name: `primate:${name}`, | ||
async init(app, next) { | ||
imports = await import("./imports.js"); | ||
|
||
return next(app); | ||
}, | ||
async register(app, next) { | ||
app.register(extension, { | ||
handle: handler, | ||
compile: { | ||
...await compile({ | ||
app, | ||
extension, | ||
rootname, | ||
compile: imports.compile, | ||
normalize: normalized, | ||
}), | ||
}, | ||
}); | ||
|
||
return next(app); | ||
}, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,6 +132,7 @@ export default { | |
"React", | ||
"Solid", | ||
"Vue", | ||
"Web Components", | ||
"HTMX", | ||
"Handlebars", | ||
"Markdown", | ||
|