Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update the install generator to add Webpacker configuration, Add ReScript component and Fix Issue #1397 #1399

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions lib/generators/react_on_rails/base_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,26 @@ def copy_base_files
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
end

def copy_webpack_config
puts "Adding Webpack config"
base_path = "base/base/"
base_files = %w[babel.config.js
config/webpack/clientWebpackConfig.js
config/webpack/commonWebpackConfig.js
config/webpack/development.js
config/webpack/production.js
config/webpack/serverWebpackConfig.js
config/webpack/webpackConfig.js]
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
end

def copy_webpacker_config
puts "Adding Webpacker v6 config"
base_path = "base/base/"
base_files = %w[config/webpacker.yml]
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
end

def add_base_gems_to_gemfile
gem "mini_racer", platforms: :ruby
run "bundle"
Expand All @@ -51,6 +71,19 @@ def add_yarn_dependencies
puts "Adding the lastest react-on-rails NPM module. Double check this is correct in package.json"
run "yarn add react-on-rails --exact"
end

puts "Adding React dependencies"
run "yarn add react react-dom @babel/preset-react prop-types babel-plugin-macros \
babel-plugin-transform-react-remove-prop-types"

puts "Adding TypeScript dependencies"
run "yarn add typescript @babel/preset-typescript @types/react @types/react-dom"

puts "Adding CSS handlers"
run "yarn add css-loader css-minimizer-webpack-plugin mini-css-extract-plugin style-loader"

puts "Adding dev dependencies"
run "yarn add -D @pmmmwh/react-refresh-webpack-plugin fork-ts-checker-webpack-plugin react-refresh"
end

def append_to_spec_rails_helper
Expand Down
5 changes: 4 additions & 1 deletion lib/generators/react_on_rails/react_no_redux_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ class ReactNoReduxGenerator < Rails::Generators::Base

def copy_base_files
base_js_path = "base/base"
base_files = %w[app/javascript/bundles/HelloWorld/components/HelloWorld.jsx]
base_files = %w[app/javascript/packs/server-bundle.js
app/javascript/bundles/HelloWorld/components/HelloWorld.jsx
app/javascript/bundles/HelloWorld/components/HelloWorldServer.js
app/javascript/bundles/HelloWorld/components/HelloWorld.module.css]
base_files.each { |file| copy_file("#{base_js_path}/#{file}", file) }
end

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import style from './HelloWorld.module.css';

