Description
Hello!
I'm using the most recent version of react-rails (2.2.0) and Webpacker (1.2). One of the issues I ran into this week was server-side rendering, not fetching assets locally, but instead fetches from the remote asset host. I'm curious if this is fixed, if it's a bug (first of all), or if it will be-supported in the future or which gem will support it?
Explanation:
If Webpack detects that there is an asset_host set in a config, it will prepend the asset_host url in the manifest.json file in Webpacker (1.2) when it precompiles assets via rake webpacker:compile
webpacker/lib/install/config/webpack/configuration.js
const ifHasCDN = env.ASSET_HOST !== undefined && env.NODE_ENV === 'production'
const devServerUrl = `http://${devServer.host}:${devServer.port}/${paths.entry}/`
const publicUrl = ifHasCDN ? `${env.ASSET_HOST}/${paths.entry}/` : `/${paths.entry}/`
const publicPath = env.NODE_ENV !== 'production' ? devServerUrl : publicUrl
So when we precompile assets, our manifest.json file will contain, https://asset.host.com
and It will look like this
{file.js: 'https://assets.com/assets/file.js}
or like this
{file.js: '//assets.com/assets/file.js}
So when we server-side render using react ujs, using the web packer manifest container. The container will read from the local manifest.json with the asset host in it and recognize the asset_path that starts with 'http'. And when it does that, it will fetch the assets remotely, when server-side rendering. We don't want that, I think, we should just fetch it locally (since we have the files already)
react-rails/lib/react/server_rendering/webpack_manifest_container.rb
def find_asset(logical_path)
# raises if not found
asset_path = Webpacker::Manifest.lookup(logical_path).to_s
if asset_path.start_with?("http")
# Get a file from the webpack-dev-server
dev_server_asset = open(asset_path).read
# Remove `webpack-dev-server/client/index.js` code which causes ExecJS to 💥
dev_server_asset.sub!(CLIENT_REQUIRE, '//\0')
dev_server_asset
else
# Read the already-compiled pack:
full_path = Webpacker::Manifest.lookup_path(logical_path).to_s
File.read(full_path)
end
end
Current Solution
To get around that and solve this issue we will have to create a separate manifest.json file (from Webpack, not Webpacker), which will not include the asset host, so we can locally reference them, without fetching assets remotely.
That, we can do on the Webpacker gem side and add in a separate webpack manifest plugin entry that creates a separate manifest.json for server-side rendering in a separate folder, something like:
webpacker/lib/install/config/webpack/shared.js
plugins: [
new webpack.EnvironmentPlugin(JSON.parse(JSON.stringify(env))),
new ExtractTextPlugin(env.NODE_ENV === 'production' ? '[name]-[hash].css' : '[name].css'),
new ManifestPlugin({ fileName: paths.manifest, publicPath, writeToFileEmit: true }),
new ManifestPlugin({
fileName: '../pre-render/manifest.json',
publicPath: `${paths.prerender_path}/`,
writeToFileEmit: true})
],
Then, after that, we will need to read that server-side manifest json file in
react-rails/lib/react/server_rendering/webpack_manifest_container.rb
so we can use the local assets that are precompiled, for server-side rendering
something like this:
module React
module ServerRendering
class WebpackerManifestContainer
def find_asset(logical_path)
path = ::Rails.root.join(File.join(Webpacker::Configuration.output_path, '/pre-render/manifest.json'))
full_path = ::Rails.root.join(File.join(Webpacker::Configuration.output_path, JSON.parse(File.read(path))[logical_path.to_s]))
if full_path
return File.read(full_path)
end
// else do original find_asset stuff
end
end
end