Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a951f4c
Update to Shakapacker 9.1.0 and migrate to Rspack
justin808 Oct 9, 2025
879d171
Add missing i18n translation files
justin808 Oct 10, 2025
087ec70
Fix Ruby version mismatch for CI
justin808 Oct 10, 2025
5d85f15
Fix SSR by using classic React runtime in SWC
justin808 Oct 10, 2025
3fe61f0
Fix CSS modules config for server bundle
justin808 Oct 10, 2025
fbc5781
Add .bs.js extension to resolve extensions for ReScript
justin808 Oct 11, 2025
76921b8
Add patch for rescript-json-combinators to generate .bs.js files
justin808 Oct 11, 2025
012b0b7
Fix yarn.lock and patch file for rescript-json-combinators
justin808 Oct 11, 2025
1685fb4
Fix CSS modules to use default exports for ReScript compatibility
justin808 Oct 11, 2025
28014b2
Move CSS modules fix into function to ensure it applies on each call
justin808 Oct 11, 2025
3da3dfc
Fix server bundle to properly filter Rspack CSS extract loader
justin808 Oct 12, 2025
71b934a
Remove generated i18n files that should be gitignored
justin808 Oct 12, 2025
752919b
Consolidate Rspack config into webpack directory with conditionals
justin808 Oct 12, 2025
4c761bb
Add bundler auto-detection to all webpack config files
justin808 Oct 12, 2025
431a8ee
Add comprehensive documentation and address code review feedback
justin808 Oct 12, 2025
2e03f56
Add performance benchmarks to README
justin808 Oct 12, 2025
5f92988
Correct performance benchmarks to show actual bundler times
justin808 Oct 12, 2025
0ab9eac
Refactor bundler detection and improve documentation
justin808 Oct 12, 2025
a32ebff
Add test coverage and improve documentation
justin808 Oct 13, 2025
84311cc
Fix CI failure and add bundler validation improvements
justin808 Oct 13, 2025
660aab3
Fix YAML alias parsing in RSpec bundler tests
justin808 Oct 13, 2025
2bdc624
Remove heavyweight RSpec bundler integration test
justin808 Oct 13, 2025
2af9d6f
Fix ESLint violations in bundlerUtils test
justin808 Oct 14, 2025
4d9d19e
Fix CSS plugin filtering and add cache immutability docs
justin808 Oct 14, 2025
13449f0
Migrate to modern ReScript .res.js suffix and remove patch
justin808 Oct 14, 2025
b7171e5
Add ror_components wrapper for ReScript component
justin808 Oct 14, 2025
8bae4aa
Remove generated .res.js files from git and add to .gitignore
justin808 Oct 14, 2025
ab8bd51
Add .bs.js to .gitignore for completeness
justin808 Oct 14, 2025
921844d
Remove patches/README.md
justin808 Oct 14, 2025
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby "3.4.6"

gem "react_on_rails", "16.1.1"
gem "shakapacker", "9.0.0.beta.8"
gem "shakapacker", "9.1.0"

# Bundle edge Rails instead: gem "rails", github: "rails/rails"
gem "listen"
Expand Down
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ GEM
websocket (~> 1.0)
semantic_range (3.1.0)
sexp_processor (4.17.1)
shakapacker (9.0.0.beta.8)
shakapacker (9.1.0)
activesupport (>= 5.2)
package_json
rack-proxy (>= 0.6.1)
Expand Down Expand Up @@ -493,7 +493,7 @@ DEPENDENCIES
scss_lint
sdoc
selenium-webdriver (~> 4)
shakapacker (= 9.0.0.beta.8)
shakapacker (= 9.1.0)
spring
spring-commands-rspec
stimulus-rails (~> 1.3)
Expand All @@ -502,7 +502,7 @@ DEPENDENCIES
web-console

RUBY VERSION
ruby 3.4.6p54
ruby 3.4.6p32

