VulcanJS / vulcan-npm

The full-stack JavaScript App Framework
https://vulcan-docs.vercel.app
MIT License
31 stars 8 forks source link

RFC: Fullstack and isomorphic package (work in progress) #14

Open eric-burel opened 4 years ago

eric-burel commented 4 years ago

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

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.

eric-burel commented 3 years ago

Next.js has it's own tree shaking function for removing SSR code from the client bundle: https://github.com/vercel/next.js/pull/27552 We could do something similar to remove resolver field from schemas client-side.