VulcanJS / vulcan-next

The Next starter for GraphQL developers
http://vulcan-docs.vercel.app
MIT License
393 stars 29 forks source link

Toward a static Vulcan #3

Open eric-burel opened 4 years ago

eric-burel commented 4 years ago

Problem

Currently, Vulcan rely massively on the application startup process to dynamically generate features:

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.

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 https://github.com/VulcanJS/vulcan-next-starter/issues/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.

See https://github.com/VulcanJS/Vulcan/issues/2549

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:

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:

swyxio commented 4 years ago

interesting! cc @jlengstorf - another jamstackifying react framework

eric-burel commented 4 years ago

Strapi seems to be relating a lot on static file creation. Creating a new model will create a bunch of new files, representing the data ( a bit like our schema) but also the CRUD operation and model.

The GraphQL part use a "Shadow CRUD" feature that seems to behave like our schema: dynamically generating the resolvers and mutations, and only statically a schema.graphql file.

See plugins/graphql/services/Resolvers.js in a Strapi appi using graphql for more details.