Skip to content

Instantly share code, notes, and snippets.

@ukazap
Forked from wingrunr21/environment.js
Created September 24, 2019 01:51
Show Gist options
  • Save ukazap/c208b3d05520b6244b244082ca4ad155 to your computer and use it in GitHub Desktop.
Save ukazap/c208b3d05520b6244b244082ca4ad155 to your computer and use it in GitHub Desktop.

Revisions

  1. @wingrunr21 wingrunr21 created this gist Oct 2, 2017.
    22 changes: 22 additions & 0 deletions environment.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,22 @@
    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
    38 changes: 38 additions & 0 deletions exec_js_renderer.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,38 @@
    # 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
    34 changes: 34 additions & 0 deletions react_server_helper.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,34 @@
    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
    12 changes: 12 additions & 0 deletions server_side_render.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,12 @@
    // 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