-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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: renderer plugins #155
Conversation
renderers/preact/index.js
Outdated
jsxFactory: 'h', | ||
jsxFragmentFactory: 'Fragment', | ||
jsxImportSource: 'preact', | ||
validExtensions: ['.jsx', '.tsx'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One design choice in the Snowpack API was how to assign certain files to certain plugins. We decided on input: []
and output: []
extension arrays as a nicer utility.
Prior art includes webpack’s RegEx-based module loading, as well as Rollup’s resolveId example. We felt that RegEx can get unwieldy very quickly (especially when you’re doing things like negative lookbehinds), but in an ESM world where everything exposes its extensions, we just figured why not use that?
Of course, extensions make it harder when you’re looking for some other part of the module, or for example, you want .css
but not .module.css
. But we felt making the 95% of usecases simpler outweighed the edge cases (and when in doubt, you could always accept all files of an extension, and return undefined
for files you didn’t want to transform).
All that to say, if we want to swap this for a resolveId()
-like function, I‘d be game, so long as it stays synchronous because that’s very key for perf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your thoughts! Yeah this is the trickiest part to get right. Don't think I've nailed it yet.
I like the resolveId
idea but the specifier alone won't be enough to map a component to a single framework. I think we'll also have to expose the other import specifiers like react
or preact
.
renderers/preact/index.js
Outdated
jsxImportSource: 'preact', | ||
validExtensions: ['.jsx', '.tsx'], | ||
|
||
server: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this separation between server
/ client
! Should this be a function, though, that passes in certain params (I don’t know what exactly; just seems like renderer may need certain context like environment)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it needs to be a function. My thought is that renderToStaticMarkup
and hydrateStaticMarkup
are both passed the necessary context for rendering the component (or hydration script). The wrapper plugin would be instantiated only once, assuming it claims a certain file to render (resolveId
or extension
matching, etc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that’s fair. I just didn’t know if any of the static properties may be affected by environment or not.
renderers/preact/index.js
Outdated
export const vue = { | ||
jsxFactory: 'h', | ||
jsxImportSource: 'vue', | ||
validExtensions: ['.vue'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A really good thing to stress test this design would be Vue + JSX. There’s some wacky stuff going on there (and for an added challenge, try .tsx
-flavor of Vue). If you could SSR that, I bet you could SSR anything.
renderers/preact/index.js
Outdated
const { h } = lib; | ||
return async (Component, props, children) => { | ||
const html = renderToString(h(Component, props, children)) | ||
return { html } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another design decision that Snowpack made was having the output be file extensions, e.g.:
{
'.html': { code, map },
'.css': { code, map },
'.json': { code ,map },
}
That way it lets building work with binary files like images & assets as well as UTF8 files without limiting what’s possible. I think we could limit this to only HTML or HTML/CSS if we wanted to; just wanted to ask if there’s ever any chance we’d need another output from this like JSON?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh that's a good point. We only need html
and css
for the current set of frameworks. The output of this file is the server-side representation of this component in the DOM.
But Svelte might need head
? And I can imagine some future world where components have a way to declare other types of dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But Svelte might need
head
?
Ah good callout. Yeah that might be good to separate the two.
But if we need CSS, then we should probably bake sourcemap support into the return signature (I don’t think that exists for HTML, right?). Just to guilt us into doing it this time 😛
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm all for it! I like the signature you proposed.
renderers/preact/index.js
Outdated
jsxFragmentFactory: 'Fragment', | ||
jsxImportSource: 'react', | ||
validExtensions: ['.jsx', '.tsx'], | ||
transformChildrenTo: 'jsx', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does transformChildrenTo
do? And why doesn’t preact need it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understandable confusion as I haven't implemented it yet! But in order to support passing children
, react
and preact
need to convert their children (HTML strings) back to JSX. vue
and svelte
support HTML strings, so the other (default) option would be transformChildrenTo: 'string'
and we could skip that work.
1922f86
to
e2dcd5f
Compare
let prefix = ''; | ||
let args = [] | ||
if (clientImports.length === 1) { | ||
args[0] = '$a'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are these about? Can you add a comment explaining?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, just added some code comments!
import { childrenToJsx, dedent } from './utils'; | ||
|
||
export const createRendererPlugin = (plugin: any) => { | ||
const { jsxFactory, jsxImportSource, validExtensions, transformChildrenTo } = plugin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note that we should keep the fact that Astro compiles to h() functions an implementation detail. I realize that this part has to do with converting children into framework nodes and (probably?) doesn't leak the fact that Astro files themselves are h() functions. Let me know if I'm wrong here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is the children => framework vnode utility! Nothing to do with Astro's internal h
function. A new name like childrenToFrameworkVNode
might be more clear?
Looks good overall. Let's try to avoid making the interface JSX-centric. I think Also is there a reason why |
ec49df4
to
af2ccb8
Compare
|
@matthewp Great feedback, thanks!
I'm working on the Svelte renderer—it won't need to use JSX at all, so that will be a good test to see if this interface is too JSX-centric. Regarding the |
87105a1
to
2f05f7c
Compare
2f05f7c
to
02aca39
Compare
Closing in favor of #231 which is infinitely less complex! |
Todo
build
Changes
Follow up to #74, this PR is a WIP attempt at a possible renderer plugin interface. This approach is similar to the one we currently have but more robust. There's a ton of work left to get these integrated into the actual build process, but this feels like a good foundation.
Here is the current
preact
interface:For comparison, here is the proposed
svelte
interface which doesn't rely on JSX but does use a local wrapper to use a JSX-like function signature.Testing
Docs