BUNDLED WITH
2.4.17
8 changes: 3 additions & 5 deletions bin/shakapacker
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

ENV["RAILS_ENV"] ||= "development"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
ENV["APP_ROOT"] ||= File.expand_path("..", __dir__)

require "bundler/setup"
require "shakapacker"
require "shakapacker/webpack_runner"
require "shakapacker/runner"

APP_ROOT = File.expand_path("..", __dir__)
Dir.chdir(APP_ROOT) do
Shakapacker::WebpackRunner.run(ARGV)
end
Shakapacker::Runner.run(ARGV)
9 changes: 9 additions & 0 deletions client/app/libs/i18n/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Default locale and messages for i18n
export const defaultLocale = 'en';

export const defaultMessages = {
'app.name': 'React Webpack Rails Tutorial',
'comment.form.name_label': 'Name',
'comment.form.text_label': 'Text',
'comment.form.submit': 'Submit',
};
15 changes: 15 additions & 0 deletions client/app/libs/i18n/translations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Translation messages for different locales
export const translations = {
en: {
'app.name': 'React Webpack Rails Tutorial',
'comment.form.name_label': 'Name',
'comment.form.text_label': 'Text',
'comment.form.submit': 'Submit',
},
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Eliminate code duplication with defaultMessages.

The English translations here duplicate defaultMessages from client/app/libs/i18n/default.js. This violates the DRY principle and creates a maintenance burden—updating English text requires changes in two places.

Apply this refactor to import and reuse defaultMessages:

+import { defaultMessages } from './default';
+
 // Translation messages for different locales
 export const translations = {
-  en: {
-    'app.name': 'React Webpack Rails Tutorial',
-    'comment.form.name_label': 'Name',
-    'comment.form.text_label': 'Text',
-    'comment.form.submit': 'Submit',
-  },
+  en: defaultMessages,
   es: {
     'app.name': 'Tutorial de React Webpack Rails',
     'comment.form.name_label': 'Nombre',
     'comment.form.text_label': 'Texto',
     'comment.form.submit': 'Enviar',
   },
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const translations = {
en: {
'app.name': 'React Webpack Rails Tutorial',
'comment.form.name_label': 'Name',
'comment.form.text_label': 'Text',
'comment.form.submit': 'Submit',
},
import { defaultMessages } from './default';
// Translation messages for different locales
export const translations = {
en: defaultMessages,
es: {
'app.name': 'Tutorial de React Webpack Rails',
'comment.form.name_label': 'Nombre',
'comment.form.text_label': 'Texto',
'comment.form.submit': 'Enviar',
},
};
🤖 Prompt for AI Agents
In client/app/libs/i18n/translations.js around lines 2 to 8, the English
translations duplicate strings from client/app/libs/i18n/default.js; import
defaultMessages from that file and reuse it rather than hardcoding the same keys
here (e.g., replace the inline en object with a spread/merge of defaultMessages
into translations.en), ensuring any additional or overridden keys remain after
the spread so updates live in one place.

es: {
'app.name': 'Tutorial de React Webpack Rails',
'comment.form.name_label': 'Nombre',
'comment.form.text_label': 'Texto',
'comment.form.submit': 'Enviar',
},
};
9 changes: 9 additions & 0 deletions config/rspack/alias.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { resolve } = require('path');

module.exports = {
resolve: {
alias: {
Assets: resolve(__dirname, '..', '..', 'client', 'app', 'assets'),
},
},
};
24 changes: 24 additions & 0 deletions config/rspack/clientRspackConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const rspack = require('@rspack/core');
const commonRspackConfig = require('./commonRspackConfig');

const configureClient = () => {
const clientConfig = commonRspackConfig();

clientConfig.plugins.push(
new rspack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
ActionCable: '@rails/actioncable',
}),
);

// 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;
71 changes: 71 additions & 0 deletions config/rspack/commonRspackConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Common configuration applying to client and server configuration
const { generateWebpackConfig, merge } = require('shakapacker');

const baseClientRspackConfig = generateWebpackConfig();
const commonOptions = {
resolve: {
extensions: ['.css', '.ts', '.tsx'],
},
};

// add sass resource loader
const sassLoaderConfig = {
loader: 'sass-resources-loader',
options: {
resources: './client/app/assets/styles/app-variables.scss',
},
};

const ignoreWarningsConfig = {
ignoreWarnings: [/Module not found: Error: Can't resolve 'react-dom\/client'/],
};

const scssConfigIndex = baseClientRspackConfig.module.rules.findIndex((config) =>
'.scss'.match(config.test) && config.use,
);

if (scssConfigIndex === -1) {
console.warn('No SCSS rule with use array found in rspack config');
} else {
// Configure sass-loader to use the modern API
const scssRule = baseClientRspackConfig.module.rules[scssConfigIndex];
const sassLoaderIndex = scssRule.use.findIndex((loader) => {
if (typeof loader === 'string') {
return loader.includes('sass-loader');
}
return loader.loader && loader.loader.includes('sass-loader');
});

if (sassLoaderIndex !== -1) {
const sassLoader = scssRule.use[sassLoaderIndex];
if (typeof sassLoader === 'string') {
scssRule.use[sassLoaderIndex] = {
loader: sassLoader,
options: {
api: 'modern'
}
};
} else {
sassLoader.options = sassLoader.options || {};
sassLoader.options.api = 'modern';
}
}

// Fix css-loader configuration for CSS modules if namedExport is enabled
// When namedExport is true, exportLocalsConvention must be camelCaseOnly or dashesOnly
const cssLoader = scssRule.use.find((loader) => {
const loaderName = typeof loader === 'string' ? loader : loader?.loader;
return loaderName?.includes('css-loader');
});

if (cssLoader?.options?.modules?.namedExport) {
cssLoader.options.modules.exportLocalsConvention = 'camelCaseOnly';
}

baseClientRspackConfig.module.rules[scssConfigIndex].use.push(sassLoaderConfig);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Prettier violations to unblock lint.

eslint --fix fails here:

  • Lines 23-24 need the multiline arrow formatting Prettier expects.
  • Lines 45-46 are missing trailing commas.
    These are hard errors, so bundles won’t build until the style issues are resolved.
🧰 Tools
🪛 ESLint

[error] 23-24: Replace (config)·=>⏎· with ⏎··(config)·=>

(prettier/prettier)


[error] 45-45: Insert ,

(prettier/prettier)


[error] 46-46: Insert ,

(prettier/prettier)

🤖 Prompt for AI Agents
config/rspack/commonRspackConfig.js lines 23-66: Prettier is failing — change
the single-line arrow callback passed to findIndex into an explicit multiline
arrow function with braces and a return (so the function is formatted across
lines), and add missing trailing commas to the object literals you create when
replacing the sass-loader (ensure options: { api: 'modern', }, and the outer
object has a trailing comma as well) so the created objects conform to
Prettier's trailing-comma rules.


// Copy the object using merge b/c the baseClientRspackConfig and commonOptions are mutable globals
const commonRspackConfig = () => merge({}, baseClientRspackConfig, commonOptions, ignoreWarningsConfig);

module.exports = commonRspackConfig;
25 changes: 25 additions & 0 deletions config/rspack/development.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
process.env.NODE_ENV = process.env.NODE_ENV || 'development';

const { devServer, inliningCss } = require('shakapacker');

const rspackConfig = require('./rspackConfig');

const developmentEnvOnly = (clientRspackConfig, _serverRspackConfig) => {
// 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 to the server bundle.

// eslint-disable-next-line global-require
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
clientRspackConfig.plugins.push(
new ReactRefreshWebpackPlugin({
overlay: {
sockPort: devServer.port,
},
}),
);
}
};

module.exports = rspackConfig(developmentEnvOnly);
9 changes: 9 additions & 0 deletions config/rspack/production.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
process.env.NODE_ENV = process.env.NODE_ENV || 'production';

const rspackConfig = require('./rspackConfig');

const productionEnvOnly = (_clientRspackConfig, _serverRspackConfig) => {
// place any code here that is for production only
};

module.exports = rspackConfig(productionEnvOnly);
15 changes: 15 additions & 0 deletions config/rspack/rspack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const { env, generateWebpackConfig } = require('shakapacker');
const { existsSync } = require('fs');
const { resolve } = require('path');

const envSpecificConfig = () => {
const path = resolve(__dirname, `${env.nodeEnv}.js`);
if (existsSync(path)) {
console.log(`Loading ENV specific rspack configuration file ${path}`);
return require(path);
}

return generateWebpackConfig();
};

module.exports = envSpecificConfig();
34 changes: 34 additions & 0 deletions config/rspack/rspackConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const clientRspackConfig = require('./clientRspackConfig');
const serverRspackConfig = require('./serverRspackConfig');

const rspackConfig = (envSpecific) => {
const clientConfig = clientRspackConfig();
const serverConfig = serverRspackConfig();

if (envSpecific) {
envSpecific(clientConfig, serverConfig);
}

let result;
// For HMR, need to separate the the client and server rspack configurations
if (process.env.WEBPACK_SERVE || process.env.CLIENT_BUNDLE_ONLY) {
// eslint-disable-next-line no-console
console.log('[React on Rails] Creating only the client bundles.');
result = clientConfig;
} else if (process.env.SERVER_BUNDLE_ONLY) {
// eslint-disable-next-line no-console
console.log('[React on Rails] Creating only the server bundle.');
result = serverConfig;
} else {
// default is the standard client and server build
// eslint-disable-next-line no-console
console.log('[React on Rails] Creating both client and server bundles.');
result = [clientConfig, serverConfig];
}

// To debug, uncomment next line and inspect "result"
// debugger
return result;
};

module.exports = rspackConfig;
122 changes: 122 additions & 0 deletions config/rspack/serverRspackConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const path = require('path');
const { config } = require('shakapacker');
const commonRspackConfig = require('./commonRspackConfig');

const rspack = require('@rspack/core');

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix import order (ESLint import/order).

Place external packages before local modules.

Apply this diff:

 const path = require('path');
 const { config } = require('shakapacker');
-const commonRspackConfig = require('./commonRspackConfig');
-
-const rspack = require('@rspack/core');
+const rspack = require('@rspack/core');
+const commonRspackConfig = require('./commonRspackConfig');
 
 const configureServer = () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const path = require('path');
const { config } = require('shakapacker');
const commonRspackConfig = require('./commonRspackConfig');
const rspack = require('@rspack/core');
const path = require('path');
const { config } = require('shakapacker');
const rspack = require('@rspack/core');
const commonRspackConfig = require('./commonRspackConfig');
const configureServer = () => {
// …rest of implementation…
};
🧰 Tools
🪛 ESLint

[error] 5-5: @rspack/core import should occur before import of ./commonRspackConfig

(import/order)

🤖 Prompt for AI Agents
In config/rspack/serverRspackConfig.js lines 1-6, the import order violates
ESLint import/order by placing a local module before an external package;
reorder requires so that external packages (e.g., path, @rspack/core,
shakapacker) come first and local modules (./commonRspackConfig) come after,
keeping existing declarations the same but moving the const rspack =
require('@rspack/core') line up with the other external requires and leaving
./commonRspackConfig below them.

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

// We just want the single server bundle entry
const serverEntry = {
'server-bundle': serverRspackConfig.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",
);
}

serverRspackConfig.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
serverRspackConfig.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/)),
);
}
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove duplicate mini-css-extract-plugin filtering.

