Open renchap opened 7 years ago
I'm gonna start working on this soon. I think to load the pack files we should stick to some convention (probably configurable) like react-rails' components.js does, so we know where can find the components.
You can have multiple components registered in one packfile. The logic is to have separate packs for different parts of your website, but you may have multiple root components in it, especially if not using an SPA.
Exactly, that file (components.js
) loads all the components you'll be using for sever-side rendering and their dependencies. I think that makes sense and simplifies the logic of trying to locate the right packfile. And in practice you don't necessarily have to include it in your views, you can include there the separate packs you mention.
Seems good. Maybe use a more expressive default name, like app/javascripts/components/serverRendering.js
?
Also this should be a configuration option, and allow an array of files to be loaded.
I'd try to challenge the whole server-side rendering idea later :)
Hi guys, any news about server side rendering?
I hate have mixed feelings with the idea of server side rendering:
it kills the purpose of SPAs - didn't we want to have single page apps because we wanted to take the load off the server and move to the client/browser to allow scalabilityTM? And now what? Again moving back rendering to server. It seems like a roundtrip for me. Let's just use Turbolinks and call the SPA experiment a failure ;)
it surely adds some edge-cases (not everything can be handled outside of the browser, some libs?).
@sevos for our company server side rendering is not a choice: it's mandatory. We don't have a full SPA but many simple widgets that for SEO and UX reasons need to be already present when the browser/bot loads the page.
It can of course add some edge cases due to many different libraries available, but IMHO is up you to choose one that does not need the browser env or to implement a different approach when the server side rendering is running...
What do you think?
@risinglf @sevos @daninfpj Why would prefer to put server rendering in this library compared to using React on Rails?
@justin808: The whole point of this library (not just server rendering) is to take advantage of Rails 5.1 (currently on rc2) first-class support for Webpack and React. This allows for a cleaner and simpler integration in my opinion.
@daninfpj wrote:
@justin808: The whole point of this library (not just server rendering) is to take advantage of Rails 5.1 (currently on rc2) first-class support for Webpack and React. This allows for a cleaner and simpler integration in my opinion.
We're almost done with that.
See https://github.com/shakacode/react_on_rails/pull/822 and https://github.com/shakacode/react_on_rails/pull/811.
and see:
React on Rails 8.0.0 shipped with support for webpacker_lite. I think this has the server rendering support you desire.
Hi guys, how is this feature progress?
Is there anything we can do to help?
I have not got time to really work on server-rendering. If you would like to have a stab at it, feel free! I outlined my ideas in this issue and I am available to discuss it further. React 16 changes server-side rendering and hydrating, and I think it would be great to support it.
I am not opposed to a minimal and non-modular approach at first, supporting only mini_racer
(via ExecJS
?) to keep things simple.
I've got a minimal approach working already if that is of interest. I'm using webpacker-react
for the client side stuff but the server side is independent. It's pretty heavily influenced by react-rails
as I thought their implementation was pretty clean.
I took the single point of entry approach with one "server" pack that imports all the various components. Right now the issue I'm working through is due to the way webpacker 3 enables the style-loader
when HMR is enabled, you can't server side render and use HMR at the same time.
Nice! This seems like a good start. There is a related issue in the Webpacker repo: https://github.com/rails/webpacker/issues/842
What is the issue with style-loader
and server-rendering?
Ya, I've read through that issue. I actually don't need to turn off inline mode like is specified there. react-rails
removes the client require that inline mode inserts. Simply adding a var self = self || this
fixes the other error.
I'm messing with a server-only webpack config to try and figure out a clean way of having HMR and server side rendering. I'm not a huge fan of that thread's suggestion of maintaining a per-file list. IMO there should be another point of entry for server-side packs that the second config handles.
Another issue at play here is whether or not the client will then mount the same component on top of the server side render. This may not be wanted if you are basically statically rendering React components out, but if you are trying to bootstrap a SPA you probably want to do that. One thing at a time though π
What is the issue with style-loader and server-rendering?
style-loader
basically wants a DOM to write <style></style>
out to. It is actually recommended to use extract-text-plugin
for server side, but we don't have control over webpacker enabling that right now. If you turn off HMR, webpacker disables the style-loader
so everything works fine.
Well, server rendering should not worry about styles at all and not output anything related (except for css modules). Hydrating a server-rendered component is a mandatory feature, I dont see a usecase for server-side only React (just use Ruby!).
React 16 changed quite a lot of things related to SsRβ I foudn this article useful: https://medium.com/@aickin/whats-new-with-server-side-rendering-in-react-16-9b0d78585d67 I dont know if it may help you.
Can you publish your work in progress somewhere so I can have a look and we can discuss over real code? This will be easier π
Agree to disagree on that one. We use styled-components
pretty extensively. As they ship all styles via JS and subsequently write a style tag to the DOM on component tree render, you have to have a way of pulling the styles out of the render tree on the server. Luckily, they added first party support for server side rendering in v2, so that works pretty nicely.
Also, not everyone's whole stack is Ruby. We've got SPA, express, and Rails apps in production. We have a React-based component library that is shared between them. Components like our footer take an initial set of props and are rendered out. Having that statically rendered is a valid use case for us.
The issue isn't even about the appropriateness of CSS on the server. When the style-loader is enabled webpack emits additional code that causes execJS to choke. However, you need the style-loader for HMR. So, you either get HMR or server side rendering.
Sure, I can put stuff up in a gist
Ok I see how it works. Let me correct what I said by "server-rendering should ignore style-loader
and other CSS-related webpack loaders :) For production your stylesheets will be compiled in CSS files by extract-text-plugin
, and in dev your styles are inserted into a <style>
tag by style-loader
.
I guess we will need hooks to allow styled-components
(and other similar projects) to work with webpacker-react server-rendering, as it it a library-specific feature.
The first and simplest goal of server-side rendering is to be able to call renderToString()
on your component on the server and output the result into your Rails view, and then to call ReactDOM.hydrate
when mounting the component client-side.
Here's a gist of what we are using right now. Like I said, this is a pretty simple implementation. I'm still wondering if a server-only webpack config is a better approach. It would make messing around with CommonsChunk
and such easier and would enable hmr to work alongside.
https://gist.github.com/wingrunr21/b2e2a1aca3083eb877a6deae9dedbd89
@renchap @wingrunr21 I am about to tackle this in my app. Is there any progress on this or should I pickup from the gist @wingrunr21 posted?
@tomasc I won't be submitting a PR to this repo for server side render support. As we continued to iterate on our solution, it became obvious that a more standalone solution was the best fit. We are prepping to open source a generic SSR solution for webpacker (which will still have full support for webpacker-react
).
@wingrunr21 that sounds good. If interested, I can help test your project as I am about to start dealing with SSR and solution with bare-bones webpacker
would work best in my case.
@tomasc I havent got time to work on this yet. Feel free to tackle it if you want! The preferred way is to open a PR as soon as you have a WIP, so we can discuss about it while it progresses.
@wingrunr21 I am curious about how you want to tackle it at the Webpacker level. Can you outline how it would work?
@wingrunr21 Yep that sounds interesting. Just commenting so I watch this thread (spying from react-rails π )
@BookOfGreg honestly, your SSR support formed a really solid basis for the work. It definitely gave us a good starting point on a solution that was known to work. Your project is also a reason we wanted to target a more generic webpacker solution. It's fairly difficult to parse out how to implement SSR in an express application without using something like create-react-app
or next.js
. We didn't think another implementation of SSR (with react-rails
and react_on_rails
both having implementations) would benefit from being specific to a given project.
@renchap Sure. We wanted this to work as closely as possible to how "real" SSR is done in a node environment. As I outlined before, our onus around this is because our React codebases are used across various environments. In addition, the vast majority of SSR testing by upstream users is done against a node environment. Emulating how those setups work seemed to be the optimal approach.
ExtractTextPlugin
configuration is not exported at all when the dev server is running and HMR is enabled)webpacker-react
's auto mountingStaticRouter
from react-router
or dealing with redux
stores. The good part is that sticking fairly close to how node does things means these use cases can be supported in much the same way@wingrunr21 is there any updated on this please? I am eager to test or help out.
@tomasc sorry, our November/December ended up being crazy with other work. I'm working on it this weekend and hope to have some good stuff come next week.
@wingrunr21 thanks, that would be fantastic β and right on time ;-)
@wingrunr21 π€ that you still working on this, do you have ETA in mind?
Code is up: https://github.com/GuildEducationInc/webpacker_ssr, https://github.com/GuildEducationInc/webpacker_ssr-execjs, https://github.com/GuildEducationInc/webpacker_ssr-react
Need to write docs still (hoping today or tomorrow).
We just deployed the changes into production yesterday.
@wingrunr21 I am trying to test, but have hard time setting it up. Can you please share a few steps required to get this running with React? Thanks!
Here my findings:
# config/initializers/webpacker_ssr.rb
require 'webpacker_ssr'
require 'webpacker_ssr/execjs'
require 'webpacker_ssr/react'
WebpackerSSR.configure do |config|
config.server_bundle = 'server_bundle.js'
config.server_manifest = 'manifest.json'
config.renderer = :execjs
end
When assets are precompiled (bin/webpack
) I get Cannot read property 'createElement' of undefined
β seems React is not available, even though I have it imported on top of the server_bundle.js
:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
When using bin/webpack-dev-server
, I am slo getting error of calling .protocol
of undefined. I guess that comes from the JS that webpack dev server injects into the bundle.
Also when using the CommonsChunkPlugin
to isolate all vendor libs I get following error: ExecJS::ProgramError: ReferenceError: webpackJsonp is not defined
. I tried to make new WebpackerSSR::React::Plugin
that would include the manifest.json
file, but with no luck:
module WebpackerSSR
class VendorBundle < ServerBundle
def self.read(file_name)
manifest = ServerManifest.new
asset_path = manifest.lookup(file_name).to_s
Webpacker.dev_server.running? ? load_from_dev_server(asset_path) : load_from_file(asset_path)
end
end
end
module WebpackerSSR
module React
module Plugins
class Vendor < Plugin
self.priority = -1
def set_up_js_variables(_input)
VendorBundle.read('manifest.js')
end
end
end
end
end
WebpackerSSR::React.register_plugin(:vendor, WebpackerSSR::React::Plugins::Vendor)
WebpackerSSR::React.config.default_plugins = %i[vendor react]
Help would be appreciated.
Hi all,
Really sorry. We are in the middle of multiple large client launches right now and my time is being monopolized in support of those.
In the mean time:
Gemfile:
gem 'webpacker', '~> 3.2.0'
gem 'webpacker-react', '~> 0.3.1'
gem 'webpacker_ssr', '~> 1.0.0.alpha.1', github: 'GuildEducationInc/webpacker_ssr'
gem 'webpacker_ssr-execjs', '~> 1.0.0.alpha.1', github: 'GuildEducationInc/webpacker_ssr-execjs'
gem 'webpacker_ssr-react', '~> 1.0.0.alpha.1', github: 'GuildEducationInc/webpacker_ssr-react'
config/initializers/webpacker_ssr.rb
WebpackerSSR.configure do |config|
config.renderer = :execjs
config.server_bundle = 'server_side_render'
config.server_manifest = 'server-manifest.json'
config.react.default_plugins = [:react]
end
config/webpack/server.js
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const ManifestPlugin = require('webpack-manifest-plugin')
const { Environment } = require('@rails/webpacker')
// Webpacker screws around with the exports so this needs to be redefined
const postcssConfigPath = path.resolve(process.cwd(), '.postcssrc.yml')
const isProduction = process.env.NODE_ENV === 'production'
const extractOptions = {
fallback: 'style-loader',
use: [
{ loader: 'css-loader', options: { minimize: isProduction } },
{ loader: 'postcss-loader', options: { sourceMap: true, config: { path: postcssConfigPath } } },
'resolve-url-loader',
{ loader: 'sass-loader', options: { sourceMap: true } }
]
}
const extractCSSLoader = {
test: /\.(scss|sass|css)$/i,
use: ExtractTextPlugin.extract(extractOptions)
}
class ServerEnvironment extends Environment {
constructor() {
super()
// Fix so HMR can be used at the same time
this.loaders.set('style', extractCSSLoader)
// Override server manifest
const manifestPlugin = this.plugins.get('Manifest')
manifestPlugin.opts.fileName = 'server-manifest.json'
this.plugins.set('Manifest', manifestPlugin)
}
toWebpackConfig() {
const result = super.toWebpackConfig()
const serverSideEntry = result.entry['server_side_render']
result.devtool = undefined
result.output.libraryTarget = 'this'
result.entry = () => {
return {'server_side_render': serverSideEntry }
}
return result
}
}
const environment = new ServerEnvironment()
module.exports = environment
config/webpack/development.js
const environment = require('./environment')
const serverEnvironment = require('./server')
const config = environment.toWebpackConfig()
delete config.entry['server_side_render']
const serverConfig = serverEnvironment.toWebpackConfig()
module.exports = [config, serverConfig]
config/webpack/production.js
const environment = require('./environment')
const serverEnvironment = require('./server')
const config = environment.toWebpackConfig()
config.devtool = 'hidden-source-map'
const serverConfig = serverEnvironment.toWebpackConfig()
module.exports = [config, serverConfig]
We are running against webpacker 3.2.0 (apparently there are some issues with 3.2.2).
Thanks, @wingrunr21, that's very helpful, I will give it a try!
PS got it to work with no issues on webpacker 3.2.2 (although I do not use styled components).
@wingrunr21 would be helpful if the three projects could be released as gems already βΒ even if as alpha.
kk. I want to get some tests in place first. Crossing my fingers that I can use some weekend time on these projects.
Thanks. Once we get the base set up I can help with refining the edges.
Hey all,
So sorry. I have not forgotten about these projects. I'm blocking time this Friday to write documentation + make some additional improvements to the gems. Startup life...
@wingrunr21 I know what you mean, barely have any time to work on react-rails anymore, moved company to a startup and there's no sponsored time anymore. Honestly good luck with this effort.
@wingrunr21 thanks β let me know if I can help anyhow. I have been running the webpacker-ssr
group of gems in production for a while now and it works very fine so far.
To anyone having issues, it took me a while to realize i needed to have a pack named server_side_render.js
that looks something like this...
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'
import HelloReact from 'components/hello_react'
execJsGlobal.React = React
execJsGlobal.ReactDOMServer = ReactDOMServer
execJsGlobal.ServerStyleSheet = ServerStyleSheet
execJsGlobal.HelloReact = HelloReact
You must also use react_server_component
instead of the usual react_component
helper.
Fantastic work!
Using ExecJS, we should be able (optionally) to run React on the server, render the content, and then re-hydrate the components in the browser.
The steps for this would be something like: