-
Notifications
You must be signed in to change notification settings - Fork 30
Description
Problem
Currently, Vulcan rely massively on the application startup process to dynamically generate features:
- the graphQL schema
- the Components registry, with replacement support
- the routes
- the graphQL fragments
- the Mongo collections
This approach was a great innovation when Meteor was just a young framework.
But nowadays, web development framework are moving away from dynamic generation, and prefer richer build step.
- Server side, this is necessary to reduce the startup time of lambdas in serverless architecture
- Client side, this is necessary to dump the startup step altogether, in order to fasten rendering
- It's a game changer for 3rd party tools, that can import built code easily (GraphQL schema static analysis, Storybook...).
Vision
For Vulcan, that means replacing all registerComponent, addRoute, registerFragment and the associated startup calls to populate various objects (the router, Components, Collections...) by build-step patterns whenever possible.
We suppose here that the code is structured in packages. The package syntax will probably be specific to Vulcan, but should rely on Next incoming plugin system.
An export standard
See #9
Relevant patterns to combine static build but allow modifications at the developer level
To be completed, multiple patterns can coexist.
Copy paste (appliable to Mongoose schema, Graphql schema)
Allow user to copy autogenerated files into his code for modification (eg for the graphql schema)
Examples
Copy .vulcan/graphqlSchema.graphql to src/lib/graphqlSchema
Generate routes directly in src/pages
Limit
Then how do you update the auto-generated part? How do you detect that users applied modification to the pages?
Mix (for replaceable Components)
Mixing autogenerated components and user override in a single file at build time?
The issue is with nested components.
Example
In Vulcan, we define default components:
// in vulcan code
// some non replaceable component
const Footer = ({children}) => <footer>{children}</footer>
// replaceable components (exported)
export const DatatableFooter = ({children}) => (<Footer>{children}</Footer>)
export const DatatableContent = ({children}) => (<main>{children}<DatatableFooter /></main>)
export const Datatable = ({children}) => (<DatatableFooter>{children}</Footer>In users code, we define some overrided versions, but only for some components. We keep the default for other components:
// in user override
import { DatatableFooter } from `vulcan/ui`
// some non exported code
const StyledDiv = (props) => <div {...props} style={{backgroundColor: "red"}} />
export const DatatableContent = ({children}) => (<StyledDiv>MY CUSTOM CONTENT {children}<DatatableFooter /></StyledDiv>)It will be mixed at build time into:
// FINAL BUILT FILE
// We first have code necessary to components
// copy pasted as-is from Vulcan
const Footer = ({children}) => <footer>{children}</footer>
// copy pasted as-is from user override
const StyledDiv = (props) => <div {...props} style={{backgroundColor: "red"}} />
// Then components
// from vulcan
export const DatatableFooter = ({children}) => (<Footer>{children}</Footer>)
// nested component that has been overriden
export const DatatableContent = ({children}) => (<div>MY CUSTOM CONTENT {children}<DatatableFooter /></div>)
// from vulcan again
export const Datatable = ({children}) => (<DatatableFooter>{children}</Footer>Limit
How do you even code that?
How to handle imports in the user code?
How to correctly type props?
How do you handle all additional code, for example 3rd party package used to implement some component? Eg package imported at the top level for each component? The risk of name clashes? Will we need to define only one file per component in Vulcan, so that we explicit the dependency to some additional code on a per-component basis?
Others?
Per feature
Routes
Build step
This means copying package "pages" into the "pages" folder of the Next application.
User overrides
??
Updates in case of user overrides
??
Collections
Build step
This means translating Vulcan schemas into Mongoose schemas.
User overrides
??
Updates in case of user overrides
??
GraphQL schema
Build step
This means creating a .graphql file.
User overrides
???
User extension
We can probably stitch schemas. Use should be able to enjoy text editor features, so writing directly into .graphql files or gql tags.
Relevant tools:
Query and mutation resolvers
??
Fragments
Build step
This means creating multiple .graphql file.
User overrides
We can expect user to create its own fragment, independant from Vulcan default fragments. So no need for overrides.
Mutations and resolvers
We need both the GraphQL part and the JS implementation. For the GraphQL part the process is the same as for any part of a grapqhl schema.
For the function we can simply export and merge into the global schema.
Components
Here the tricky part is replacement. See this issue
i18n files
They are currently a pain point because they may bloat exports. Relying directly on an i18n JSON file could work.
Open questions:
- Where to we put the generated code? In a
.vulcanfolder similar to.nextand.meteor? - How user can extend this code ?
- How user can override certain parts of the code ? Eg modifying a generated page, a generated graphql schema ? A component ?
- How do we update the auto-generated part when the user has applied overrides ? For example in the graphql schema?
- When static analysis is needed (eg to mix Component overrides), where to apply it? To TypeScript code? To built babel code? It probably needs some AST feature?
- How to handle build step in the dev process? Rebuild on file change?
- Easy creation of Storybook addon (eg to add i18n to preview for instance)