This filtering logic is duplicated at lines 67-107, where it's handled more comprehensively along with other SSR-specific loader adjustments. The duplicate code reduces maintainability without adding value.

Apply this diff:

   serverRspackConfig.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
-  serverRspackConfig.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
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Remove the mini-css-extract-plugin from the style loaders because
// the client build will handle exporting CSS.
// replace file-loader with null-loader
serverRspackConfig.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/)),
);
}
});
serverRspackConfig.entry = serverEntry;
// No splitting of chunks for a server bundle
🧰 Tools
🪛 ESLint

[error] 32-32: Assignment to property of function parameter 'loader'.

(no-param-reassign)

🤖 Prompt for AI Agents
In config/rspack/serverRspackConfig.js around lines 27 to 36, remove the
duplicate block that filters out mini-css-extract-plugin from loader.use — this
logic is already implemented comprehensively later (lines 67–107). Delete the
entire forEach that checks loader.use.filter and reassigns loader.use to the
filtered array so only the later SSR-specific loader adjustments remain,
ensuring no functional changes beyond removing the redundant duplicate.


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

// Custom output for the server-bundle that matches the config in
// config/initializers/react_on_rails.rb
// Output to a private directory for SSR bundles (not in public/)
// Using the default React on Rails path: ssr-generated
serverRspackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
// If using the React on Rails Pro node server renderer, uncomment the next line
// libraryTarget: 'commonjs2',
path: path.resolve(__dirname, '../../ssr-generated'),
publicPath: config.publicPath,
// https://rspack.dev/config/output#outputglobalobject
};