const HelloWorld = (props) => {
const [name, setName] = useState(props.name);
Expand All @@ -9,7 +10,7 @@ const HelloWorld = (props) => {
<h3>Hello, {name}!</h3>
<hr />
<form>
<label htmlFor="name">
<label className={style.bold} htmlFor="name">
Say hello to:
<input id="name" type="text" value={name} onChange={(e) => setName(e.target.value)} />
</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.bold {
color: green;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import HelloWorld from './HelloWorld';
// This could be specialized for server rendering
// For example, if using React-Router, we'd have the SSR setup here.

export default HelloWorld;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import ReactOnRails from 'react-on-rails'

import HelloWorld from '../bundles/HelloWorld/components/HelloWorldServer'

// This is how react_on_rails can see the HelloWorld in the browser.
ReactOnRails.register({
HelloWorld,
})
104 changes: 104 additions & 0 deletions lib/generators/react_on_rails/templates/base/base/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// The source code including full typescript support is available at:
// https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/blob/master/babel.config.js

module.exports = function (api) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going to be hard to maintain.

Any thoughts on how we can rely more on rails/webpacker's defaults and then maybe modify those?

const validEnv = ['development', 'test', 'production']
const currentEnv = api.env()
// https://babeljs.io/docs/en/config-files#apienv
// api.env is almost the NODE_ENV
const isDevelopmentEnv = api.env('development')
const isProductionEnv = api.env('production')
const isTestEnv = api.env('test')

if (!validEnv.includes(currentEnv)) {
throw new Error(`${'Please specify a valid `NODE_ENV` or ' +
'`BABEL_ENV` environment variables. Valid values are "development", ' +
'"test", and "production". Instead, received: '}${
JSON.stringify(currentEnv)
}.`)
}

return {
presets: [
isTestEnv && [
'@babel/preset-env',
{
targets: {
node: 'current',
},
modules: 'commonjs',
},
'@babel/preset-react',
],
(isProductionEnv || isDevelopmentEnv) && [
'@babel/preset-env',
{
forceAllTransforms: true,
useBuiltIns: 'entry',
corejs: 3,
modules: false,
exclude: ['transform-typeof-symbol'],
},
],
[
'@babel/preset-react',
{
development: isDevelopmentEnv || isTestEnv,
useBuiltIns: true,
},
],
['@babel/preset-typescript', { allExtensions: true, isTSX: true }],
].filter(Boolean),
plugins: [
'babel-plugin-macros',
'@babel/plugin-syntax-dynamic-import',
isTestEnv && 'babel-plugin-dynamic-import-node',
'@babel/plugin-transform-destructuring',
[
'@babel/plugin-proposal-class-properties',
{
loose: true,
},
],
[
'@babel/plugin-proposal-object-rest-spread',
{
useBuiltIns: true,
},
],
[
'@babel/plugin-transform-runtime',
{
helpers: false,
regenerator: true,
corejs: false,
},
],
[
'@babel/plugin-transform-regenerator',
{
async: false,
},
],
[
'@babel/plugin-proposal-private-property-in-object',
{
loose: true,
},
],
[
'@babel/plugin-proposal-private-methods',
{
loose: true,
},
],
process.env.WEBPACK_SERVE && 'react-refresh/babel',
isProductionEnv && [
'babel-plugin-transform-react-remove-prop-types',
{
removeImport: true,
},
],
].filter(Boolean),
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,5 @@
# different. You should have ONE server bundle which can create all of your server rendered
# React components.
#
config.server_bundle_js_file = "hello-world-bundle.js"
config.server_bundle_js_file = "server-bundle.js"
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// The source can be found at:
// https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/blob/master/config/webpack/clientWebpackConfig.js

const commonWebpackConfig = require('./commonWebpackConfig')

const configureClient = () => {
const clientConfig = commonWebpackConfig()

// server-bundle is special and should ONLY be built by the serverConfig
// In case this entry is not deleted, a very strange "window" not found
// error shows referring to window["webpackJsonp"]. That is because the
// client config is going to try to load chunks.
delete clientConfig.entry['server-bundle']

return clientConfig
}

module.exports = configureClient
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// The source can be found at:
// https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/blob/master/config/webpack/commonWebpackConfig.js

// Common configuration applying to client and server configuration

const { webpackConfig: baseClientWebpackConfig, merge } = require('@rails/webpacker')

const commonOptions = {
resolve: {
extensions: ['.css', '.ts', '.tsx'],
},
}

// Copy the object using merge b/c the baseClientWebpackConfig and commonOptions are mutable globals
const commonWebpackConfig = () => (merge({}, baseClientWebpackConfig, commonOptions))

module.exports = commonWebpackConfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// The source code including full typescript support is available at:
// https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/blob/master/config/webpack/development.js

process.env.NODE_ENV = process.env.NODE_ENV || 'development'

const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')

const { devServer, inliningCss } = require('@rails/webpacker')

const webpackConfig = require('./webpackConfig')

const developmentEnvOnly = (clientWebpackConfig, _serverWebpackConfig) => {
// plugins
if (inliningCss) {
// Note, when this is run, we're building the server and client bundles in separate processes.
// Thus, this plugin is not applied.
clientWebpackConfig.plugins.push(new ReactRefreshWebpackPlugin({
overlay: {
sockPort: devServer.port,
},
}))
}
}
module.exports = webpackConfig(developmentEnvOnly)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// The source can be found at:
// https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/blob/master/config/webpack/production.js

process.env.NODE_ENV = process.env.NODE_ENV || 'production'

const webpackConfig = require('./webpackConfig')

const productionEnvOnly = (_clientWebpackConfig, _serverWebpackConfig) => {
// place any code here that is for production only
}

module.exports = webpackConfig(productionEnvOnly)
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/* eslint-disable no-unused-vars */
/* eslint-disable no-param-reassign */

// The source can be found at:
// https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/blob/master/config/webpack/serverWebpackConfig.js

const { merge, config } = require('@rails/webpacker')
const commonWebpackConfig = require('./commonWebpackConfig')

const webpack = require('webpack')

const configureServer = () => {
// We need to use "merge" because the clientConfigObject, EVEN after running
// toWebpackConfig() is a mutable GLOBAL. Thus any changes, like modifying the
// entry value will result in changing the client config!
// Using webpack-merge into an empty object avoids this issue.
const serverWebpackConfig = commonWebpackConfig()

// We just want the single server bundle entry
const serverEntry = {
'server-bundle': serverWebpackConfig.entry['server-bundle'],
}

if (!serverEntry['server-bundle']) {
throw new Error('Create a pack with the file name \'server-bundle.js\' containing all the server rendering files')
}

serverWebpackConfig.entry = serverEntry

// Remove the mini-css-extract-plugin from the style loaders because
// the client build will handle exporting CSS.
// replace file-loader with null-loader
serverWebpackConfig.module.rules.forEach((loader) => {
if (loader.use && loader.use.filter) {
loader.use = loader.use.filter((item) =>
!(typeof item === 'string' && item.match(/mini-css-extract-plugin/)))
}
})

// No splitting of chunks for a server bundle
serverWebpackConfig.optimization = {
minimize: false,
}
serverWebpackConfig.plugins.unshift(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }))

// Custom output for the server-bundle that matches the config in
// config/initializers/react_on_rails.rb
serverWebpackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
// If using the React on Rails Pro node server renderer, uncomment the next line
// libraryTarget: 'commonjs2',
path: config.outputPath,
publicPath: config.publicPath,
// https://webpack.js.org/configuration/output/#outputglobalobject
}

// Don't hash the server bundle b/c would conflict with the client manifest
// And no need for the MiniCssExtractPlugin
serverWebpackConfig.plugins = serverWebpackConfig.plugins.filter((plugin) =>
plugin.constructor.name !== 'WebpackAssetsManifest' &&
plugin.constructor.name !== 'MiniCssExtractPlugin' &&
plugin.constructor.name !== 'ForkTsCheckerWebpackPlugin')

// Configure loader rules for SSR
// Remove the mini-css-extract-plugin from the style loaders because
// the client build will handle exporting CSS.
// replace file-loader with null-loader
const { rules } = serverWebpackConfig.module;
rules.forEach((rule) => {
if (Array.isArray(rule.use)) {
// remove the mini-css-extract-plugin and style-loader
rule.use = rule.use.filter((item) => {
let testValue;
if (typeof item === 'string') {
testValue = item;
} else if (typeof item.loader === 'string') {
testValue = item.loader;
}
return !(testValue.match(/mini-css-extract-plugin/) || testValue === 'style-loader');
});
const cssLoader = rule.use.find((item) => {
let testValue;

if (typeof item === 'string') {
testValue = item;
} else if (typeof item.loader === 'string') {
testValue = item.loader;
}

return testValue.includes('css-loader');
});
if (cssLoader && cssLoader.options) {
cssLoader.options.modules = { exportOnlyLocals: true };
}

// Skip writing image files during SSR by setting emitFile to false
} else if (rule.use && (rule.use.loader === 'url-loader' || rule.use.loader === 'file-loader')) {
rule.use.options.emitFile = false;
}
});

// eval works well for the SSR bundle because it's the fastest and shows
// lines in the server bundle which is good for debugging SSR
// The default of cheap-module-source-map is slow and provides poor info.
serverWebpackConfig.devtool = 'eval'

// If using the default 'web', then libraries like Emotion and loadable-components
// break with SSR. The fix is to use a node renderer and change the target.
// If using the React on Rails Pro node server renderer, uncomment the next line
// serverWebpackConfig.target = 'node'

return serverWebpackConfig
}

module.exports = configureServer
Loading