-
-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Preferences #29
base: main
Are you sure you want to change the base?
Conversation
Yeah, I think this is a worth-wile problem to solve. In my website, I used a dependency filter that adds a custom tag. Basically, you have markup that read like this: <html>
<head></head>
<body>
<custom-script-dependency src="/foo.js" async="true">
</body>
</html> that get's rewritten using two Visitors to <html>
<head>
<script src="/foo.js" async></script>
</head>
<body></body>
</html> I like the simplicity of keeping everything in Node-space. Missing the preprocessing step is also relatively simple since there's always an inspectable output and no "invisible nodes" that don't effect the outcome. I wonder if we could mix these approaches where preferences on a let body = article {
"Lorem ipsum dolor sit amet."
}
.preference(ScriptDependencyKey.self, ScriptDependency(src: "/foo.js", async: true)) would, assuming it's not filtered out by default in a pre-processing-step, turn into <article swim-script-dependency="{src: ":foo.js":, async: ":true":}">
Lorem ipsum dolor sit amet.
</article> Not sure if purging these attributes by rewriting the notes is better than suppressing them in rendering. In #28 I played around with relaxing the requirements for attribute values to That said, I'm also open to saying we need a Sail library that adds a |
Yes, if we want to keep things separate we'd probably also need a separate result builder. Also not sure how it would work with things like the built-in HTML tags. What would be the type of the child nodes? |
I was thinking something like this: protocol Component {
@ComponentBuilder
var body: some Component
func render() -> Node
}
extension Component {
func render() -> Node {
body.render()
}
}
extension Node: Component {
@ComponentBuilder
var body: Node { self }
func render() -> Node {
self
}
}
struct TabBar: Component {
var tabs: [Tab]
@ComponentBuilder
var body: some Component {
ul {
tabs.map { tab in
li {
tab
}
}
}
// Hypothetical Component-modifier wrapper around adding a dependency
// using `DependencyPreferenceKey` that reduces by adding to a set.
.dependency(src: "/tabs.js", async: false)
}
}
struct Page<Content>: Component where Content: Component {
var content: Content
@ComponentBuilder
var body: some Component {
html {
head {
// Hypothetical wrapper around reading all dependencies using
// `DependencyPreferenceKey`.
content.dependencies.map { dependency in
script(src: dependency.src, async: dependency.async)
}
}
body {
content
}
}
}
} Might be missing something obvious that makes this unworkable tho? |
I think this could work! I'll also play around with this approach, hopefully I'll find some time tomorrow. |
I guess I handwaived the preferences, but I think they could work something like this? struct PreferenceWriter<Content>: Component where Content: Component {
var content: Content
var preferences: [String: AnyHashable]
@ComponentBuilder
var body: some Component {
content
}
func render() -> Node {
// Produces something like <sail-preference for="bar" /> if rendered by
// mistake but should usually be stripped.
//
// Putting prefixed attributes on `content.rendered()` would be nicer
// but it could be a `Node.text` or `Node.trim` where that wouldn't
// work.
PreferenceTag(preferences: preferences)
content.rendered()
}
}
extension Component {
var preferences: [String: AnyHashable] {
let rendered = render()
// Finds every `PreferenceTag`, merges preferences
let visitor = PreferenceVisitor(content: rendered)
return visitor.preferences
}
} This is a bit inefficient since we'd be visiting every |
I'm not sure if this is a good idea. But this is one way you could implement preferences. I would love to have this supported, but it also complicates everything (it's easy to add a feature like this, but hard to remove).
I'm also not sure if this is the best way to implement this, the visitor could also return an optional and then
readPreference
could do nil-coalescing.