// Don't hash the server bundle b/c would conflict with the client manifest
// And no need for the MiniCssExtractPlugin
serverRspackConfig.plugins = serverRspackConfig.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 = serverRspackConfig.module.rules;
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) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against undefined when detecting css-loader.

Avoid potential TypeError if testValue is not set.

Apply this diff:

-        return testValue.includes('css-loader');
+        return typeof testValue === 'string' && testValue.includes('css-loader');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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) {
const cssLoader = rule.use.find((item) => {
let testValue;
if (typeof item === 'string') {
testValue = item;
} else if (typeof item.loader === 'string') {
testValue = item.loader;
}
return typeof testValue === 'string' && testValue.includes('css-loader');
});
if (cssLoader && cssLoader.options && cssLoader.options.modules) {
🤖 Prompt for AI Agents
In config/rspack/serverRspackConfig.js around lines 84 to 95, the detection of
css-loader calls testValue.includes(...) without ensuring testValue is defined
which can throw a TypeError; update the predicate to guard testValue (e.g. check
testValue is a non-empty string before calling includes or use optional
chaining) so the find callback returns false when testValue is undefined, and
keep the subsequent check for cssLoader.options as-is.

// Preserve existing modules config but add exportOnlyLocals for SSR
cssLoader.options.modules = {
...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;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add null safety check for rule.use.options.

When rule.use is an object (not an array), accessing rule.use.options.emitFile without verifying that options exists could cause a runtime error if a loader doesn't define options.

Apply this diff:

     // 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;
+    if (rule.use.options) {
+      rule.use.options.emitFile = false;
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (rule.use && (rule.use.loader === 'url-loader' || rule.use.loader === 'file-loader')) {
rule.use.options.emitFile = false;
}
// 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')) {
if (rule.use.options) {
rule.use.options.emitFile = false;
}
}
🧰 Tools
🪛 ESLint

[error] 105-105: Assignment to property of function parameter 'rule'.

(no-param-reassign)

🤖 Prompt for AI Agents
In config/rspack/serverRspackConfig.js around lines 104 to 106, the code sets
rule.use.options.emitFile without ensuring rule.use.options exists; update the
branch handling object-style rule.use to first ensure rule.use.options is
defined (e.g. if (!rule.use.options) rule.use.options = {}), then set
rule.use.options.emitFile = false so you avoid runtime errors when options is
undefined.

});

// 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.
serverRspackConfig.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
// serverRspackConfig.target = 'node'

return serverRspackConfig;
};

module.exports = configureServer;
Loading
Loading