Closed leebenson closed 6 years ago
How would you feel about using react-loadable as the higher order component for code-splitting? Using it's babel plugin it works well with with webpack, server side rendering, and ensuring that the react checksum matches on client side hydration. James Kyle has a gist to show how server side rendering would work with it, but I haven't yet seen any examples of it working yet.
Thanks @jtmthf, looks promising. Will play around with it and see if it fits the build flow.
One more thing I'd like to see, but I'm not sure if it fits within the scope of ReactQL, is SVG icon support.
I know there's already a loader for .svg
files, but you can't have the SVG match the color or size of text, or do mouse-overs, or other fancy effects with <img>
tags. Also, loading each SVG individually isn't as efficient as packing them into a spritesheet.
Something like svg-sprite-loader or external-sprite-loader would be good. However, the latter won't work on CDNs as far as I'm aware because of a stupid CORs issue the browser vendors refuse to fix, and the former doesn't support SVGs in CSS.
Alternatively, the icons could be packed into a webfont, which is still the better solution IMO. However, I haven't seen a loader that does this this yet.
Icons are pretty popular in web apps, so I think some sort of icon solution would be good.
@mnpenner, I'm toying with the idea of building a modular 'recipe' build flow for the starter kit, where it'd be possible to add in separate loaders out-the-box by either specifying options on the command line, or working through some kind of CLI wizard. This would make it possible to add in supported loaders like the SVG support you mentioned, but without need to anticipate every use-case and causing bloat for those that don't specifically need it.
Already, including SASS and LESS is probably an assumption too far for many dev, but included in every kit by default. I've started a 'cli' branch locally that would turn the basic git clone
instructions into an interactive reactql
CLI on Windows/Mac that would help spawn projects, run recipes against them, bundle, etc.
I imagine it something like Semantic UI's wizard (but without gulp):
Still working through it, but something like that is my goal.
@leebenson Hah... I started pretty much the exact same thing like a week or two ago. I'll make it public so you can take a look. It's not very far long along though. You're welcome to pillage anything you think is useful. In particular, I used prettier to clean up the source after I finish generating it, and I was planning on keeping a versions.js
file which maintains versions of the packages that I know are compatible with each-other, and it also means I don't have to query npmjs.com to get the versions.
My goal was to generate a webpack config file that's still human-readable and editable at the end so that you can tweak it as much as you need. I realized that there's far too many questions that you can ask a person, so it's probably better to leave some questions out, just ask the big ones, and they can modify it later.
Just a heads-up that the first version of the CLI has now been released with the 2.0.0 tag.
Install the ReactQL CLI on Mac/Windows/Linux:
npm i -g reactql
Then run reactql
for help, or reactql new
to start a new project.
This is an early first version, so if you run into any issues, please let me know and I'll get them looked at ASAP.
I've tested the kit on OS 10.12.4 and Windows 10, and it seems to working okay so far. I haven't tested on Linux yet. Any testing you could do yourselves would be greatly appreciated- and I'd be really interested in your feedback.
These are the main features:
reactql new
whenever you want to start a new project.reactql
to see a full list of commands and options.npm i -g reactql
to get the latest kit at any time (whenever you use the CLI, it'll check NPM for the latest version and alert you if a new one is available.)starter
folder in this repo. Everything to do with the cli goes in cli
. Simple.git init
in your new folder neatly.package.json
, based on your project name and description.yarn
, but drop back to npm
if it's not available..reactql
extension to a file inside starter/files
will run it through an EJS parser with the command line args before dumping a compiled version in the new starter kit, minus the .reactql extension. This is early work against starter kit options that will be presented in the wizard, for enabling SASS, LESS, ESlint, other loaders, etc.npm start
runs it.This is still early work, so I expect there might be some issues, and code may be a little messy in places (I wrote this over the weekend.) But I think it should be a pretty good start.
Known issues:
As mentioned, no dynamic options (yet). You get exactly the same kit as v1 for now.
Windows installation via NPM is pretty terrible. I clocked 116s to build on a relatively new/fast Windows machine, on a 200mbps connection. Yarn on a Mac, by contrast, did it in 39s flat. Bear in mind this is a full installation without a 'warm' cache.
I don't include a yarn.lock
file in the starter by default, because of the potential of making dynamic additions to package.json
depending on user options (coming soon). It's probably best still to have a base lock file, however, to give Yarn a starting point-- I'll likely do that in the next version.
Thinking about the templating engine some more (intended to allow the user to enable/disable various loaders, drop CSS support, etc), I'm considering separate repos instead.
There could be a common 'base' repo, which is bare-bones -- no CSS, no SASS/LESS, no ESLint, etc. just the bare minimum to get it run. Then a 'kitchen sink' version, which is every best-practice and configuration option, with possibly a couple of other flavours for experimental or community driven approaches.
This would allow devs to choose from a slim starter kit, or something more comprehensive for starting a new project.
If the code is laid out in a slightly more modular way, it may be possible to inherit from a base repo and then just layer on top. I need to think about that some more, but maintaining needn't be difficult if done properly. And it'd provide a lot more flexibility than template files with conditional statements to include/exclude lines of code.
In this model, the CLI would be relegated to the simple role of letting the user choose the theme, and then downloading/extracting the appropriate .zip
directly from github. The user would always be extracting the latest version, so it becomes somewhat evergreen, except for additional 'themes' being made available.
What do you think? Would you be interested in being able to choose from several 'flavours' of kit with additional features?
@leebenson Yep! That‘s a good way to go IMO. Nice job with the CLI!
@leebenson I like that approach better over templating. I used redux-cli about a year ago which uses ejs templates for code, but it was hard to work with because the templates weren't real javascript. I ended up writing a poorly maintained eslint plugin for ejs specifically for that project, but I'm not sure if it even works anymore.
Any ideas yet on how upgrading will work? Having the easy upgradeability of Next.js and create-react-app would be ideal, but being able to customize anything when needed is great too.
Related to customization, I was thinking about how Storybook currently handles this by providing both a simple config mode and a "full control" mode. For example if a developer needed to add a single loader, it would be nice if there was a configuration file separate from kit
that they could add it to and have it "just work". That way developers don't have to dig through a complicated webpack config and worry about future upgrade conflicts for simple config changes.
I prefer this starter kit specifically because I can customize anything. If you were to remove that ability in favour of upgradeability, I think that might be a deal-breaker. That said, upgradeability certainly is nice, just not in lieu of flexibility. To do this right, I think it needs to be very modular. Lots of small helper scripts/functions that can be subbed out as needed. The source for those can be tucked away so that they can be upgraded without conflicts.
As for the repo-splitting: I think it's OK, as long as the CLI tool downloads and manages those automatically. I don't want to be cloning 5 different projects to get all the functionality I need. That defeats the purpose of a starter kit. If I wanted that, I'd download and install webpack, React, GraphQL and whatever else myself.
I don't think I mind too much if the CLI tool itself gets fat because of all the functionality it supports. In the end, it spits out a ready-made project, and as long as you didn't pick too many options, that project would still be "light". So, whether or the CLI tool is one repo or many doesn't matter to me as a user, however, for the purposes of maintaining the tool, multiple projects may be easier as long as you can ensure that they don't become incompatible with each-other. Babel comes to mind -- they split that into many packages and it seems to be working for them, so maybe it's doable -- but it is a pain in the butt because every time I add or remove a plugin/preset from my .babelrc
I also have to npm install/uninstall it.
Last thought -- if I have to start using this reactql
CLI tool for everything instead of npm/yarn, you're also removing some transparency. What is this tool doing? Why do I have to use it to install a package? How do I do these things without this tool? You become dependant on it instead of it being a starter to get you going.
@leebenson I like that approach better over templating. I used redux-cli about a year ago which uses ejs templates for code, but it was hard to work with because the templates weren't real javascript.
That's the issue I ran into here. Mixing EJS (or any templating) with JS turns it into a weird hybrid that is tough to maintain, and will fail every linter. Makes editing/maintaining the kit less fun than I'd like.
Any ideas yet on how upgrading will work? Having the easy upgradeability of Next.js and create-react-app would be ideal, but being able to customize anything when needed is great too.
Thinking about it some more, I'm not sure upgrading is that big of a priority. With frameworks like Meteor, where the whole platform is hidden behind a CLI and the only code written is userland, there are clean lines between what is 'kit' and what is 'code'. Here, the starter kit and user code is tightly coupled, and this fusion will make it hard to do auto-upgrades without some very intelligent merge strategy to figure out what has changed. It's mirky territory to start digging into.
There are plenty of frameworks (Next.js, et al) that hide the build details from the user and upgrading simply means bumping the Next libs that are imported. My intention with this starter kit was instead of expose the whole lot so it could be configured by the user without any larger ecosystem buy-in. Upgrading kinda breaks with that.
Besides, in practice, upgrading generally won't be that hard. Just start a new project, copy over src
to the latest kit, and tweak whatever kit
changes were made between versions. At least it forces developers to understand what's happening under the hood and determine whether the changes make sense to them, IMO.
Related to customization, I was thinking about how Storybook currently handles this by providing both a simple config mode and a "full control" mode. For example if a developer needed to add a single loader, it would be nice if there was a configuration file separate from kit that they could add it to and have it "just work". That way developers don't have to dig through a complicated webpack config and worry about future upgrade conflicts for simple config changes.
I've used Storybook on multiple projects, and wrestled with their webpack config more than a time or two to get it to fit with the config of the underlying project I'm trying to test. I ended up writing code that attempted to 'extend' the config, and merge in changes via Object.assign
. It was very messy, and if Storybook added or changed a config option, it would often through up my brittle merge expectations.
I get the benefit of offering a framework, but I think sticking to the raison d'etre of ReactQL being a starter kit is probably best.
I prefer this starter kit specifically because I can customize anything. If you were to remove that ability in favour of upgradeability, I think that might be a deal-breaker. That said, upgradeability certainly is nice, just not in lieu of flexibility. To do this right, I think it needs to be very modular. Lots of small helper scripts/functions that can be subbed out as needed. The source for those can be tucked away so that they can be upgraded without conflicts.
Agreed. Per my comment above, I'm not sure upgrading will generally be that tough. In a starter kit like ReactQL, version bumps don't need to happen just because the new kit has a higher number than the one you're using. Knowing when to fork your code and start maintaining it outside of the kit is just as important.
As for the repo-splitting: I think it's OK, as long as the CLI tool downloads and manages those automatically. I don't want to be cloning 5 different projects to get all the functionality I need. That defeats the purpose of a starter kit. If I wanted that, I'd download and install webpack, React, GraphQL and whatever else myself.
That's the whole point of the CLI - to ask a bunch of questions, handle the download/building and leave you with code you can immediately edit. You'd download the CLI one-time, and it'd handle the rest for you. No need to faff with other repos or even now/care they exist.
Last thought -- if I have to start using this reactql CLI tool for everything instead of npm/yarn, you're also removing some transparency. What is this tool doing? Why do I have to use it to install a package? How do I do these things without this tool? You become dependant on it instead of it being a starter to get you going.
That's definitely not my intention. IMO, the CLI should be there one-time - right at the start of the project, to get the initial code. Then, the code is yours, and you're not 'locked' into the ReactQL ecosystem, but instead building with 'native' NPM commands and everything else that the libraries you're using expose directly.
This is in contrast to, say, the Meteor CLI, which provides a ton of ongoing functionality and the entire framework that you invoke every time you run your code. That approach works great for full frameworks, but is too big a buy-in for a starter kit, IMO.
I've just released 2.2.0 which relegates the CLI to a pure build tool. The starter kit code is now located at https://github.com/reactql/kit
Future issues should be posted above as needed. The CLI issue board will specifically be for the CLI tool only.
This is a precursor to allowing multiple 'flavours' of kit to be hosted and maintained separately, each with its own contributors/maintainers and issue boards. I don't necessarily want to encourage lots of different kits, but at a minimum I see two - the 'kitchen sink' version above, and perhaps a 'slim' version that removes the CSS, optional loaders and examples found in the fuller variant.
Right now, the CLI automatically downloads, extracts and builds the full version, with no other kits to choose from. Eventually, the 'slim' option will be added too, and this will appear during invocation of reactql new
where the user can select which variant of kit they wish to download.
I think this is a cleaner separation of concerns than bundling code along with each kit, and having lots of different EJS templates that include/drop lines of code depending on deeply nested if
statements. It should be far easier to maintain and work with.
The other nice thing is that you can pull the repo code directly if you wish to bypass the CLI altogether. The CLI is now simply a helper. Which also means that fixes to the underlying kit won't require bumping to a new CLI version... a user will get the latest version every time they invoke reactql new
.
Tested it on Mac and Windows 10 and appears to be working well.
@leebenson will the list of kits be hard-coded into CLI or will there be an option to pass in a URL to a third-party kit such as how create-react-app
has its --scripts-version
flag? Or perhaps both?
Hard-coded, at least for now. I don't have any plans to support third-party kits - I think that's mirky territory for ReactQL branding. Third-party kits are free to fork the CLI, though.
Okay reason why I ask is I have a TypeScript fork of the kit that some could find useful. Identical to the original except for the application code and the transpilation step of the build.
@jtmthf - if you're interested in maintaining an official Typescript version, I'd be happy to set-up a kit.ts repo and appoint you as a maintainer. Up for it?
@leebenson That'd be great! What I've been doing so far and plan to continue with, is to track kit and only make TypeScript specific modifications.
Cool, thanks @jtmthf. I've invited you to the repo @ https://github.com/reactql/kit.ts
Once that's up and running, I'll update the CLI to let devs choose between the JS or TS version.
FYI, you can now choose between Javascript (ES6) and Typescript flavours of the kit in the CLI, released in 2.3.0
A shoutout to @jtmthf for maintaining the TS version - thanks man!
Heads-up: Kit v1.3.0 was just released, which adds a development web server that restarts automatically on code changes -- making it easier to test CSS output and SSR on-the-fly, without manually re-bundling on the command line:
The next ReactQL CLI project you create will add this capability for you via the usual npm start
.
There's also a ton of other improvements, including neater Webpack messages, ESLint speed improvements and a fix for Atom, colour console messages that show local network bindings and ports in dev/production, <NotFound>
and <Redirect>
components for isomorphic 404/301/302 handling (some extra helper boilerplate in the server entry is coming soon) and the new npm run build-static
, that will generate a production browser bundle and index.html
, that's ready to upload a fully working app to a static host of your choice. There's also npm run static
that spawns a local server to test your static bundle, on port 5000.
See the release log for details.
CLI v2.4.0 has been released. It adds deterministic versioning of the kit; each CLI release is associated with one JS / Typescript kit pair, making it clear what you're installing and making it easy to install the same version of the kit elsewhere.
This way, the next time you run reactql new
, you won't get any surprises.
The update notifier will let you know when a new ReactQL kit is available. To upgrade the CLI to 2.4.0, simply re-install with npm i -g reactql
or yarn global add reactql
Great work @leebenson ! Thank you.
Create React App recently added "Progressive Web Apps". Do you have plans to add this feature as well?
Thanks @mnpenner. Definitely want to explore PWA. Tracking in https://github.com/reactql/cli/issues/40
Kit 1.4.0 has been released, offering Brotli compression via statically compressed .br
assets, and improved gzip compression via Zopfli.
Somewhat dependent on #40, but a hnpwa would be a great example project. We would need to build a server, as it doesn't look like graph.cool will handle this use case, but that's not too difficult. @leebenson or anyone else, let me know if you'd be interested in collabing on something like this.
@jtmthf - I've added you as a maintainer to https://github.com/reactql/examples, the new home for examples. It's empty (save for a README/good intentions); I'll be adding to it with some GraphQL server examples and other stuff soon.
Feel free to create any examples you see fit there.
The hnpwa app would be a great one to tackle, post-https://github.com/reactql/kit/issues/31
I've added the first example of running a GraphQL server alongside the existing web server - check it out at https://github.com/reactql/examples/tree/master/graphql-server
FYI, I've added an example that yanks out GraphQL, for times when you need to connect to a legacy REST endpoint instead, or just don't want/need GraphQL in your project.
See: https://github.com/reactql/examples/tree/master/no-graphql
This example also demonstrates the built-in Redux hooks, with a simple to-do component. A to-do gets added on the server, and sent down the wire for location rehydration in the browser. Shows how you can add custom reducers to kit/lib/redux.js
alongside/instead of Apollo.
@leebenson Hey Lee! Any plans for built-in support for serverless architecture like AWS Lambda? It seems like it could be a great scalable way to deploy a ReactQL app in production.
I've been trying to get ReactQL working with the super simple https://github.com/dougmoscrop/serverless-http package and had to manually edit the transpiled server.js file to module.exports the koa instance but I'm having trouble and getting ambiguous errors.
Basically we just need a file which can module.exports
the koa instance (without listening()
) wrapped in the serverless()
function.
@scf4 - that should be trivial to do. kit/entry/server.js already exports 'just' the app
(and a router
object you can attach to it) - it's kit/entry/server_prod.js (in production) that wires up the listener, logs back to the console, etc.
You'd basically just need to replicate that file, but with a call to serverless(app)
, and then copy kit/webpack/server_prod.js to something like serverless.js
but pointing to the above new 'entry', then create an npm script that uses the WEBPACK_CONFIG=serverless
env var.
I'm not sure about supporting it in core - if there's enough demand for it, sure, but otherwise it's another option to manage that may have a relatively niche audience. For now, I think it'd make a good project for https://github.com/reactql/examples -- feel free to submit a PR, thanks!
@leebenson thanks! I had to upgrade to the latest version to get the server_prod file, but unfortunately the whole boilerplate isn't useable on node 6 anymore (and AWS Lambda only supports LTS). I think the main culprit is koa static. Going forward will you only be supporting newer versions of node?
@scf4 - all ReactQL code gets transpiled based on the current environment, so generally all project code should work. NPM modules don't get touched (on the server), since they should (technically) be fit for consumption in pretty much any environment -- with transpilation handled by their own build scripts.
It looks like koa-send
is the culprit; it uses 'bare' async
and throws this in Node 6.x:
/Users/leebenson/dev/github/reactql/kit/node_modules/koa-send/index.js:37 async function send (ctx, path, opts = {}) { ^^^^^^^^ SyntaxError: Unexpected token function at createScript (vm.js:56:10) at Object.runInThisContext (vm.js:97:10) at Module._compile (module.js:542:28) at Object.Module._extensions..js (module.js:579:10) at Module.load (module.js:487:32) at tryModuleLoad (module.js:446:12) at Function.Module._load (module.js:438:3) at Module.require (module.js:497:17) at require (internal/module.js:20:19) at Object.
(/Users/leebenson/dev/github/reactql/kit/dist/server_dev.js:1428:18)
Looking at its package.json, they're specifically targeting Node 7.6 (i.e. the introduction of native async/await):
{
"engines": {
"node": ">= 7.6.0"
}
}
The simplest fix is probably to transpile everything (including NPM) with a babel-register
hook in the kit/entry/server.js
script, which should touch everything require()
'd in node_modules
, too.
I want to avoid doing that for core, because a) NPM really ought not to be the province of ReactQL project code; packages should be good citizens (although clearly, this doesn't always happen) and b) this is only an issue until October, when 8.0 hits LTS, and c) I want to encourage adoption (where possible) of 8.x for the speed and native async/await -- having a babel loader is a drag on the dev server, etc.
Based on your use case though, I should at least add a section to the docs that explains how to get it running in old versions.
Try babel-register
in kit/entry/server.js and let me know how you get on. You might need to also fiddle with .babelrc
in the root, or add a custom config to your babel-register require statement to get async working -- in that case, you can just copy and paste it from the webpack config.
The simplest fix is probably to transpile everything (including NPM) with a babel-register hook in the kit/entry/server.js script, which should touch everything require() in node_modules, too.
You shouldn't need to do that. If you just tweak one of the webpack configs to specifically include koa-send
it'll transpile just that node module. I've had to do this for broken libs before.
@mnpenner - yup, that'll work too (and it'll be faster). babel-register
is more of a 'catch all' in case there winds up being a bunch of other packages that need special treatment.
Kit 1.7.0 was released. It adds a Dockerfile
for building your ReactQL projects as Docker images, for front-end scalability via Kubernetes, Rancher, etc.
I'd like to gauge the community's opinion on integrating first-class support for Relay Modern. There's a thread at https://github.com/reactql/kit/issues/36 for discussing thoughts.
I'm particularly interested in:
Would be interested to hear your thoughts in https://github.com/reactql/kit/issues/36
CLI 3.0.0 was released, which temporarily removes Typescript support pending https://github.com/reactql/kit.ts/issues/18
It also bumps to kit 1.8.1, which introduced a pattern for adding custom Redux reducers in 1.8.0 to augment the built-in Apollo store.
Thanks for the advice @leebenson. I just got around to this again, but I can't get the server.js
to actually export anything as it's all wrapped up in the Webpack stuff. Any suggestions?
@scf4 - Are you looking at kit/entry/server.js?
It exports a default object containing router
(the default route handlers) and app
(the Koa instance).
In kit/entry/server_prod.js is where we actually wire up app
with the router
.
To work with AWS Lambda, you'd need to create something like serverless.js
that follows the same pattern- i.e. imports kit/entry/server.js as a base, and then configures it to work with Lambda.
Finally, you'd need to create a Webpack config that transpiles the serverless.js
entry point.
If Lambda only supports Node 6.11, you'll need to wait until it's upgraded to > 7.6 for native async/await
to land. Otherwise you can configure Webpack to include the Babel async to generator plugin and set it to transpile koa-send
along with any other plugin that uses async.
Just a heads-up that I'll be releasing kit v2.0 over the weekend.
The kit will introduce two significant improvements:
The graph.cool endpoint will be replaced with a sample GraphQL server that's built-in to the ReactQL koa web server. This will provide the advantage of demonstrating how to add your own GraphQL schema and respond to queries, in the common scenario where you're building a 'monolithic' web app on a single Node.js codebase. It will also make it easier for you to test against real GraphQL schema, and go beyond the immutable 'Hello from graph.cool' message that the kit currently offers. You'll be able to add your own GraphQL functionality via the next improvement...
The kit will get a new API, with the ability to change settings, add custom reducers, set the GraphQL endpoint, and even add your own Koa handlers, without ever touching kit/*
files. You'll be able to do this within src/app.js
, via Settings.addReducer()
and equivalent functions on a new global class.
This paves the way for super-simple upgrades -- just keep everything within src
and use kit API hooks, and you'll rarely need to delve under the hood.
The documentation will also get a refresh, to introduce the new settings API.
@leebenson thanks for the advice regarding lambda deployment again. It really turned out to be a bigger headache than I had time for, and the rendering was much slower than I'd hoped.
My advice for anyone running into hurdles with ReactQL and Lambda would be to save the time and look for another solution.
I'm now running on Heroku (as it's where my API was anyway) and the DX is infinitely better, especially with their CI.
Unfortunately rendering is still fairly slow as React SSR tends to be, but I'll be looking into something like RapScallion for my project.
Have you considered replacing the default rendertostring in this kit? IIRC rapscallion has a ~30% speed boost OTB.
@leebenson
in the common scenario where you're building a 'monolithic' web app on a single Node.js codebase
Will it be easy to separate the two? i.e., run Koa on one server and GraphQL on another?
without ever touching kit/* files
Sounding a bit like CRA now. Not that that's necessarily bad, but I thought the difference was that this was a starter kit and not a framework?
@scf4 First I've heard of Rapscallion. Sounds nice, but before jumping on that, keep in mind that React 16 is due out in probably the next month or two, and as I understand it, it supports streaming-rendering too. Not sure if that'll bring it up to the same speed or not though.
@mnpenner Oh nice, I wasn't aware of the SSR improvements in 16. I'll be upgrading this weekend so it'll be interesting to compare the SSR performance.
If it has comparable performance then perhaps rapscallion wouldn't be worth potentially complicating things for a general starter kit, but I'll likely be using it for my app anyway since it has other features like render caching at the component level.
Since we're on the topic, perhaps it'd be worth releasing v2 with React 16?
It's fairly close to an official release, but Facebook already considers it stable enough to use in production and has been using it for some time.
@mnpenner
Will it be easy to separate the two? i.e., run Koa on one server and GraphQL on another?
Yep, you'll be able to turn GraphQL on in the built-in Koa server if you wish, or avoid it completely and simply point to an external endpoint that you run off-site.
The API will be quite flexible, without changing the overall layout of a project.
Sounding a bit like CRA now. Not that that's necessarily bad, but I thought the difference was that this was a starter kit and not a framework?
It's still very much a starter kit. I'm simply making the lines between 'kit' and 'app' slightly less blurry, so when a bug fix or another feature is released in a future kit, less surgery is required to inherit the upgrades.
Since we're on the topic, perhaps it'd be worth releasing v2 with React 16?
There's an open PR. I'll do some local testing over the weekend and if I don't run into anything major, will bump to v16. 👍
Thanks @mnpenner. I've managed to get the streaming API working on the server. Koa natively handles streams, so it was just a case of figuring out the new streaming API; the documentation shown at the link is incorrect (I wound up trawling through source and discovering it was actually just a case of swapping renderToString
to renderToStream
on the same import.)
So far, v16 is performing perfectly with out-the-box ReactQL components, in all modes. A little too well, actually - I was expecting something would balk, but inline hot reloading, streaming, re-hydration of server generated HTML... all seem to be holding up well.
Maybe v16 is ready 😉 . I'll throw some more at it over the weekend and let you know what happens.
Performance looks to be significantly improved in kit v2.0 with React v16:
vs. React 15:
In case you were curious how quickly the ReactQL stack parses the initial request, creates a store, and returns the React render stream:
Speed FTW.
@leebenson awesome! 3x render speeding is amazing.
Would you be able to quickly share a snippet of how you hooked up the streaming API to Koa? I'm launching something with this kit and 16 (or rapscallion if I run into troubles) this weekend and it'd save me some time :)
@scf4 - sure, here's what I did:
First, upgrade react
and react-dom
to ^16.0.0-beta.3 in your project.
Then edit:
kit/entry/server.js
Add this import:
/* Node */
// For pre-pending a `<!DOCTYPE html>` stream to the server response
import { PassThrough } from 'stream';
Then modify the createReactHandler
func to this:
export function createReactHandler(css = [], scripts = [], chunkManifest = {}) {
return async function reactHandler(ctx) {
const routeContext = {};
// Create a new server Apollo client for this request
const client = serverClient();
// Create a new Redux store for this request
const store = createNewStore(client);
// Generate the HTML from our React tree. We're wrapping the result
// in `react-router`'s <StaticRouter> which will pull out URL info and
// store it in our empty `route` object
const components = (
<StaticRouter location={ctx.request.url} context={routeContext}>
<ApolloProvider store={store} client={client}>
<App />
</ApolloProvider>
</StaticRouter>
);
// Wait for GraphQL data to be available in our initial render,
// before dumping HTML back to the client
await getDataFromTree(components);
// Handle redirects
if ([301, 302].includes(routeContext.status)) {
// 301 = permanent redirect, 302 = temporary
ctx.status = routeContext.status;
// Issue the new `Location:` header
ctx.redirect(routeContext.url);
// Return early -- no need to set a response body
return;
}
// Handle 404 Not Found
if (routeContext.status === 404) {
// By default, just set the status code to 404. You can add your
// own custom logic here, if you want to redirect to a permanent
// 404 route or set a different response on `ctx.body`
ctx.status = routeContext.status;
}
// Create a HTML stream, to send back to the browser
const htmlStream = new PassThrough();
// Prefix the doctype, so the browser knows to expect HTML5
htmlStream.write('<!DOCTYPE html>');
// Create a stream of the React render. We'll pass in the
// Helmet component to generate the <head> tag, as well as our Redux
// store state so that the browser can continue from the server
const reactStream = ReactDOMServer.renderToStream(
<Html
head={Helmet.rewind()}
window={{
webpackManifest: chunkManifest,
__STATE__: store.getState(),
}}
css={css}
scripts={scripts}>
{components}
</Html>,
);
// Pipe the React stream to the HTML output
reactStream.pipe(htmlStream);
// Set the return type to `text/html`, and stream the response back to
// the client
ctx.type = 'text/html';
ctx.body = htmlStream;
};
}
I made one slight adjustment to the <Html>
component. Instead of accepting a rendered HTML string (and therefore foregoing streaming) and dangerously setting the inner HTML, it'll take components
as a child prop and render as part of the final stream:
kit/views/ssr.js
// Modify `Html` to the following
const Html = ({ head, scripts, window, css, children }) => (
<html lang="en" prefix="og: http://ogp.me/ns#">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta httpEquiv="Content-Language" content="en" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{head.meta.toComponent()}
<link rel="stylesheet" href={css} />
{head.title.toComponent()}
</head>
<body>
<div id="main">{children}</div>
<script
dangerouslySetInnerHTML={{
__html: Object.keys(window).reduce(
(out, key) => out += `window.${key}=${JSON.stringify(window[key])};`,
''),
}} />
{scripts.map(src => <script key={src} src={src} />)}
</body>
</html>
);
Html.propTypes = {
head: PropTypes.object.isRequired,
window: PropTypes.object.isRequired,
scripts: PropTypes.arrayOf(PropTypes.string).isRequired,
css: PropTypes.string.isRequired,
children: PropTypes.element.isRequired,
};
Finally, in the browser entry point, change doRender()
to use the new ReactDOM.rehydrate()
method (since render
has been deprecated with SSR):
kit/entry/browser.js
function doRender() {
ReactDOM.hydrate(
<Root />,
document.getElementById('main'),
);
}
That should get you going.
A nice side-effect to v16 btw, is that the previous data-reactid
attributes have disappeared, so the rendered HTML is much cleaner/leaner:
General roadmap for upcoming ReactQL features, and a tentative order they'll be approached.
Feel free to suggest any features you'd like to see in this thread, and they'll be considered for inclusion.
Note: The raison d'être of ReactQL is to remain a lightweight, general purpose starter kit for building GraphQL-backed front-ends. Niche features should generally be built on top of the kit, rather than wind up in core. But all sensible use-cases and suggestions will be considered.
Soon:
[x] ReactQL CLI, installed via
npm i -g
. Easily spawn a new project withreactql new
instead of manual git clones and tidy-up. Will make it easier to version the kit and distribute the start-up code offline.[x] Yarn building. Should speed up equivalent npm installs of modules.
[x] Better SASS/LESS imports. The loader config needs tweaking to standardise import styles between different CSS loaders. Could also do with a better strategy for importing third-party classes globally or locally, for better framework support -- Semantic, Bootstrap 4, etc.
[x] Typescript option.
[x] Development web server, that restarts automatically on code changes, alongside the existing hot-code reload Webpack dev server.
[x] Deterministic versioning- have one kit version per CLI installation, which can alert when there's an update available, so there are no surprises when creating a new project and finding that the kit has changed.
[ ] Helper component for code splitting and loading screens a la #3, along with updated docs.
[ ] Video guides & articles in docs for common use cases and code tutorials on how to use GraphQL in practice like how to build a GraphQL server on top of the kit, etc.
[x] ~i18n / l10n internationalisation / localisation support~ (After some testing, it's clear that i18n is far too opinionated to suit every use case-- libs, translation loading strategies, etc will differ widely per project. I'll save this one for a future 'examples' kit)
Future / tentative
[ ] Progressive web app features (#40)
[ ] Mobile/desktop framework support, for creating Cordova/Phonegap/Electron apps.
Example kits
[ ] i18n / internationalisation. (Originally, I wanted this in core, but there are far too many ways this could be done. An example starter is probably best.)
[ ] GraphQL subscription examples and patterns. Possible web socket proxy via the built-in server? (needs more thought)