Description
Describe the problem you want to solve or enhancement you want to bring
Ability to write full-stack "feature-oriented" packages with both server and client features, in NPM, as we are used to do in Meteor.
@vulcanjs/graphql
is a good example of this, as it generates the graphql backend, the client fragments, and both are very tied.
Define relevant concepts
Full-stack package: A package that export server-specific code and client-specific code. But it could apply to any package that expose different code depending on various environment (server, mobile, client).
Meteor packages are example of fullstack package with their client
and server
logic.
This is different from just having code that works everywhere, like a low-level library. We are talking about code that is actually specific to the env (calling window
on the client for instance).
We should be able to do something like import createServer from "@vulcanjs/graphql/server"
, import someHook from "@vulcanjs/graphql/client"
and import someSharedCode from "@vulcanjs/graphql"
Isomorphic import: Using a fullstack package transparently.
Example: a full-stack package could expose @vulcanjs/graphql/server
and @vulcanjs/graphql/client
. An isomorphic import would be import foobar from "@vulcanjs/graphql"
and let the the build system add /client
or /server
depending on the context.
Meteor is also doing that.
Limitation: the text editor can't know the environment where the code is run. So, for static typing, you have to explose a generic version. Meteor is subject to this limitation.
Blitz.js has something similar, but with "unbalanced isomorphism", limited to how they consume the server using a custom query
system. The typing is coming from the server, that contains the actual logic, as the client version is just a dull wrapper that does an HTTP call.
True isomorphism: you write a single piece of code and let the build system modify it depending on the execution environment. Next pages
fall into this category. getServerSideProps
and getStaticProps
functions exported in a page are server-only methods. The rest React (so client but also compatible with server-side rendering). At build time, those methods are removed from the client bundle.
This is something we would want to achieve for Vulcan model's:
const Foo = createModel({
schema: {
foobar: {
type: string; // both client and server, to render form, generate the gql schema etc.
resolver : () => {...} // field resolver, server-only
onCreate: () => {...} // server-only
}
},
queries: { ... },
mutations: { ... } // CRUD operations, server-only
})
In this example, the schema mixes shared elements, like the type for "Foo.foobar", and server only methods like the graphql resolver.
The power of Vulcan is to let you write only one schema with everything. The limitation is that you don't want the client bundle to contain server code, and sometimes vice-versa. You can bypass this with ugly require
or playing around with GraphQL context, but only so much.
Related
- RFC for Vulcan Package Format usable on any any app. It is focused on the actual content of the package, the what, (how we export graphql stuff etc.), while this RFC is focused on how we deliver such fullstack package, regarding build system, import tec. RFC: Vulcan Package Format vulcan-next#9
- Random thoughts about moving from Meteor to Next from back when we started the migration, with a few things related to packages: Demeteorizing Vulcan and how you can help vulcan-next#1
- Meteor application structure: https://guide.meteor.com/structure.html
- Next pages
- Blitz.js query system
- Blitz architecture discussion: Some future facing ideas for Blitz blitz-js/blitz#1141
Describe your solution
First step is implementing full-stack packages.
Then isomorphic import could be kept as a "magic" helper specific to Vulcan. But it is not mandatory (like if you create an Express server and don't use weback, you just do import foobar from "@vulcanjs/graphql/server"
as usual). This feature is more comfort for people used to Meteor.
Questions to the community:
For full-stack package: Multi entry Webpack export/TypeScript. It should not be too hard, but I've never done that before, there are a lot of moving pieces. So help welcome!
For isomorphic import: there is a demo package multi-env
that does exactly this. The way it works, it imports either index.server
or index.client
, using Webpack magic.
For the text editor autocompletion, or common code, we can also add an index
that exports both. That's a prototype so again help and feedback welcome.
A good example to get started is the graphql
package, that contains both server and client logic.
For true isomorphism: it's not easy at all to mix server and client stuff at the same place.
- is it even a good idea? MVC architecture would basically force you to explode your model in multiple pieces and bypass this issue.
- it could be easier to have multiple named exports with a convention, like Next does.
For example, infoobar.ts
:
export const schema = { foobar: {...} } // the common part
export const fieldResolvers = {foobar: {...}} // server-only. We have to repeat the field names
export const resolvers = {... } // server-only
// etc.
And then you create the full model using some build time magic. This is still a bit blurry, that's the part that require the most thinking.