Rspack support#582
Conversation
WalkthroughAdds Rspack support across docs, install templates, JS package APIs and configs, Ruby runtime selection and runners, dev-server bundler-aware behavior, new rspack rules/environments, package.json exports/deps, tests, and a small change to shared CSS utilities export shape. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant Bin as bin/shakapacker
participant Config as Shakapacker::Configuration
participant Runner as Shakapacker::Runner
participant WR as WebpackRunner
participant RR as RspackRunner
Note over Bin,Config: boot and config discovery
User->>Bin: run (ARGV)
Bin->>Config: new(root_path:, config_path:, env:)
Bin->>Runner: initialize(@config)
alt bundler == "rspack"
Runner->>Runner: find_bundler_config() (rspack or fallback webpack)
Bin->>RR: require & run(ARGV)
RR->>RR: build_cmd("rspack") + flags + --config (unless help)
RR->>System: exec(env, cmd)
else bundler == "webpack"
Runner->>Runner: find_webpack_config()
Bin->>WR: require & run(ARGV)
WR->>System: exec(env, cmd)
end
sequenceDiagram
autonumber
participant Dev as DevServerRunner
participant CFG as Configuration
participant CLI as CLI
Dev->>CFG: get_bundler_type()
alt webpack
Dev->>CLI: webpack serve [--config] [--progress --color] [--hot only | --no-hot]
else rspack
Dev->>CLI: rspack serve [--config] [--hot if requested] (no --progress/--color)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
package.json (1)
103-106: Align Node engine with Rspack 1.x requirement
Rspack 1.x no longer supports Node 14; ensure yourenginesfield reflects the minimum Node.js 16 requirement.
- File: package.json (lines 103–106)
"engines": { - "node": ">= 14", + "node": ">= 16", "yarn": ">=1 <5" },
🧹 Nitpick comments (36)
CHANGELOG.md (1)
11-12: Unify list style and fix minor grammar ("a webpack-compatible API").Consistent list markers and a small grammar tweak will satisfy markdownlint (MD004) and read more naturally.
-- Rspack support as an alternative bundler to webpack. Configure `bundler: 'rspack'` in `shakapacker.yml` to use Rspack's faster Rust-based bundling with webpack-compatible +* Rspack support as an alternative bundler to webpack. Configure `bundler: 'rspack'` in `shakapacker.yml` to use Rspack's faster Rust-based bundling with a webpack-compatible APIs, built-in SWC loader, and CSS extraction. Automatic bundler detection in `bin/shakapacker` with fallback support for webpack configurations.package/rspack/rules/raw.js (1)
1-5: Prefer EOF newline and consider adding a quick test for?rawimports.Prettier flags missing newline at EOF. Also, adding a unit/integration test that asserts
import x from './file.txt?raw'yields raw source would lock this in.module.exports = { resourceQuery: /raw/, type: "asset/source" -} +} +I can sketch a minimal Jest test using in-memory compilation if you’d like.
docs/rspack.md (3)
3-3: Grammar: insert article (“a webpack-compatible API”).-Shakapacker supports [Rspack](https://rspack.rs) as an alternative bundler to Webpack. Rspack is a fast Rust-based web bundler with webpack-compatible API that can significantly speed up your build times. +Shakapacker supports [Rspack](https://rspack.rs) as an alternative bundler to Webpack. Rspack is a fast Rust-based web bundler with a webpack-compatible API that can significantly speed up your build times.
31-33: Duplicate “Configuration” heading. Rename the second for clarity (MD024).-## Configuration +## Rspack configuration file
13-17: Trim trailing spaces and normalize capitalization (“Rspack”).These small edits fix markdown lint warnings and keep style consistent.
-# or +# or pnpm add @rspack/core @rspack/cli -D -# or +# or bun add @rspack/core @rspack/cli -D-# Development server (automatically uses rspack when bundler: 'rspack') +# Development server (automatically uses Rspack when bundler: 'rspack') ./bin/shakapacker-dev-server-- **5-20x faster** incremental builds +- **5-20x faster** incremental builds - **Faster HMR** (Hot Module Replacement)Also applies to: 125-127, 138-140
package.json (1)
17-23: Missing TypeScript declarations for subpath exportsOur check shows only
package/index.d.tsexists and there’s notypesVersionsin package.json. Consumers importing:
shakapacker/rspackshakapacker/swcshakapacker/esbuildwill not get any
.d.tsfiles.Please consider one of the following optional refactors:
- Add declaration files next to each subpath entrypoint:
•package/rspack/index.d.ts
•package/swc/index.d.ts
•package/esbuild/index.d.ts- Add a
typesVersionsmap in package.json to point TypeScript at these files.- Or embed JSDoc typedefs and generate
.d.tson publish.Let me know if you’d like help drafting these typings!
package/rspack/rules/erb.js (2)
15-16: Use string for env var value to avoid cross-platform spawn issues.Process env values should be strings. Using number 1 can be coerced, but explicit strings are safer.
Apply:
- ...process.env, - DISABLE_SPRING: 1 + ...process.env, + DISABLE_SPRING: "1"
21-21: Prettier: ensure newline at end of file.Static analysis flagged a missing newline at EOF. Add a trailing newline to satisfy prettier.
Apply:
-})) +})) +package/rspack/rules/file.js (2)
16-18: Potential false-positive match due to prefix-only check.Using startsWith on the raw string can mis-match siblings (e.g., /foo/bar and /foo/barista). The diff above addresses this by using path.relative and longest-prefix selection.
29-29: Prettier: missing trailing newline.Insert a newline at end of file to satisfy prettier.
package/rspack/environments/test.js (1)
14-14: Prettier: missing trailing newline.Insert a newline at end of file to satisfy prettier.
lib/install/bin/shakapacker-rspack (1)
4-4: Prefer dir over FILE for path calculation.Minor readability/consistency tweak; the rest of the script already uses dir.
-ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __dir__)lib/install/config/webpack/rspack.config.js (1)
6-6: Add a trailing newline at EOFKeeps linters and Git diffs happy.
Apply this diff:
-module.exports = rspackConfig +module.exports = rspackConfig +package/rspack/environments/development.js (1)
4-4: Fix Prettier issues (whitespace and EOF newline)
- Line 4 has trailing whitespace.
- Add a newline at end of file.
Apply this diff:
-const devServerConfig = require("../../webpackDevServerConfig") +const devServerConfig = require("../../webpackDevServerConfig") @@ -module.exports = merge(baseConfig, devConfig) +module.exports = merge(baseConfig, devConfig) +Also applies to: 19-19
package/rspack/rules/index.js (3)
7-31: Consider splitting JS and TS handling for SWC parser correctnessUsing the TypeScript parser for all JS/TS extensions can be surprising for plain JS and may affect parsing/transform semantics. Recommend separate rules for JS (ecmascript parser) and TS (typescript parser).
Apply this diff:
-// Use Rspack's built-in SWC loader for JS/TS/JSX/TSX files -rules.push({ - test: /\.(js|jsx|ts|tsx|mjs)$/, - exclude: /node_modules/, - type: "javascript/auto", - use: [ - { - loader: "builtin:swc-loader", - options: { - jsc: { - parser: { - syntax: "typescript", - tsx: true, - jsx: true - }, - transform: { - react: { - runtime: "automatic" - } - } - } - } - } - ] -}) +// Use Rspack's built-in SWC loader +// JS/JSX/MJS +rules.push({ + test: /\.(js|jsx|mjs)$/, + exclude: /node_modules/, + type: "javascript/auto", + use: [ + { + loader: "builtin:swc-loader", + options: { + jsc: { + parser: { syntax: "ecmascript", jsx: true }, + transform: { react: { runtime: "automatic" } } + } + } + } + ] +}) + +// TS/TSX +rules.push({ + test: /\.(ts|tsx)$/, + exclude: /node_modules/, + type: "javascript/auto", + use: [ + { + loader: "builtin:swc-loader", + options: { + jsc: { + parser: { syntax: "typescript", tsx: true }, + transform: { react: { runtime: "automatic" } } + } + } + } + ] +})
16-21: Minor: jsx is redundant when tsx is trueSWC implies JSX handling when tsx is enabled. If you keep a single TS rule, jsx: true can be dropped for clarity.
45-45: Align with ESLint/Prettier: spacing and newline-after-import
- Remove trailing spaces in the “Less rules” comment.
- Insert a blank line after require statements not followed by another require.
- Add a trailing newline at EOF.
Apply this diff:
-// Less rules +// Less rules @@ -const erb = require("./erb") -rules.push(erb) +const erb = require("./erb") + +rules.push(erb) @@ -const file = require("./file") -rules.push(file) +const file = require("./file") + +rules.push(file) @@ -const raw = require("./raw") -rules.push(raw) +const raw = require("./raw") + +rules.push(raw) @@ -module.exports = rules +module.exports = rules +Also applies to: 58-60, 62-64, 66-68, 69-69
package/rspack/environments/production.js (3)
15-26: Silence ESLint’s global-require warning for optional compression plugin.The inline require is intentional to keep
compression-webpack-pluginoptional, but it triggersglobal-require. Add a one-line disable to keep the pattern explicit and lint-clean.-if (moduleExists("compression-webpack-plugin")) { - const CompressionPlugin = require("compression-webpack-plugin") +if (moduleExists("compression-webpack-plugin")) { + // eslint-disable-next-line global-require + const CompressionPlugin = require("compression-webpack-plugin") plugins.push( new CompressionPlugin({ filename: "[path][base].gz[query]", algorithm: "gzip", test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/, threshold: 8192, minRatio: 0.8 }) ) }
28-35: Guard minimizer plugins for broader Rspack version compatibility.If a consumer is on an older Rspack that doesn’t expose these minimizer constructors, this will throw at startup. Consider guarding and letting
minimize: truefall back to built-ins.-productionConfig.optimization = { - minimize: true, - minimizer: [ - new rspack.SwcJsMinimizerRspackPlugin(), - new rspack.LightningCssMinimizerRspackPlugin() - ] -} +productionConfig.optimization = { + minimize: true, + ...(rspack.SwcJsMinimizerRspackPlugin && rspack.LightningCssMinimizerRspackPlugin + ? { + minimizer: [ + new rspack.SwcJsMinimizerRspackPlugin(), + new rspack.LightningCssMinimizerRspackPlugin() + ] + } + : {}) +}If you prefer to keep the explicit minimizers, please confirm the minimum @rspack/core version in package.json aligns with these constructors.
41-41: Prettier: ensure newline at EOF.Prettier requests a trailing newline. Save once to add it.
-module.exports = merge(baseConfig, productionConfig) +module.exports = merge(baseConfig, productionConfig) +spec/shakapacker/config_path_spec.rb (1)
49-51: Optionally assert the error message for better signal.You can also verify we emit the expected guidance when no configs are found.
Example:
expect($stderr).to receive(:puts).with(/rspack config .* not found/i) expect { Shakapacker::RspackRunner.new([]) }.to raise_error(SystemExit)If you want, I can add a separate example covering the fallback path where a webpack config exists and a deprecation is printed.
lib/shakapacker/dev_server_runner.rb (1)
66-71: Minor: only set WEBPACK_SERVE for webpack.This env var is a webpack-dev-server hint and is unnecessary for Rspack.
- env["SHAKAPACKER_CONFIG"] = @shakapacker_config - env["WEBPACK_SERVE"] = "true" + env["SHAKAPACKER_CONFIG"] = @shakapacker_config + env["WEBPACK_SERVE"] = "true" if get_bundler_type == "webpack"package/rspack/rules/stylus.js (1)
18-18: Prettier: ensure newline at EOF.Add the trailing newline to satisfy Prettier.
-) +) +package/rspack/rules/css.js (3)
3-22: Unify CSS handling via getStyleRule for consistency and correctness (fixes importLoaders, PostCSS, and CSS Modules).This rule diverges from the shared getStyleRule behavior used elsewhere (e.g., less/sass). As written:
- importLoaders is 1 while there are no loaders after css-loader, which is incorrect for @import processing.
- PostCSS is not attempted when present.
- CSS Modules behavior differs (modules: false vs modules: { auto: true }).
Adopting getStyleRule aligns behavior across style rules and avoids future drift.
Apply this diff:
const { rspack } = require("@rspack/core") -const inliningCss = require("../../utils/inliningCss") -const { moduleExists } = require("../../utils/helpers") +const inliningCss = require("../../utils/inliningCss") +const { getStyleRule } = require("../../utils/getStyleRule") const { CssExtractRspackPlugin } = rspack -// Simple CSS rule for rspack - don't use getStyleRule to avoid complexity -module.exports = moduleExists("css-loader") ? { - test: /\.(css)$/i, - type: 'javascript/auto', - use: [ - inliningCss ? "style-loader" : CssExtractRspackPlugin.loader, - { - loader: "css-loader", - options: { - sourceMap: true, - importLoaders: 1, - modules: false - } - } - ] -} : null +// Delegate to shared getStyleRule for consistent css-loader/postcss handling +module.exports = getStyleRule(/\.(css)$/i, [ + inliningCss ? "style-loader" : CssExtractRspackPlugin.loader +])
15-18: If keeping the bespoke rule: fix importLoaders to 0 and consider PostCSS.With only css-loader in the chain (extraction loader does not count), importLoaders should be 0. Also consider adding PostCSS when available to match other style rules.
Apply this diff:
- importLoaders: 1, - modules: false + importLoaders: 0, + modules: falseOptional: add PostCSS when present
use: [ inliningCss ? "style-loader" : CssExtractRspackPlugin.loader, { loader: "css-loader", options: { sourceMap: true, - importLoaders: 0, + importLoaders: 1, modules: false } - } + }, + // only if postcss-loader is installed: + // require("../../utils/helpers").canProcess("postcss-loader", (loaderPath) => ({ + // loader: loaderPath, + // options: { sourceMap: true } + // })) ]
8-22: Prettier violations reported by ESLint.Formatting issues (indentation, quotes, and trailing newlines) are flagged by Prettier. The refactor above will incidentally satisfy these, but if you retain the current structure, please run the formatter.
lib/shakapacker/runner.rb (2)
44-46: Unused private method get_bundler_type.Currently unused in this class. Either remove or use where appropriate (e.g., dev server runner).
- def get_bundler_type - @config.bundler - end + # def get_bundler_type + # @config.bundler + # end
48-73: Clear rspack config fallback with deprecation guidance. Consider exit vs exit!.The fallback/warning path is helpful. Minor: exit! skips at_exit and ensure blocks; if that’s intentional in CLI context, fine. Otherwise, Kernel.exit(1) is safer.
package/rspack/rules/less.js (2)
21-22: Prefer resolving node_modules from the app root, not the library directory.path.resolve(__dirname, "node_modules") points to this package’s node_modules, not the application’s. Using process.cwd() better reflects the Rails app context during compilation.
Apply this diff:
- paths: [path.resolve(__dirname, "node_modules"), sourcePath, ...paths] + paths: [path.resolve(process.cwd(), "node_modules"), sourcePath, ...paths]
27-27: Prettier: add trailing newline.ESLint reports a missing newline at end of file.
-) +) +lib/shakapacker/rspack_runner.rb (1)
1-1: Remove unused require "shellwords".Not used in this file.
-require "shellwords"lib/install/bin/shakapacker (1)
12-16: Consider extracting environment resolution logic for consistency.The environment resolution logic
ENV["RAILS_ENV"] || ENV["NODE_ENV"] || "development"appears both here (line 15) and at line 3. Consider using the value already set at line 3 to avoid duplication.config = Shakapacker::Configuration.new( root_path: Pathname.new(APP_ROOT), config_path: Pathname.new(File.join(APP_ROOT, "config/shakapacker.yml")), - env: ENV["RAILS_ENV"] || ENV["NODE_ENV"] || "development" + env: ENV["RAILS_ENV"] )package/rspack/index.js (1)
40-40: Add trailing newline for consistency.- merge: rspackMerge + merge: rspackMerge +package/rspack/environments/base.js (3)
80-80: Use consistent quote style.The codebase appears to use double quotes for strings. Consider maintaining consistency.
- fileName: config.manifestPath.split('/').pop(), // Get just the filename + fileName: config.manifestPath.split("/").pop(), // Get just the filename
94-134: Consider refactoring the entrypoints loop for better readability.While the for...of loop works correctly, consider using Object.entries().forEach() or reduce() to align with the functional programming style used elsewhere in the codebase. This would also address the ESLint warning about avoiding loops.
- for (const [entrypointName, entrypointFiles] of Object.entries(entrypoints)) { - const jsFiles = entrypointFiles.filter(file => file.endsWith('.js')) - const cssFiles = entrypointFiles.filter(file => file.endsWith('.css')) - - // Use a helper function to resolve file paths consistently - const resolveFilePath = (file) => { - // Try exact match first - if (manifest[file]) return manifest[file] - - // Try filename only - const filename = file.split('/').pop() - if (manifest[filename]) return manifest[filename] - - // For hashed files, try to find the base name without hash - // e.g., "css/org-350a7e61.css" -> "org.css" - const baseMatch = filename.match(/^(.+?)-[a-f0-9]+(\.\w+)$/) - if (baseMatch) { - const baseName = baseMatch[1] + baseMatch[2] // "org.css" - if (manifest[baseName]) return manifest[baseName] - } - - // For webpack chunk files with full directory path - // e.g., "js/598-7f94a9abddc251f3.js" -> "js/598-js" - const chunkMatch = file.match(/^(.+\/)?(\d+)-[a-f0-9]+\.(js|css)$/) - if (chunkMatch) { - const [, dir = '', chunkNum, ext] = chunkMatch - const chunkKey = `${dir}${chunkNum}-${ext}` // "js/598-js" - if (manifest[chunkKey]) return manifest[chunkKey] - } - - // Fallback to original file path - return file - } - - entrypointsManifest[entrypointName] = { - assets: { - js: jsFiles.map(resolveFilePath), - css: cssFiles.map(resolveFilePath) - } - } - } + const entrypointsManifest = Object.entries(entrypoints).reduce( + (acc, [entrypointName, entrypointFiles]) => { + const jsFiles = entrypointFiles.filter((file) => + file.endsWith(".js") + ) + const cssFiles = entrypointFiles.filter((file) => + file.endsWith(".css") + ) + + // Use a helper function to resolve file paths consistently + const resolveFilePath = (file) => { + // Try exact match first + if (manifest[file]) return manifest[file] + + // Try filename only + const filename = file.split("/").pop() + if (manifest[filename]) return manifest[filename] + + // For hashed files, try to find the base name without hash + // e.g., "css/org-350a7e61.css" -> "org.css" + const baseMatch = filename.match(/^(.+?)-[a-f0-9]+(\.\w+)$/) + if (baseMatch) { + const baseName = baseMatch[1] + baseMatch[2] // "org.css" + if (manifest[baseName]) return manifest[baseName] + } + + // For webpack chunk files with full directory path + // e.g., "js/598-7f94a9abddc251f3.js" -> "js/598-js" + const chunkMatch = file.match(/^(.+\/)?(\d+)-[a-f0-9]+\.(js|css)$/) + if (chunkMatch) { + const [, dir = "", chunkNum, ext] = chunkMatch + const chunkKey = `${dir}${chunkNum}-${ext}` // "js/598-js" + if (manifest[chunkKey]) return manifest[chunkKey] + } + + // Fallback to original file path + return file + } + + acc[entrypointName] = { + assets: { + js: jsFiles.map(resolveFilePath), + css: cssFiles.map(resolveFilePath) + } + } + return acc + }, + {} + )
219-219: Add trailing newline for consistency.module: { rules } -} +} +
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (2)
package-lock.jsonis excluded by!**/package-lock.jsonyarn.lockis excluded by!**/yarn.lock,!**/*.lock
📒 Files selected for processing (33)
CHANGELOG.md(1 hunks)docs/rspack.md(1 hunks)lib/install/bin/shakapacker(1 hunks)lib/install/bin/shakapacker-rspack(1 hunks)lib/install/config/rspack/rspack.config.js(1 hunks)lib/install/config/shakapacker.yml(1 hunks)lib/install/config/webpack/rspack.config.js(1 hunks)lib/shakapacker/configuration.rb(1 hunks)lib/shakapacker/dev_server_runner.rb(2 hunks)lib/shakapacker/rspack_runner.rb(1 hunks)lib/shakapacker/runner.rb(3 hunks)package.json(4 hunks)package/rspack/environments/base.js(1 hunks)package/rspack/environments/development.js(1 hunks)package/rspack/environments/production.js(1 hunks)package/rspack/environments/test.js(1 hunks)package/rspack/index.js(1 hunks)package/rspack/rules/css.js(1 hunks)package/rspack/rules/erb.js(1 hunks)package/rspack/rules/file.js(1 hunks)package/rspack/rules/index.js(1 hunks)package/rspack/rules/less.js(1 hunks)package/rspack/rules/raw.js(1 hunks)package/rspack/rules/sass.js(1 hunks)package/rspack/rules/stylus.js(1 hunks)package/rules/css.js(1 hunks)package/rules/less.js(1 hunks)package/rules/sass.js(1 hunks)package/rules/stylus.js(1 hunks)package/utils/getStyleRule.js(2 hunks)spec/shakapacker/config_path_spec.rb(1 hunks)spec/shakapacker/rspack_runner_spec.rb(1 hunks)test/package/rspack/index.test.js(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-10-09T10:47:17.620Z
Learnt from: marvinthepa
PR: shakacode/shakapacker#520
File: spec/shakapacker/utils_manager_spec.rb:68-89
Timestamp: 2024-10-09T10:47:17.620Z
Learning: In `spec/shakapacker/utils_manager_spec.rb`, the code within the context `"when lockfile is in Rails.root, but pwd is different"` is not duplicated for each package manager.
Applied to files:
spec/shakapacker/config_path_spec.rblib/install/bin/shakapacker-rspackspec/shakapacker/rspack_runner_spec.rblib/shakapacker/runner.rb
📚 Learning: 2024-10-09T10:50:16.512Z
Learnt from: marvinthepa
PR: shakacode/shakapacker#520
File: spec/shakapacker/utils_manager_spec.rb:104-116
Timestamp: 2024-10-09T10:50:16.512Z
Learning: In `spec/shakapacker/utils_manager_spec.rb`, `error_unless_package_manager_is_obvious` is not a test method, so extracting into a shared context may not be helpful.
Applied to files:
spec/shakapacker/config_path_spec.rbspec/shakapacker/rspack_runner_spec.rb
📚 Learning: 2024-10-09T10:46:03.499Z
Learnt from: marvinthepa
PR: shakacode/shakapacker#520
File: lib/shakapacker/utils/manager.rb:19-19
Timestamp: 2024-10-09T10:46:03.499Z
Learning: In the Shakapacker project, within `lib/shakapacker/runner.rb`, `PackageJson.read(app_path)` is used to read `package.json` using the application path, ensuring the correct path is used regardless of the current working directory.
Applied to files:
spec/shakapacker/config_path_spec.rblib/install/bin/shakapacker-rspackspec/shakapacker/rspack_runner_spec.rblib/shakapacker/rspack_runner.rblib/shakapacker/runner.rblib/install/bin/shakapacker
📚 Learning: 2024-10-09T10:46:03.499Z
Learnt from: marvinthepa
PR: shakacode/shakapacker#520
File: lib/shakapacker/utils/manager.rb:19-19
Timestamp: 2024-10-09T10:46:03.499Z
Learning: In `lib/install/template.rb` of the Shakapacker project, calls to `PackageJson.read` are wrapped inside `Dir.chdir(Rails.root)`, ensuring that `package.json` is read from the Rails root directory.
Applied to files:
spec/shakapacker/config_path_spec.rblib/install/bin/shakapacker-rspackspec/shakapacker/rspack_runner_spec.rblib/shakapacker/rspack_runner.rblib/shakapacker/runner.rblib/install/bin/shakapacker
🧬 Code Graph Analysis (25)
test/package/rspack/index.test.js (3)
package/rspack/environments/base.js (5)
require(4-4)require(5-5)require(8-8)rules(10-10)config(11-11)package/rspack/index.js (3)
generateRspackConfig(15-27)rules(8-8)config(9-9)package/rspack/rules/index.js (1)
rules(5-5)
package/rspack/environments/test.js (3)
package/rspack/environments/development.js (3)
require(1-1)require(5-5)baseConfig(3-3)package/rspack/environments/base.js (3)
require(4-4)require(5-5)require(8-8)package/rspack/index.js (1)
baseConfig(7-7)
package/rspack/rules/stylus.js (2)
package/rspack/index.js (1)
inliningCss(13-13)package/utils/getStyleRule.js (1)
getStyleRule(5-44)
spec/shakapacker/config_path_spec.rb (3)
spec/support/package_json_helpers.rb (1)
within_temp_directory(15-19)lib/shakapacker/runner.rb (2)
run(9-86)run(10-14)lib/shakapacker/rspack_runner.rb (1)
run(20-49)
lib/install/config/webpack/rspack.config.js (1)
package/rspack/index.js (1)
generateRspackConfig(15-27)
package/rspack/rules/css.js (5)
package/rspack/rules/less.js (4)
require(2-2)require(3-3)require(4-4)inliningCss(5-5)package/rspack/rules/sass.js (4)
require(1-1)require(2-2)inliningCss(3-3)rspack(5-5)package/utils/getStyleRule.js (2)
require(2-2)inliningCss(3-3)package/rspack/environments/base.js (8)
require(4-4)require(5-5)require(8-8)require(9-9)require(12-12)require(13-13)require(164-166)rspack(145-145)package/rspack/index.js (1)
inliningCss(13-13)
package/rules/sass.js (4)
package/utils/getStyleRule.js (1)
require(2-2)package/rules/css.js (1)
require(1-1)package/rules/less.js (2)
require(2-2)require(3-3)package/rules/stylus.js (2)
require(2-2)require(3-3)
package/rspack/environments/production.js (1)
package/rspack/environments/base.js (2)
plugins(77-140)rspack(145-145)
package/rspack/rules/index.js (6)
package/rspack/rules/css.js (2)
require(1-1)require(3-3)package/rspack/rules/less.js (3)
require(2-2)require(3-3)require(4-4)package/rspack/rules/sass.js (2)
require(1-1)require(2-2)package/rspack/rules/stylus.js (3)
require(1-1)require(2-2)require(3-3)package/rspack/environments/base.js (4)
require(4-4)require(5-5)require(8-8)rules(10-10)package/rspack/index.js (1)
rules(8-8)
package/rspack/rules/less.js (2)
package/utils/helpers.js (1)
resolvedPath(5-14)package/utils/getStyleRule.js (1)
getStyleRule(5-44)
lib/install/config/rspack/rspack.config.js (2)
lib/install/config/webpack/rspack.config.js (1)
rspackConfig(4-4)package/rspack/index.js (1)
generateRspackConfig(15-27)
lib/shakapacker/dev_server_runner.rb (2)
lib/shakapacker/configuration.rb (1)
bundler(91-93)lib/shakapacker/runner.rb (2)
get_bundler_type(44-46)package_json(31-33)
spec/shakapacker/rspack_runner_spec.rb (3)
spec/support/package_json_helpers.rb (1)
within_temp_directory(15-19)lib/shakapacker/runner.rb (3)
run(9-86)run(10-14)package_json(31-33)lib/shakapacker/rspack_runner.rb (1)
run(20-49)
lib/shakapacker/rspack_runner.rb (1)
lib/shakapacker/runner.rb (3)
run(9-86)run(10-14)package_json(31-33)
package/rspack/index.js (3)
package/rspack/environments/base.js (5)
require(4-4)require(5-5)require(8-8)rules(10-10)config(11-11)package/rspack/rules/index.js (1)
rules(5-5)package/env.js (1)
nodeEnv(11-12)
lib/shakapacker/runner.rb (1)
lib/shakapacker/configuration.rb (2)
rspack?(95-97)bundler(91-93)
package/rspack/rules/file.js (1)
package/rules/jscommon.js (1)
sourcePath(3-6)
package/rspack/rules/erb.js (1)
package/utils/helpers.js (1)
resolvedPath(5-14)
package/rules/less.js (4)
package/utils/getStyleRule.js (1)
require(2-2)package/rules/css.js (1)
require(1-1)package/rules/stylus.js (2)
require(2-2)require(3-3)package/rules/sass.js (2)
require(3-3)require(4-4)
package/rules/stylus.js (4)
package/utils/getStyleRule.js (1)
require(2-2)package/rules/css.js (1)
require(1-1)package/rules/less.js (2)
require(2-2)require(3-3)package/rules/sass.js (2)
require(3-3)require(4-4)
package/utils/getStyleRule.js (5)
package/rspack/rules/css.js (3)
inliningCss(2-2)require(1-1)require(3-3)package/rules/css.js (1)
require(1-1)package/rules/less.js (2)
require(2-2)require(3-3)package/rules/stylus.js (2)
require(2-2)require(3-3)package/rules/sass.js (2)
require(3-3)require(4-4)
package/rspack/rules/sass.js (1)
package/utils/getStyleRule.js (1)
getStyleRule(5-44)
package/rules/css.js (4)
package/utils/getStyleRule.js (1)
require(2-2)package/rules/less.js (2)
require(2-2)require(3-3)package/rules/stylus.js (2)
require(2-2)require(3-3)package/rules/sass.js (2)
require(3-3)require(4-4)
package/rspack/environments/base.js (5)
package/rspack/rules/css.js (3)
require(1-1)require(3-3)rspack(5-5)package/utils/getStyleRule.js (1)
require(2-2)package/rspack/rules/index.js (3)
require(3-3)rules(5-5)file(62-62)package/rspack/index.js (3)
rules(8-8)config(9-9)path(23-23)package/env.js (1)
isProduction(13-13)
package/rspack/environments/development.js (2)
package/rspack/environments/base.js (7)
require(4-4)require(5-5)require(8-8)require(9-9)require(12-12)require(13-13)require(164-166)package/rspack/index.js (1)
baseConfig(7-7)
🪛 ESLint
test/package/rspack/index.test.js
[error] 1-1: Replace ·generateRspackConfig,·config,·rules· with ⏎··generateRspackConfig,⏎··config,⏎··rules⏎
(prettier/prettier)
[error] 3-4: Delete ⏎
(prettier/prettier)
[error] 19-19: Replace plugin·=>·plugin.constructor.name) with ⏎······(plugin)·=>·plugin.constructor.name
(prettier/prettier)
[error] 20-20: Insert )⏎
(prettier/prettier)
[error] 23-23: Delete ····
(prettier/prettier)
[error] 31-31: Delete ····
(prettier/prettier)
[error] 33-34: Replace rule·=>·⏎····· with ⏎······(rule)·=>
(prettier/prettier)
[error] 34-34: Avoid having conditionals in tests
(jest/no-conditional-in-test)
[error] 34-34: Avoid having conditionals in tests
(jest/no-conditional-in-test)
[error] 48-49: Replace rule·=>·⏎ with ⏎······(rule)·=>⏎··
(prettier/prettier)
[error] 49-49: Avoid having conditionals in tests
(jest/no-conditional-in-test)
[error] 49-49: Avoid having conditionals in tests
(jest/no-conditional-in-test)
[error] 49-49: Avoid having conditionals in tests
(jest/no-conditional-in-test)
[error] 54-54: Insert ⏎
(prettier/prettier)
package/rspack/environments/test.js
[error] 14-14: Insert ⏎
(prettier/prettier)
package/rspack/rules/stylus.js
[error] 18-18: Insert ⏎
(prettier/prettier)
package/rspack/rules/css.js
[error] 8-8: Insert ⏎·
(prettier/prettier)
[error] 9-9: Insert ····
(prettier/prettier)
[error] 10-10: Replace type:·'javascript/auto' with ····type:·"javascript/auto"
(prettier/prettier)
[error] 11-11: Insert ····
(prettier/prettier)
[error] 12-12: Insert ····
(prettier/prettier)
[error] 13-13: Insert ····
(prettier/prettier)
[error] 14-14: Insert ····
(prettier/prettier)
[error] 15-15: Replace ······ with ··········
(prettier/prettier)
[error] 16-16: Insert ····
(prettier/prettier)
[error] 17-17: Replace ········ with ············
(prettier/prettier)
[error] 18-18: Insert ····
(prettier/prettier)
[error] 19-19: Replace ······ with ··········
(prettier/prettier)
[error] 20-20: Replace ···· with ········
(prettier/prettier)
[error] 21-21: Insert ····
(prettier/prettier)
[error] 22-22: Replace }·:·null with ····}⏎··:·null⏎
(prettier/prettier)
package/rspack/environments/production.js
[error] 16-16: Unexpected require().
(global-require)
[error] 41-41: Insert ⏎
(prettier/prettier)
package/rspack/rules/index.js
[error] 45-45: Delete ··
(prettier/prettier)
[error] 58-58: Expected 1 empty line after require statement not followed by another require.
(import/newline-after-import)
[error] 62-62: Expected 1 empty line after require statement not followed by another require.
(import/newline-after-import)
[error] 66-66: Expected 1 empty line after require statement not followed by another require.
(import/newline-after-import)
[error] 69-69: Insert ⏎
(prettier/prettier)
package/rspack/rules/raw.js
[error] 5-5: Insert ⏎
(prettier/prettier)
package/rspack/rules/less.js
[error] 27-27: Insert ⏎
(prettier/prettier)
package/rspack/index.js
[error] 40-40: Insert ⏎
(prettier/prettier)
package/rspack/rules/file.js
[error] 29-29: Insert ⏎
(prettier/prettier)
package/rspack/rules/erb.js
[error] 21-21: Insert ⏎
(prettier/prettier)
package/utils/getStyleRule.js
[error] 15-15: Delete ····
(prettier/prettier)
[error] 17-17: Replace ·extractionLoader·||· with ⏎······extractionLoader·||
(prettier/prettier)
[error] 38-38: Replace 'javascript/auto' with "javascript/auto"
(prettier/prettier)
package/rspack/rules/sass.js
[error] 19-19: Insert ⏎
(prettier/prettier)
package/rspack/environments/base.js
[error] 80-80: Replace '/' with "/"
(prettier/prettier)
[error] 86-86: Delete ········
(prettier/prettier)
[error] 88-88: Replace (file with ((file)
(prettier/prettier)
[error] 91-91: Delete ········
(prettier/prettier)
[error] 94-134: iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.
(no-restricted-syntax)
[error] 94-94: Replace entrypoints with ⏎··········entrypoints⏎········
(prettier/prettier)
[error] 95-95: Replace file·=>·file.endsWith('.js' with (file)·=>·file.endsWith(".js"
(prettier/prettier)
[error] 96-96: Replace file·=>·file.endsWith('.css') with (file)·=>⏎············file.endsWith(".css"
(prettier/prettier)
[error] 97-97: Insert )⏎
(prettier/prettier)
[error] 102-102: Delete ············
(prettier/prettier)
[error] 104-104: Replace '/' with "/"
(prettier/prettier)
[error] 106-106: Delete ············
(prettier/prettier)
[error] 114-114: Delete ············
(prettier/prettier)
[error] 119-119: Replace '' with ""
(prettier/prettier)
[error] 123-123: Delete ············
(prettier/prettier)
[error] 127-127: Delete ··········
(prettier/prettier)
[error] 136-136: Delete ········
(prettier/prettier)
[error] 219-219: Insert ⏎
(prettier/prettier)
package/rspack/environments/development.js
[error] 4-4: Delete ·
(prettier/prettier)
[error] 5-5: 'runningWebpackDevServer' is assigned a value but never used.
(no-unused-vars)
[error] 19-19: Insert ⏎
(prettier/prettier)
🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md
11-11: Unordered list style
Expected: asterisk; Actual: dash
(MD004, ul-style)
docs/rspack.md
31-31: Multiple headings with the same content
(MD024, no-duplicate-heading)
🪛 LanguageTool
docs/rspack.md
[grammar] ~3-~3: There might be a mistake here.
Context: ...Rspack is a fast Rust-based web bundler with webpack-compatible API that can signifi...
(QB_NEW_EN)
[grammar] ~85-~85: There might be a mistake here.
Context: ...pack.CssExtractRspackPlugininstead ofmini-css-extract-plugin` - Asset Handling: Uses built-in asset mo...
(QB_NEW_EN)
[grammar] ~138-~138: There might be a mistake here.
Context: ...rovides: - 2-10x faster cold builds - 5-20x faster incremental builds - **...
(QB_NEW_EN)
[grammar] ~139-~139: There might be a mistake here.
Context: ...ds - 5-20x faster incremental builds - Faster HMR (Hot Module Replacement) - ...
(QB_NEW_EN)
[grammar] ~140-~140: There might be a mistake here.
Context: ... Faster HMR (Hot Module Replacement) - Lower memory usage ## Migration Check...
(QB_NEW_EN)
🔇 Additional comments (25)
package/utils/getStyleRule.js (1)
46-46: Export shape change verified: no breaking imports detectedI’ve confirmed that every
requireorimportofgetStyleRuleuses destructuring, so switching tomodule.exports = { getStyleRule }will not break any callers. All import sites already follow the pattern:
- package/rspack/rules/{less,sass,stylus}.js
- package/rules/{css,sass,stylus,less}.js
No further changes are needed.
package/rules/sass.js (1)
3-3: Named export verified across all call sites
No remaining default‐import uses ofgetStyleRulewere found. ✅ Approved.package/rules/stylus.js (1)
3-3: Consistent shift to named export. LGTM.Matches the new export shape; usage below remains unchanged.
package/rules/less.js (1)
3-3: Named import update is correct and consistent with other rule files.Nothing else to change here.
package/rules/css.js (1)
1-1: Named import aligns with the updated API.module.exports usage remains intact, so this looks good.
package/rspack/rules/erb.js (1)
3-3: Windows runner prefix logic is sensible.Using "ruby " on Windows and nothing elsewhere to invoke bin/rails is a pragmatic approach. Consider adding a note in docs/tests to ensure this behavior is covered on Windows CI.
package/rspack/environments/test.js (1)
5-12: LGTM for test env overrides.Development mode with cheap source maps and ignoring node_modules for watch aligns well with test runs.
lib/install/config/rspack/rspack.config.js (1)
1-6: Straightforward and correct Rspack config export.Using the public generateRspackConfig API keeps the app config minimal and consistent with docs.
lib/install/config/shakapacker.yml (1)
42-44: Bundler knob looks good and is self-documented.Defaults to webpack while enabling rspack via a single switch. Nice addition.
lib/install/bin/shakapacker-rspack (1)
10-13: LGTM overall.Sets app root, chdirs, and delegates to RspackRunner with args. Behavior matches existing bin launcher patterns.
lib/install/config/webpack/rspack.config.js (2)
2-6: Straightforward Rspack config export — looks goodMinimal and conventional usage of generateRspackConfig(); no issues spotted with correctness.
1-6: Confirm intent: two Rspack config templates under different directoriesThis file lives under webpack/rspack.config.js. If there’s also lib/install/config/rspack/rspack.config.js, please confirm both are needed and won’t drift. If only one should be installed per bundler, consider deduplicating or symlinking.
package/rspack/environments/development.js (1)
11-16: devMiddleware.writeToDisk is supported in Rspack devServerRspack’s devServer honors the webpack-dev-middleware-style
writeToDiskoption (boolean or function). Your existing configuration is valid—no changes needed. Just ensure you’re using a recent Rspack release to include any bug fixes related to this feature.package/rspack/environments/production.js (1)
7-11: Confirm Rspack parity forbailand production source maps.
bail: trueis a Webpack setting; Rspack generally mirrors it, but please confirm it’s honored in your tested versions.devtool: "source-map"in production is fine, but consider your defaults (cost vs. debuggability) depending on typical app size.spec/shakapacker/rspack_runner_spec.rb (2)
39-45: Assertion approach looks good; validates the right package manager wrapper is used.The start_with check against the manager name is a pragmatic smoke test across package managers.
74-86: Solid command execution verification; also confirms package manager validation is called.Stubbing Kernel.exec and asserting invocation with Shakapacker::Compiler.env matches the runner’s contract.
lib/shakapacker/runner.rb (2)
21-26: Good move: central Configuration drives bundler selection and environment.Using Configuration with Pathname for root/config paths is clean and sets up bundler-aware behavior.
35-42: Bundler-aware config resolution is clear and maintainable.The separation between rspack and webpack discovery keeps concerns tidy.
package/rspack/rules/less.js (1)
12-26: LGTM: Leverages getStyleRule; aligns less handling with css/postcss pipeline.Using getStyleRule ensures consistent css-loader/postcss behavior, and the extraction loader swaps correctly based on inliningCss.
lib/shakapacker/rspack_runner.rb (3)
20-49: Run flow and env wiring look correct.
- Respects existing NODE_OPTIONS and appends debug/trace flags.
- Adds --config unless invoking a non-build/info command.
- Executes within @app_path and uses Shakapacker::Compiler.env.
53-55: Command construction via package manager wrapper is appropriate.Delegating to package_json.manager.native_exec_command("rspack") keeps PM-specific logic centralized.
7-18: Verify commands that should skip --configThe guard currently excludes only help/version/info. According to the Rspack CLI docs, the
previewcommand (which serves already-built output) does not accept a--configflag either. Please:
- Run
npx rspack --helpandnpx rspack preview --helpto confirm which subcommands do not support--config- Update lib/shakapacker/rspack_runner.rb in the
RSPACK_COMMANDSconstant to:
- Add
"preview"- Verify whether
"info"/"i"is still a valid CLI command (and remove it if obsolete)lib/install/bin/shakapacker (1)
18-24: LGTM! Clean implementation of bundler selection.The conditional loading of runners based on configuration is well-implemented. Loading the runner classes only when needed is a good performance optimization.
package/rspack/index.js (1)
15-20: Clear and appropriate error handling for multiple configs.The validation preventing multiple extra configs with a clear error message is well-implemented. This prevents confusion and ensures users properly merge configs before passing them to Shakapacker.
package/rspack/environments/base.js (1)
36-41: Good defensive check for problematic configuration.The validation preventing source_entry_path of "/" with nested_entries is excellent. It prevents accidentally creating packs for every source file, which would be a performance disaster.
| def bundler | ||
| fetch(:bundler) | ||
| end | ||
|
|
||
| def rspack? | ||
| bundler == "rspack" | ||
| end | ||
|
|
||
| def webpack? | ||
| bundler == "webpack" | ||
| end | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Default bundler to “webpack” and normalize value to avoid nil/unknown states
If :bundler is missing in existing apps’ shakapacker.yml, bundler returns nil and both rspack?/webpack? will be false. Defaulting to “webpack” preserves backward compatibility and normalizing case avoids subtle config issues.
Apply this diff:
- def bundler
- fetch(:bundler)
- end
+ def bundler
+ raw = fetch(:bundler)
+ value = raw.to_s.strip.downcase
+ value.empty? ? "webpack" : value
+ end🤖 Prompt for AI Agents
In lib/shakapacker/configuration.rb around lines 91 to 102, the bundler method
can return nil which makes rspack?/webpack? both false; update bundler to
default to "webpack" and normalize the value (e.g., fetch(:bundler,
"webpack").to_s.downcase) so missing or differently-cased config entries still
work, and ensure rspack? and webpack? compare against lowercase strings
("rspack" / "webpack").
| cmd += ["--config", @webpack_config] | ||
| cmd += ["--progress", "--color"] if @pretty | ||
|
|
||
| # Default behavior of webpack-dev-server is @hot = true | ||
| cmd += ["--hot", "only"] if @hot == "only" | ||
| cmd += ["--no-hot"] if !@hot | ||
|
|
||
| # Add bundler-specific flags | ||
| bundler = get_bundler_type | ||
| if bundler == "webpack" | ||
| cmd += ["--progress", "--color"] if @pretty | ||
| # Default behavior of webpack-dev-server is @hot = true | ||
| cmd += ["--hot", "only"] if @hot == "only" | ||
| cmd += ["--no-hot"] if !@hot | ||
| elsif bundler == "rspack" | ||
| # Rspack supports --hot but not --no-hot or --progress/--color | ||
| cmd += ["--hot"] if @hot && @hot != false | ||
| # For rspack, HMR control should be done via config file if needed | ||
| end |
There was a problem hiding this comment.
Don’t pass --config to rspack “serve” (incompatible with Rspack commands).
You always append --config, but RspackRunner explicitly avoids this for Rspack commands. Passing --config to rspack serve breaks the CLI.
- cmd += ["--config", @webpack_config]
-
- # Add bundler-specific flags
- bundler = get_bundler_type
- if bundler == "webpack"
+ # Add bundler-specific flags
+ bundler = get_bundler_type
+ if bundler == "webpack"
+ cmd += ["--config", @webpack_config]
cmd += ["--progress", "--color"] if @pretty
# Default behavior of webpack-dev-server is @hot = true
cmd += ["--hot", "only"] if @hot == "only"
cmd += ["--no-hot"] if !@hot
elsif bundler == "rspack"
# Rspack supports --hot but not --no-hot or --progress/--color
cmd += ["--hot"] if @hot && @hot != false
# For rspack, HMR control should be done via config file if needed
endFollow-up: Consider adding a spec to assert we do not pass --config when bundler is rspack.
📝 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.
| cmd += ["--config", @webpack_config] | |
| cmd += ["--progress", "--color"] if @pretty | |
| # Default behavior of webpack-dev-server is @hot = true | |
| cmd += ["--hot", "only"] if @hot == "only" | |
| cmd += ["--no-hot"] if !@hot | |
| # Add bundler-specific flags | |
| bundler = get_bundler_type | |
| if bundler == "webpack" | |
| cmd += ["--progress", "--color"] if @pretty | |
| # Default behavior of webpack-dev-server is @hot = true | |
| cmd += ["--hot", "only"] if @hot == "only" | |
| cmd += ["--no-hot"] if !@hot | |
| elsif bundler == "rspack" | |
| # Rspack supports --hot but not --no-hot or --progress/--color | |
| cmd += ["--hot"] if @hot && @hot != false | |
| # For rspack, HMR control should be done via config file if needed | |
| end | |
| # Add bundler-specific flags | |
| bundler = get_bundler_type | |
| if bundler == "webpack" | |
| cmd += ["--config", @webpack_config] | |
| cmd += ["--progress", "--color"] if @pretty | |
| # Default behavior of webpack-dev-server is @hot = true | |
| cmd += ["--hot", "only"] if @hot == "only" | |
| cmd += ["--no-hot"] if !@hot | |
| elsif bundler == "rspack" | |
| # Rspack supports --hot but not --no-hot or --progress/--color | |
| cmd += ["--hot"] if @hot && @hot != false | |
| # For rspack, HMR control should be done via config file if needed | |
| end |
🤖 Prompt for AI Agents
In lib/shakapacker/dev_server_runner.rb around lines 78 to 91, the code
unconditionally appends "--config", which is incompatible with rspack's "serve"
command; change the logic so that "--config", @webpack_config is only added when
the bundler is "webpack" (not when get_bundler_type returns "rspack"); adjust
the ordering so bundler is checked before adding config or move the config
append into the webpack branch; also add/adjust a spec that asserts "--config"
is not present in the generated command when bundler is rspack.
| @@ -0,0 +1,29 @@ | |||
| const { dirname } = require("path") | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Make filename generation cross-platform safe and pick the most specific strip path.
- Avoid splitting/replacing with hard-coded "/" (breaks on Windows).
- Guard when no strip path matches (currently
${undefined}gets stringified). - Prefer the longest matching strip path over the first match to avoid broader prefixes shadowing specific ones.
- Rename the local
pathvariable for clarity.
Apply this diff:
-const { dirname } = require("path")
+const { dirname, isAbsolute, resolve, relative, sep, posix } = require("path")
@@
- filename: (pathData) => {
- const path = dirname(pathData.filename)
- const stripPaths = [...additionalPaths, sourcePath]
-
- const selectedStripPath = stripPaths.find((includePath) =>
- path.startsWith(includePath)
- )
-
- const folders = path
- .replace(`${selectedStripPath}`, "")
- .split("/")
- .filter(Boolean)
-
- const foldersWithStatic = ["static", ...folders].join("/")
- return `${foldersWithStatic}/[name]-[hash][ext][query]`
- }
+ filename: (pathData) => {
+ const assetDir = dirname(pathData.filename)
+ const stripPaths = [...(additionalPaths || []), sourcePath].filter(Boolean)
+
+ // Normalize to absolute paths relative to CWD if needed
+ const stripPathsAbs = stripPaths.map((p) => (isAbsolute(p) ? p : resolve(process.cwd(), p)))
+
+ // Choose the longest matching prefix (most specific)
+ const selectedStripPath = stripPathsAbs.reduce((best, p) => {
+ return assetDir.startsWith(p) && (!best || p.length > best.length) ? p : best
+ }, undefined)
+
+ // Compute relative path safely; if no match, default to no subfolders
+ const relOsPath = selectedStripPath ? relative(selectedStripPath, assetDir) : ""
+ const relPosix = relOsPath
+ ? relOsPath.split(sep).filter(Boolean).join("/")
+ : ""
+
+ const base = "static"
+ const foldersWithStatic = relPosix ? posix.join(base, relPosix) : base
+ return `${foldersWithStatic}/[name]-[hash][ext][query]`
+ }Also applies to: 12-26
🤖 Prompt for AI Agents
In package/rspack/rules/file.js around lines 1 and 12-26, the filename
generation currently splits paths on hard-coded "/" and uses a variable named
`path` plus may stringify `undefined` when no strip path matches; update it to
use Node's path utilities (require("path") already imported) to perform
cross-platform-safe operations (e.g., path.sep, path.dirname, path.basename,
path.join) instead of manual string splitting, rename the local `path` variable
to something clearer (e.g., candidatePath or fullPath) to avoid shadowing the
required module, select the longest matching strip path when stripping prefixes
(compare lengths) rather than the first match, and guard the case where no strip
path matches so you don't interpolate `${undefined}`—fall back to the original
basename or a safe default.
| // Extract the first loader (usually the extraction loader) and remaining preprocessors | ||
| const [extractionLoader, ...otherPreprocessors] = preprocessors | ||
|
|
||
| // Fallback to mini-css-extract-plugin if no extraction loader provided (for webpack compatibility) | ||
| const finalExtractionLoader = extractionLoader || | ||
| (inliningCss ? "style-loader" : require("mini-css-extract-plugin").loader) | ||
|
|
There was a problem hiding this comment.
Potential loader ordering bug: treating the first preprocessor as an extraction loader will misorder sass/less/stylus loaders.
Call sites typically pass preprocessors (e.g., sass-loader, less-loader) — not an extraction loader. Using the first element as the extraction loader will place sass-loader in the wrong position. Fix by detecting a true extraction loader (style-loader or mini-css-extract) and otherwise treating the first element as a regular preprocessor. Also align with Prettier’s style for quotes/whitespace.
- // Extract the first loader (usually the extraction loader) and remaining preprocessors
- const [extractionLoader, ...otherPreprocessors] = preprocessors
-
- // Fallback to mini-css-extract-plugin if no extraction loader provided (for webpack compatibility)
- const finalExtractionLoader = extractionLoader ||
- (inliningCss ? "style-loader" : require("mini-css-extract-plugin").loader)
+ // Optionally accept a leading extraction loader; otherwise, fallback based on inliningCss.
+ const [maybeExtraction, ...restPreprocessors] = preprocessors
+ const isExtractionLoader = (ldr) => {
+ if (!ldr) return false
+ if (typeof ldr === "string") {
+ return ldr.includes("style-loader") || ldr.includes("mini-css-extract-plugin")
+ }
+ if (typeof ldr === "object" && ldr.loader) {
+ const ll = String(ldr.loader)
+ return ll.includes("style-loader") || ll.includes("mini-css-extract-plugin")
+ }
+ return false
+ }
+ const extractionLoader = isExtractionLoader(maybeExtraction) ? maybeExtraction : null
+ const otherPreprocessors = extractionLoader ? restPreprocessors : [maybeExtraction, ...restPreprocessors].filter(Boolean)
+ // Fallback to mini-css-extract-plugin if no extraction loader provided (for webpack compatibility)
+ const finalExtractionLoader =
+ extractionLoader || (inliningCss ? "style-loader" : require("mini-css-extract-plugin").loader)
@@
- type: 'javascript/auto', // Required for rspack CSS extraction
+ type: "javascript/auto", // Required for rspack CSS extractionOptionally, consider setting type only for Rspack-specific rules to avoid touching Webpack defaults, but this is non-blocking if current tests pass.
Also applies to: 38-38
🧰 Tools
🪛 ESLint
[error] 15-15: Delete ····
(prettier/prettier)
[error] 17-17: Replace ·extractionLoader·||· with ⏎······extractionLoader·||
(prettier/prettier)
🤖 Prompt for AI Agents
In package/utils/getStyleRule.js around lines 13-19 (and also check line 38),
the code incorrectly assumes the first element of the preprocessors array is the
extraction loader which misorders actual preprocessors like sass/less; change
the logic to detect whether any element is a true extraction loader
(style-loader or mini-css-extract-plugin.loader) and only treat that element as
the extraction loader, otherwise treat all entries as normal preprocessors and
preserve their original order; keep the existing fallback to style-loader or
mini-css-extract-plugin.loader when no extraction loader is present (for
webpack), update string/quote/whitespace to match Prettier style, and optionally
only set rule.type for Rspack-specific code (non-blocking).
| it "uses webpack config path" do | ||
| runner = Shakapacker::WebpackRunner.new([]) | ||
| expect(runner.instance_variable_get(:@webpack_config)).to match(/webpack\.config\.js$/) | ||
| end | ||
| end |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Make the config-path assertion resilient to .ts configs.
Runner#find_webpack_config checks both .ts and .js. The current expectation fails if a TS config exists.
- expect(runner.instance_variable_get(:@webpack_config)).to match(/webpack\.config\.js$/)
+ expect(runner.instance_variable_get(:@webpack_config)).to match(%r{webpack\.config\.(js|ts)$})🤖 Prompt for AI Agents
In spec/shakapacker/config_path_spec.rb around lines 27 to 31, the test's
expectation only matches webpack.config.js and will fail when a TypeScript
config (webpack.config.ts) is present; update the assertion to accept either .js
or .ts by matching webhook config filenames with a regex that allows both
extensions (e.g., match /\.webpack\.config\.(js|ts)$/ or more simply
/\.config\.(js|ts)$/) so the spec is resilient to .ts configs.
| context "with no config files present" do | ||
| before do | ||
| FileUtils.rm_f("config/webpack/webpack.config.js") | ||
| end | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Ensure no rspack/webpack configs exist for this negative test.
For the “no config files present” case, also remove .ts variants and any rspack configs so the test doesn’t pass/fail depending on fixture content.
- before do
- FileUtils.rm_f("config/webpack/webpack.config.js")
- end
+ before do
+ %w[
+ config/webpack/webpack.config.js
+ config/webpack/webpack.config.ts
+ config/rspack/rspack.config.js
+ config/rspack/rspack.config.ts
+ ].each { |f| FileUtils.rm_f(f) }
+ end📝 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.
| context "with no config files present" do | |
| before do | |
| FileUtils.rm_f("config/webpack/webpack.config.js") | |
| end | |
| context "with no config files present" do | |
| before do | |
| %w[ | |
| config/webpack/webpack.config.js | |
| config/webpack/webpack.config.ts | |
| config/rspack/rspack.config.js | |
| config/rspack/rspack.config.ts | |
| ].each { |f| FileUtils.rm_f(f) } | |
| end |
🤖 Prompt for AI Agents
In spec/shakapacker/config_path_spec.rb around lines 44 to 48, the "with no
config files present" setup only removes config/webpack/webpack.config.js;
update the before block to also remove TypeScript variants and rspack configs so
the test is deterministic: call FileUtils.rm_f for
config/webpack/webpack.config.ts, config/webpack/rspack.config.js and
config/webpack/rspack.config.ts (in addition to the existing js) to ensure no
webpack/rspack config files remain for the negative test.
| PackageJson.read.merge! { { "packageManager" => "#{manager_name}@#{manager_version}" } } | ||
|
|
There was a problem hiding this comment.
Fix invalid merge! usage and ensure package.json is read from the app path.
- PackageJson.read should be called with test_app_path (see prior learnings).
- Using merge! without parentheses turns the hash into a block argument in Ruby, causing a runtime error.
Apply this diff:
- PackageJson.read.merge! { { "packageManager" => "#{manager_name}@#{manager_version}" } }
+ PackageJson.read(test_app_path).merge!({ "packageManager" => "#{manager_name}@#{manager_version}" })If PackageJson.read returns a wrapper rather than a Hash, consider updating via an explicit setter or by updating the test_app/package.json file on disk before instantiating the runner.
📝 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.
| PackageJson.read.merge! { { "packageManager" => "#{manager_name}@#{manager_version}" } } | |
| PackageJson.read(test_app_path).merge!({ "packageManager" => "#{manager_name}@#{manager_version}" }) |
🤖 Prompt for AI Agents
In spec/shakapacker/rspack_runner_spec.rb around lines 32-33, PackageJson.read
is being called without the app path and merge! is used incorrectly as a block;
replace it with an explicit read from test_app_path and a proper hash merge or
direct assignment. For example, call pkg = PackageJson.read(test_app_path) and
then either pkg.merge!({ "packageManager" =>
"#{manager_name}@#{manager_version}" }) or pkg["packageManager"] =
"#{manager_name}@#{manager_version}"; if PackageJson.read returns a wrapper that
doesn't accept merge!/[]=, instead update the test_app's package.json on disk
(e.g. read JSON, modify the key, write file) before instantiating the runner.
| @@ -0,0 +1,54 @@ | |||
| const { generateRspackConfig, config, rules } = require("../../../package/rspack") | |||
There was a problem hiding this comment.
🛠️ Refactor suggestion
Fix ESLint/Prettier and avoid conditional logic inside tests
- Format the destructured require across lines.
- Format the plugin names mapping for readability.
- Replace inline conditional predicates in tests with a top-level helper to satisfy jest/no-conditional-in-test.
- Add trailing newline.
Apply this diff:
-const { generateRspackConfig, config, rules } = require("../../../package/rspack")
+const {
+ generateRspackConfig,
+ config,
+ rules
+} = require("../../../package/rspack")
+
+// Helper to avoid conditional logic inside tests
+const findRuleByFilename = (filename) =>
+ rules.find(
+ (rule) => rule && rule.test instanceof RegExp && rule.test.test(filename)
+ )
@@
- const rspackConfig = generateRspackConfig()
- const pluginNames = rspackConfig.plugins.map(plugin => plugin.constructor.name)
-
+ const rspackConfig = generateRspackConfig()
+ const pluginNames = rspackConfig.plugins.map(
+ (plugin) => plugin.constructor.name
+ )
+
@@
- // Should have JavaScript/TypeScript rule using builtin:swc-loader
- const jsRule = rules.find(rule =>
- rule.test && rule.test.test && rule.test.test("test.js")
- )
+ // Should have JavaScript/TypeScript rule using builtin:swc-loader
+ const jsRule = findRuleByFilename("test.js")
expect(jsRule).toBeDefined()
expect(jsRule.use[0].loader).toBe("builtin:swc-loader")
@@
- const fileRule = rules.find(rule =>
- rule && rule.test && rule.test.test && rule.test.test("image.png")
- )
+ const fileRule = findRuleByFilename("image.png")
expect(fileRule).toBeDefined()
expect(fileRule.type).toBe("asset/resource")
@@
-})
+})
+Also applies to: 19-21, 33-38, 48-53, 54-54
🧰 Tools
🪛 ESLint
[error] 1-1: Replace ·generateRspackConfig,·config,·rules· with ⏎··generateRspackConfig,⏎··config,⏎··rules⏎
(prettier/prettier)
🤖 Prompt for AI Agents
In test/package/rspack/index.test.js around lines 1 (and also apply changes at
19-21, 33-38, 48-53, 54), the test file violates ESLint/Prettier and
jest/no-conditional-in-test rules: break the destructured require onto multiple
lines for formatting, reformat the plugin names mapping into a more readable
multi-line object, extract any inline conditional predicates used inside tests
into a top-level helper function or constant and reference that helper from the
tests (removing conditional logic inside test bodies), and ensure the file ends
with a trailing newline; apply these formatting and refactor changes
consistently at the indicated line ranges.
|
Wow, thanks for the effort @craigmcnamara Definitely covers my proof of concept point in #476 (comment) The surface area of the changes is quite significant, which is what I've expected. Feels like a lot to bring into Shakapacker core and my thinking is probably that it would fit better as some plugin/adapter/fork compared to esbuild or swc integrations we have that are way leaner. I'll review in more detail and see if I can spot some good way forward. @justin808 thoughts? |
Bootstrap 5 is particularly noisy.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
package/rspack/rules/sass.js (3)
1-3: Gate Sass rule on loader availability and import helper (align with other preprocessors).If
sass-loaderisn’t installed, the current unguarded rule will break builds. ImportcanProcessand use it to guard rule creation (same pattern as Stylus/others).Apply this diff:
const { rspack } = require("@rspack/core") const { getStyleRule } = require("../../utils/getStyleRule") +const { canProcess } = require("../../utils/helpers") const inliningCss = require("../../utils/inliningCss")
7-15: Build the loader from the resolved path; keep options; future-proof for ERB variants.Construct the Sass loader using the resolved path returned by
canProcessto avoid resolution issues and to match patterns used elsewhere.Apply this diff:
-const sassLoader = { - loader: "sass-loader", - options: { - sourceMap: true, - sassOptions: { - quietDeps: true - } - } -} +const makeSassLoader = (resolvedPath) => ({ + loader: resolvedPath, + options: { + sourceMap: true, + sassOptions: { + quietDeps: true + } + } +})
17-22: Wrap export with canProcess and include .erb variants; also fixes config-time crashes.This prevents crashes when Sass isn’t used and adds support for
.scss.erb/.sass.erbwhich is common in Rails apps.Apply this diff:
-// getStyleRule handles css-loader and postcss-loader internally -// We only need to pass the extraction loader and sass-loader -module.exports = getStyleRule(/\.(scss|sass)$/i, [ - inliningCss ? "style-loader" : CssExtractRspackPlugin.loader, - sassLoader -]) +// getStyleRule handles css-loader and postcss-loader internally +// We only need to pass the extraction loader and sass-loader +module.exports = canProcess("sass-loader", (resolvedPath) => + getStyleRule(/\.(scss|sass)(\.erb)?$/i, [ + inliningCss ? "style-loader" : CssExtractRspackPlugin.loader, + makeSassLoader(resolvedPath) + ]) +)
🧹 Nitpick comments (2)
package/rspack/rules/sass.js (2)
22-22: Prettier: ensure file ends with a single newline.Static analysis flagged a missing trailing newline.
Apply this diff (if needed):
]) +
19-21: Optional: guard style-loader resolution when inlining CSS.If
inliningCssis true butstyle-loaderisn’t installed, builds will fail. You can resolve it viacanProcess('style-loader')and fallback to extraction if missing. This keeps DX consistent across projects that don’t inline.Proposed inline tweak (illustrative; adjust to your helpers’ return shape):
- inliningCss ? "style-loader" : CssExtractRspackPlugin.loader, + inliningCss + ? (canProcess("style-loader", (p) => p) || CssExtractRspackPlugin.loader) + : CssExtractRspackPlugin.loader,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
package/rspack/rules/sass.js(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
package/rspack/rules/sass.js (2)
package/rspack/index.js (1)
inliningCss(13-13)package/utils/getStyleRule.js (1)
getStyleRule(5-44)
🪛 ESLint
package/rspack/rules/sass.js
[error] 22-22: Insert ⏎
(prettier/prettier)
| const { rspack } = require("@rspack/core") | ||
| const { getStyleRule } = require("../../utils/getStyleRule") | ||
| const inliningCss = require("../../utils/inliningCss") | ||
|
|
||
| const { CssExtractRspackPlugin } = rspack | ||
|
|
||
| const sassLoader = { | ||
| loader: "sass-loader", | ||
| options: { | ||
| sourceMap: true, | ||
| sassOptions: { | ||
| quietDeps: true | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // getStyleRule handles css-loader and postcss-loader internally | ||
| // We only need to pass the extraction loader and sass-loader | ||
| module.exports = getStyleRule(/\.(scss|sass)$/i, [ | ||
| inliningCss ? "style-loader" : CssExtractRspackPlugin.loader, | ||
| sassLoader | ||
| ]) No newline at end of file |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify rules aggregator filters out falsy entries.
With canProcess("sass-loader") guarding, this module may export null/undefined when Sass isn’t installed. Ensure the aggregator filters falsy entries before passing to Rspack.
Run:
🏁 Script executed:
#!/bin/bash
# Verify rspack rules aggregation filters out falsy entries (e.g., from sass.js when sass-loader missing)
# Show the rules index to confirm filtering pattern like .filter(Boolean)
fd -a index.js package/rspack/rules | while read -r f; do
echo "==> $f"
sed -n '1,200p' "$f" | rg -n -C2 'filter\s*\(\s*Boolean\s*\)'
doneLength of output: 282
I’ll check how getStyleRule and any canProcess guards handle missing loaders to confirm if falsy entries get filtered out.
🏁 Script executed:
#!/bin/bash
set -e
echo "1) Locate and dump getStyleRule implementation:"
fd -a getStyleRule | while read -r f; do
echo "==> $f"
sed -n '1,200p' "$f"
done
echo
echo "2) Search for any canProcess usage across the repo:"
rg -n -C2 'canProcess' .Length of output: 10134
I need to confirm how Rspack rules are aggregated and whether falsy rule exports get filtered out. Let’s inspect the Rspack entry point:
🏁 Script executed:
#!/bin/bash
set -e
echo "==> Top of package/rspack/index.js"
sed -n '1,200p' package/rspack/index.js
echo
echo "==> Any .filter usage in package/rspack/index.js"
rg -n '.filter' package/rspack/index.js || echo "— no filter calls —"Length of output: 1504
Inspecting the Rspack rules aggregator for falsy-entry filtering:
🏁 Script executed:
#!/bin/bash
set -e
echo "==> package/rspack/rules/index.js"
sed -n '1,200p' package/rspack/rules/index.js || true
echo
echo "==> .filter usage in package/rspack/rules/index.js"
rg -n 'filter' package/rspack/rules/index.js || echo "— no filters —"Length of output: 1736
Add falsy filtering to the Rspack rules aggregator
The rules array in package/rspack/rules/index.js can end up containing null (e.g. when getStyleRule returns null if css-loader isn’t installed). Without filtering, Rspack will receive invalid entries.
Locations to update:
- package/rspack/rules/index.js
Suggested change:
- module.exports = rules
+ module.exports = rules.filter(Boolean)📝 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.
| const { rspack } = require("@rspack/core") | |
| const { getStyleRule } = require("../../utils/getStyleRule") | |
| const inliningCss = require("../../utils/inliningCss") | |
| const { CssExtractRspackPlugin } = rspack | |
| const sassLoader = { | |
| loader: "sass-loader", | |
| options: { | |
| sourceMap: true, | |
| sassOptions: { | |
| quietDeps: true | |
| } | |
| } | |
| } | |
| // getStyleRule handles css-loader and postcss-loader internally | |
| // We only need to pass the extraction loader and sass-loader | |
| module.exports = getStyleRule(/\.(scss|sass)$/i, [ | |
| inliningCss ? "style-loader" : CssExtractRspackPlugin.loader, | |
| sassLoader | |
| ]) | |
| // …existing imports and rule definitions… | |
| // Export only the truthy rules, filtering out any null/undefined entries | |
| module.exports = rules.filter(Boolean) |
🧰 Tools
🪛 ESLint
[error] 22-22: Insert ⏎
(prettier/prettier)
🤖 Prompt for AI Agents
In package/rspack/rules/index.js around lines 1-200, the rules array may contain
null/undefined entries (e.g., when getStyleRule returns null) which will be
passed to Rspack; update the aggregator to remove falsy values by filtering the
final rules array (use Array.prototype.filter(Boolean) or equivalent) before
exporting or passing it to the config so only valid rule objects are included.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (9)
package/rspack/rules/raw.js (2)
3-4: Tighten the resourceQuery to avoid false positives like “?draw”Current
/raw/will match any query containing “raw” (e.g.,?draw,?b=rawness). Use a stricter regex to match?raw,?raw=..., or?a=1&rawprecisely.Apply this diff:
- resourceQuery: /raw/, + resourceQuery: /^\?raw(?:$|&|=)/,
3-4: Optional: Limit “?raw” to text-like assets or document its scopeasset/source returns a string. If someone imports a binary (e.g., PNG) with “?raw”, they’ll get a string which is likely not useful. Consider restricting via test to common text extensions (e.g., .txt, .md, .svg) or explicitly document that “?raw” is intended for textual resources.
package/rspack/environments/production.js (3)
7-11: Verify compatibility of compression-webpack-plugin with Rspack.
compression-webpack-pluginis designed for Webpack. While many Webpack plugins work with Rspack, compatibility isn’t universal. Please confirm it runs correctly in your Rspack builds, or guard it behind a config flag.If needed, consider making compression configurable (enable/disable, algorithm brotli/gzip) through the project config.
13-17: Make production source maps configurable.Hard-coding
devtool: "source-map"may be undesirable for some apps due to bundle size and source exposure. Consider driving this from config (e.g., allow "hidden-source-map" or "false").Apply this diff to read from config if available:
- devtool: "source-map", + // Prefer making this configurable; fallback preserves current behavior + devtool: process.env.SHAKAPACKER_PROD_DEVTOOL || "source-map",
23-31: Optional: exclude .map from precompression or allow opt-in.Precompressing source maps increases build time and storage; many servers compress them on-the-fly. Consider excluding
.mapfiles by default or making it configurable.- test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/, + test: /\.(js|css|html|json|ico|svg|eot|otf|ttf)$/,package/rspack/environments/base.js (2)
109-111: Make filename extraction robust across OS path separators.When deriving
filename, handle both "/" and "".Apply this diff:
- const filename = file.split("/").pop() + const filename = file.split(/[\\/]/).pop()
185-186: Avoid hardcoding mode in base; derive from environment or omit.Base is merged into both dev and prod. Hardcoding
"production"increases the risk of accidentally running dev builds with prod defaults if a future change forgets to override. Derive fromisProductionor dropmodehere.Apply this diff:
- mode: "production", + mode: isProduction ? "production" : "development",Alternatively, remove the
modefrom base and set it only in environment-specific configs.package/index.d.ts (1)
85-92: Optional: Alias RuleSetRule to avoid ambiguity across bundlers.Both modules use a
RuleSetRuletype but from different sources (webpackvs@rspack/core). To reduce cognitive load and prevent accidental mixups in future edits, consider aliasing the Rspack one:-declare module 'shakapacker/rspack' { - import type { RspackOptions, RuleSetRule } from '@rspack/core' +declare module 'shakapacker/rspack' { + import type { RspackOptions, RuleSetRule as RspackRuleSetRule } from '@rspack/core' import type { Config, Env } from 'shakapacker' ... - export const rules: RuleSetRule[] + export const rules: RspackRuleSetRule[]package.json (1)
62-81: Consider making webpack an optional peer for Rspack-only installs.If Rspack usage is supported without installing webpack, requiring
"webpack"as a hard peer will create unnecessary warnings for Rspack users. Mark it optional inpeerDependenciesMeta."peerDependenciesMeta": { "@types/babel__core": { "optional": true }, "@types/webpack": { "optional": true }, "webpack-subresource-integrity": { "optional": true }, + "webpack": { + "optional": true + }, "@rspack/core": { "optional": true }, "@rspack/cli": { "optional": true }, "rspack-manifest-plugin": { "optional": true } },If webpack remains a hard requirement by design, consider adding a short note in the Rspack docs clarifying that webpack must still be present to avoid peer warnings.
Also applies to: 82-101
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (20)
CHANGELOG.md(1 hunks)lib/shakapacker/configuration.rb(1 hunks)lib/shakapacker/dev_server_runner.rb(2 hunks)package.json(4 hunks)package/index.d.ts(1 hunks)package/rspack/environments/base.js(1 hunks)package/rspack/environments/development.js(1 hunks)package/rspack/environments/production.js(1 hunks)package/rspack/environments/test.js(1 hunks)package/rspack/index.js(1 hunks)package/rspack/rules/css.js(1 hunks)package/rspack/rules/erb.js(1 hunks)package/rspack/rules/file.js(1 hunks)package/rspack/rules/index.js(1 hunks)package/rspack/rules/less.js(1 hunks)package/rspack/rules/raw.js(1 hunks)package/rspack/rules/sass.js(1 hunks)package/rspack/rules/stylus.js(1 hunks)package/utils/getStyleRule.js(2 hunks)test/package/rspack/index.test.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
- package/rspack/environments/test.js
- package/rspack/rules/index.js
- package/rspack/rules/sass.js
- test/package/rspack/index.test.js
- package/rspack/rules/erb.js
- package/rspack/rules/stylus.js
- package/rspack/rules/file.js
- lib/shakapacker/configuration.rb
- package/rspack/rules/css.js
- CHANGELOG.md
- package/rspack/rules/less.js
- lib/shakapacker/dev_server_runner.rb
- package/utils/getStyleRule.js
- package/rspack/index.js
- package/rspack/environments/development.js
🧰 Additional context used
🧬 Code Graph Analysis (3)
package/index.d.ts (3)
package/rspack/environments/base.js (2)
config(11-11)rules(10-10)package/rspack/index.js (5)
config(9-9)generateRspackConfig(15-27)baseConfig(7-7)rules(8-8)inliningCss(13-13)package/rspack/rules/index.js (1)
rules(5-5)
package/rspack/environments/production.js (1)
package/rspack/environments/base.js (8)
require(4-4)require(5-5)require(8-8)require(9-9)require(12-12)require(13-13)plugins(77-148)rspack(153-153)
package/rspack/environments/base.js (4)
package/rspack/index.js (6)
require(5-5)require(6-6)require(12-12)rules(8-8)config(9-9)path(23-23)package/rspack/rules/css.js (3)
require(2-2)require(4-4)rspack(6-6)package/rspack/rules/index.js (2)
rules(5-5)file(87-87)package/env.js (1)
isProduction(13-13)
🔇 Additional comments (5)
package/rspack/rules/raw.js (1)
1-5: LGTM: Using Rspack’s asset/source instead of raw-loader is the right moveSimple, idiomatic, and avoids the extra dependency on raw-loader. Good call.
package/rspack/environments/production.js (1)
33-48: Good use of Rspack’s built-in minimizers with a safe fallback.Leveraging
SwcJsMinimizerRspackPluginandLightningCssMinimizerRspackPluginis the right call, and the try/catch provides a sensible degrade-to-minimize behavior.package/rspack/environments/base.js (2)
155-163: Verify CssExtractRspackPlugin options; “emit” may be ignored.For MiniCssExtractPlugin (Webpack) the plugin-level “emit” option doesn’t exist; it’s typically a loader option elsewhere. Ensure
CssExtractRspackPluginsupportsemit, otherwise this has no effect and writing-to-disk in dev needs to be handled via dev-middleware/server configuration.I can adjust this to the supported option set once you confirm the intended behavior (always write CSS to disk in dev) and the Rspack version in use.
167-175: SRI usage LGTM (plugin + crossOriginLoading).Using
rspack.SubresourceIntegrityPlugingated by config and wiringoutput.crossOriginLoadingis correct.Also applies to: 195-199
package.json (1)
104-104: LGTM on engines.node bump.Raising the Node requirement to >=16 aligns with modern bundlers and avoids older platform edge-cases.
| "exports": { | ||
| ".": "./package/index.js", | ||
| "./rspack": "./package/rspack/index.js", | ||
| "./swc": "./package/swc/index.js", | ||
| "./esbuild": "./package/esbuild/index.js", | ||
| "./package.json": "./package.json" | ||
| }, |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Avoid breaking subpath import of the Babel preset by exporting it.
With the new package “exports” map, require('shakapacker/package/babel/preset.js') (which you still declare types for) will throw ERR_PACKAGE_PATH_NOT_EXPORTED. Add this subpath to maintain backward compatibility.
"exports": {
".": "./package/index.js",
"./rspack": "./package/rspack/index.js",
"./swc": "./package/swc/index.js",
"./esbuild": "./package/esbuild/index.js",
+ "./package/babel/preset.js": "./package/babel/preset.js",
"./package.json": "./package.json"
},If you prefer a cleaner alias, you could also add "./babel/preset": "./package/babel/preset.js" and update docs/usages accordingly.
Use this to locate any usages you might need to keep working:
🏁 Script executed:
#!/bin/bash
# Find imports of the preset using the legacy deep path or a potential alias
rg -n -C2 -e $'shakapacker/package/babel/preset\\.js' -e $'shakapacker/babel/preset'Length of output: 3357
Expose Babel preset subpath in package exports
To prevent require('shakapacker/package/babel/preset.js') from breaking under the new "exports" map, add the deep‐path entry in package.json:
"exports": {
".": "./package/index.js",
"./rspack": "./package/rspack/index.js",
"./swc": "./package/swc/index.js",
"./esbuild": "./package/esbuild/index.js",
+ "./package/babel/preset.js": "./package/babel/preset.js",
"./package.json": "./package.json"
},Usage sites that rely on this import include:
- lib/install/template.rb:56
- package/index.d.ts:98–100
- spec/dummy/babel.config.js:2–4
- docs/react.md (lines 31, 78, 88, 211, 219)
- docs/customizing_babel_config.md (lines 10, 25, 58)
- README.md:622–625
Optionally, you can expose a cleaner alias:
"./babel/preset": "./package/babel/preset.js"and update examples/docs accordingly.
🤖 Prompt for AI Agents
package.json around lines 17-23: the current "exports" map omits the deep-path
used by various modules (e.g. require('shakapacker/package/babel/preset.js')),
so add an exports entry that maps the package subpath to the actual file (for
example add "./package/babel/preset.js": "./package/babel/preset.js");
optionally also add a cleaner alias like "./babel/preset":
"./package/babel/preset.js" and update docs/examples to use the new alias if
desired.
| declare module 'shakapacker/rspack' { | ||
| import type { RspackOptions, RuleSetRule } from '@rspack/core' | ||
|
|
||
| export const config: Config | ||
| export function generateRspackConfig(extraConfig?: RspackOptions): RspackOptions | ||
| export const baseConfig: RspackOptions | ||
| export const env: Env | ||
| export const rules: RuleSetRule[] | ||
| export function moduleExists(packageName: string): boolean | ||
| export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null | ||
| export const inliningCss: boolean | ||
| export * from 'webpack-merge' | ||
| } |
There was a problem hiding this comment.
Fix TS scope: Config and Env are not visible inside 'shakapacker/rspack' module.
Config and Env are declared within the 'shakapacker' ambient module and are not in scope here. This will cause TS errors like “Cannot find name 'Config' | 'Env'” for consumers.
Add explicit type imports from 'shakapacker':
declare module 'shakapacker/rspack' {
- import type { RspackOptions, RuleSetRule } from '@rspack/core'
+ import type { RspackOptions, RuleSetRule } from '@rspack/core'
+ import type { Config, Env } from 'shakapacker'
export const config: Config
export function generateRspackConfig(extraConfig?: RspackOptions): RspackOptions
export const baseConfig: RspackOptions
export const env: Env
export const rules: RuleSetRule[]
export function moduleExists(packageName: string): boolean
export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null
export const inliningCss: boolean
export * from 'webpack-merge'
}📝 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.
| declare module 'shakapacker/rspack' { | |
| import type { RspackOptions, RuleSetRule } from '@rspack/core' | |
| export const config: Config | |
| export function generateRspackConfig(extraConfig?: RspackOptions): RspackOptions | |
| export const baseConfig: RspackOptions | |
| export const env: Env | |
| export const rules: RuleSetRule[] | |
| export function moduleExists(packageName: string): boolean | |
| export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null | |
| export const inliningCss: boolean | |
| export * from 'webpack-merge' | |
| } | |
| declare module 'shakapacker/rspack' { | |
| import type { RspackOptions, RuleSetRule } from '@rspack/core' | |
| import type { Config, Env } from 'shakapacker' | |
| export const config: Config | |
| export function generateRspackConfig(extraConfig?: RspackOptions): RspackOptions | |
| export const baseConfig: RspackOptions | |
| export const env: Env | |
| export const rules: RuleSetRule[] | |
| export function moduleExists(packageName: string): boolean | |
| export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null | |
| export const inliningCss: boolean | |
| export * from 'webpack-merge' | |
| } |
🤖 Prompt for AI Agents
In package/index.d.ts around lines 84 to 96, the types Config and Env referenced
inside the 'shakapacker/rspack' module are out of scope; import them from the
'shakapacker' ambient module and use those imported types in the module
declaration (i.e., add an import type { Config, Env } from 'shakapacker' at the
top of the declaration and replace any unqualified Config/Env references with
the imported names) so TypeScript can resolve those names for consumers.
| export function moduleExists(packageName: string): boolean | ||
| export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null | ||
| export const inliningCss: boolean | ||
| export * from 'webpack-merge' |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify runtime re-export of webpack-merge from 'shakapacker/rspack'.
The typings re-export everything from webpack-merge, implying runtime re-exports. Please confirm package/rspack/index.js actually re-exports webpack-merge; otherwise, the typings will be misleading.
Run this to check for a runtime re-export:
🏁 Script executed:
#!/bin/bash
# Look for any webpack-merge usage/export in the Rspack entrypoint
rg -n -C3 -g 'package/rspack/index.js' -e 'webpack-merge|module\.exports|exports\.' Length of output: 1297
Type definitions re-export webpack-merge without matching runtime exports
The declaration file package/index.d.ts currently contains:
export * from 'webpack-merge'
but package/rspack/index.js only exports config, devServer, and generateRspackConfig (the merge function is used internally but never re-exported). Please align the typings with the actual runtime API.
Locations to address:
package/index.d.ts(around line 95): revise or remove theexport * from 'webpack-merge'package/rspack/index.js: either addmerge: rspackMergetomodule.exportsor remove the type re-export
Suggested diff in package/index.d.ts:
- export * from 'webpack-merge'
+ export { merge } from 'webpack-merge'📝 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.
| export * from 'webpack-merge' | |
| export { merge } from 'webpack-merge' |
🤖 Prompt for AI Agents
In package/index.d.ts around line 95, the declaration currently re-exports
everything from 'webpack-merge' but the runtime at package/rspack/index.js only
exports config, devServer, and generateRspackConfig (merge is internal). Fix by
making the types match runtime: either remove or stop re-exporting
'webpack-merge' from package/index.d.ts so it only exports the actual rspack API
types, or modify package/rspack/index.js to add merge: rspackMerge to
module.exports so the merge type re-export is valid; update the d.ts to reflect
whichever runtime change you choose so typings and runtime exports are
consistent.
| const namespace = relative(join(rootPath), dirname(path)) | ||
| const name = join(namespace, basename(path, extname(path))) | ||
| let assetPaths = resolve(path) |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Normalize entry names to POSIX separators to avoid Windows issues.
path.join yields backslashes on Windows, which can end up in chunk names and file paths. Normalize to forward slashes for stable output.
Apply this diff:
- const namespace = relative(join(rootPath), dirname(path))
- const name = join(namespace, basename(path, extname(path)))
+ const namespace = relative(join(rootPath), dirname(path))
+ const name = join(namespace, basename(path, extname(path))).replace(/\\/g, "/")This localizes the change without touching callers. If you prefer, we can switch to path.posix utilities instead.
Also applies to: 50-60
🤖 Prompt for AI Agents
In package/rspack/environments/base.js around lines 44 to 46 (and similarly for
lines ~50-60), entry names are built with path.join which produces backslashes
on Windows; change to produce POSIX-style separators by using path.posix.join
(or call join then replace(/\\+/g, "/") on the result) when constructing
namespace/name so chunk names and asset paths always use forward slashes; ensure
you apply the same normalization to all other spots in the file where path.join
is used for entry/chunk/asset names (lines 50-60).
| fileName: config.manifestPath.split("/").pop(), // Get just the filename | ||
| publicPath: config.publicPathWithoutCDN, |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Use path.basename for cross-platform manifest file name.
String splitting by "/" breaks on Windows paths. Use basename (already imported).
Apply this diff:
- fileName: config.manifestPath.split("/").pop(), // Get just the filename
+ fileName: basename(config.manifestPath),📝 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.
| fileName: config.manifestPath.split("/").pop(), // Get just the filename | |
| publicPath: config.publicPathWithoutCDN, | |
| fileName: basename(config.manifestPath), | |
| publicPath: config.publicPathWithoutCDN, |
🤖 Prompt for AI Agents
In package/rspack/environments/base.js around lines 80 to 81, the code uses
config.manifestPath.split("/").pop() to extract the filename which fails on
Windows; replace that expression with basename(config.manifestPath) (the
basename function is already imported) so the manifest filename is derived in a
cross-platform way.
| module.exports = { | ||
| resourceQuery: /raw/, | ||
| type: "asset/source" | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify rule ordering/oneOf to prevent conflicting asset “type” rules
If another rule (e.g., generic asset/resource for images/svg/txt) also matches the same resource, webpack/Rspack can throw on conflicting “type” matches. Ensure this “?raw” rule is evaluated before generic asset rules, ideally within a oneOf group.
Run this script to check the rules aggregation and ordering:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Locate the Rspack rules index and show first ~200 lines:"
fd -a 'rules/index.js' package | while read -r f; do
echo "== $f =="
nl -ba "$f" | sed -n '1,200p'
done
echo
echo "Show where 'raw' is referenced in Rspack rules:"
rg -n -C3 --type=js 'raw' package/rspack
echo
echo "Search for generic asset rules that might conflict (asset/resource, asset):"
rg -n -C2 --type=js '\btype\s*:\s*["'\'']asset/(resource|inline|source)["'\'']' package/rspack/rules
echo
echo "If not using oneOf, check that conditions are mutually exclusive; otherwise ensure 'raw' rule precedes the generic asset rule in oneOf."Length of output: 2102
Ensure the ?raw rule runs before the generic asset/resource rule
We verified in package/rspack/rules/index.js that you’re pushing the generic file loader (file.js) before the raw rule. That means imports like image.svg?raw will match both rules and can trigger conflicting “type” errors. Move the raw rule above the file rule—or wrap them in a oneOf block so only the first matching rule applies.
• File: package/rspack/rules/index.js
• Swap the raw-loader block (require("./raw") + rules.push(raw)) to appear before rules.push(file)
Example diff:
--- a/package/rspack/rules/index.js
+++ b/package/rspack/rules/index.js
@@
- // Generic file loader for images/fonts/etc.
- rules.push(file)
+ // Raw loader for `?raw` imports—must run first to avoid type conflicts
+ const raw = require("./raw")
+ rules.push(raw)
+
+ // Generic file loader for images/fonts/etc.
+ rules.push(file)📝 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.
| module.exports = { | |
| resourceQuery: /raw/, | |
| type: "asset/source" | |
| } | |
| // Raw loader for `?raw` imports—must run first to avoid type conflicts | |
| const raw = require("./raw") | |
| rules.push(raw) | |
| // Generic file loader for images/fonts/etc. | |
| rules.push(file) |
🤖 Prompt for AI Agents
In package/rspack/rules/raw.js around lines 2-5 the raw rule is defined but in
package/rspack/rules/index.js it is pushed after the generic file rule causing
conflicts for imports like image.svg?raw; update package/rspack/rules/index.js
to require and push the raw rule (require("./raw"); rules.push(raw)) before the
file rule (require("./file")/rules.push(file)) so the ?raw resourceQuery is
matched first, or alternatively wrap the rules array in a oneOf block so only
the first matching rule is applied.
I figured this might be a bit much to add to an existing project unless you specifically wanted it. My motivation was to get onto Rspack without having to rewrite all of my asset bundling. I basically renamed my existing config from webpack to rspack and everything works as it did before, but quite a bit faster. The Rspack people have been saying that it's extremely compatible with webpack and that seems so far to be very true. I've addressed the automated feedback, so if you think it has a home here, let me know if you need anymore changes, otherwise I'll rename my repo to rspacker or something like that and get on with it. Thanks for all the shakapacker work, I couldn't have done this, if you didn't do that first. |
|
@craigmcnamara I'm super super stoked to see work being done on this. For sure, we'll get your work merged one way or another. I'm buried in a super difficult, gigantic legacy upgrade to go from sprockets to shakapacker. So my detailed comments will have to wait. I trust @tomdracz quite a bit! @tomdracz wrote:
I definitely would like this see this as a configuration choice and that we continue to support rspack so long as these compile time differences are true.
|
There was a problem hiding this comment.
@craigmcnamara does this really require a similar file or could this be handled with some conditionals?
| "webpack-dev-server": "^4.9.0 || ^5.0.0", | ||
| "webpack-merge": "^5.8.0 || ^6.0.0" | ||
| "webpack-merge": "^5.8.0 || ^6.0.0", | ||
| "webpack-subresource-integrity": "^5.1.0" |
There was a problem hiding this comment.
@craigmcnamara it's a bit weird looking, seeing both RSPack and Webpack. I don't know if there's some way we can make this possibly two-package JSONs and maybe even two npm packages. But it definitely seems a little bit weird to have them both be in there, and since they are peer dependencies, it doesn't matter so much, but they need some more thought.
|
@craigmcnamara Nice work! We're definitely interested in providing your contribution a home here. We're considering creating a new NPM package for the Rspack-supporting config & dependencies, however, to keep things cleaner & the number of peerDependencies from getting over-whelming. I've taken the liberty of cloning your branch to our repository. Can you target that branch with this PR (after making another small commit so there's still a diff?). |
|
Closing this since we haven't heard back from @craigmcnamara |
|
Keeping this open. I just heard from a giant project: startup time in dev went from 30-90 seconds to 6 seconds with RSPack! OMG |
|
See #590 |
|
Hey, sorry. I got a bit sidetracked. I noticed that you had RSpack support now, so I clicked through the changelog I almost fell out of my chair when I saw my pull requests. Thanks for adopting this! |

Summary
Implement an option to use Rspack instead of webpacker.
Pull Request checklist
Other Information
Are you interested in Rspack support, or is this too much of a departure and belongs as a fork?
Summary by CodeRabbit