Skip to content

Instantly share code, notes, and snippets.

@wingrunr21
Created October 2, 2017 19:43
Show Gist options
  • Save wingrunr21/b2e2a1aca3083eb877a6deae9dedbd89 to your computer and use it in GitHub Desktop.
Save wingrunr21/b2e2a1aca3083eb877a6deae9dedbd89 to your computer and use it in GitHub Desktop.
Simple webpacker server side rendering
const webpack = require('webpack')
const { environment } = require('@rails/webpacker')
// Don't use commons chunk for server_side_render chunk
const entries = environment.toWebpackConfig().entry
const commonsChunkEligible = Object.keys(entries).filter(name => name !== 'server_side_render')
environment.plugins.set('CommonsChunkVendor', new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: (module, count) => {
// this assumes your vendor imports exist in the node_modules directory
return module.context && module.context.indexOf('node_modules') !== -1;
},
chunks: commonsChunkEligible
}))
environment.plugins.set('CommonsChunkManifest', new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}))
module.exports = environment
# this is heavily influenced by react-rails:
# https://github.com/reactjs/react-rails/blob/master/lib/react/server_rendering/exec_js_renderer.rb
class ExecJsRenderer
JS_TEMPLATE = <<~JS
var execJsGlobal = {};
var self = self || this;
var window = window || this;
JS
def initialize(js_code)
@context = ExecJS.compile(JS_TEMPLATE + js_code)
end
def render(component_name, props)
js_code = <<~JS
(function() {
var component = execJsGlobal["#{component_name}"];
var sheet = new execJsGlobal.ServerStyleSheet();
var element = execJsGlobal.React.createElement(component, #{props.to_json});
var html = execJsGlobal.ReactDOMServer.renderToString(sheet.collectStyles(element));
var styles = sheet.getStyleTags();
return html + styles;
})()
JS
@context.eval(js_code).html_safe
end
private
def compose_js(js)
<<~JS
(function() {
var result = #{js};
return result;
})()
JS
end
end
require 'exec_js_renderer'
module ReactServerHelper
def react_server_component(name, props = {})
server_pack = WebpackerManifestContainer.find_asset('server_side_render.js')
manifest_pack = WebpackerManifestContainer.find_asset('manifest.js')
renderer = ExecJsRenderer.new "#{manifest_pack}\n\n#{server_pack}"
renderer.render name, props
end
# from https://github.com/reactjs/react-rails/blob/master/lib/react/server_rendering/webpacker_manifest_container.rb
class WebpackerManifestContainer
# This pattern matches the code that initializes the dev-server client.
CLIENT_REQUIRE = %r{__webpack_require__\(.*webpack-dev-server\/client\/index\.js.*\n}
class << self
def find_asset(logical_path)
asset_path = Webpacker.manifest.lookup(logical_path).to_s
if Webpacker.dev_server.running?
ds = Webpacker.dev_server
dev_server_asset = open("#{ds.protocol}://#{ds.host_with_port}#{asset_path}").read
dev_server_asset.sub!(CLIENT_REQUIRE, '//\0')
dev_server_asset
else
File.read(file_path(logical_path))
end
end
def file_path(path)
::Rails.root.join('public', Webpacker.manifest.lookup(path)[1..-1])
end
end
end
end
// This is a normal JS pack but serves as the single entry point
// for server side rendering
import React from 'react'
import ReactDOMServer from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'
import Footer from 'footer'
// This is purposely not defined yet. See the exec_js_renderer.rb
execJsGlobal.React = React
execJsGlobal.ReactDOMServer = ReactDOMServer
execJsGlobal.ServerStyleSheet = ServerStyleSheet
execJsGlobal.Footer = Footer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment