Skip to content

Rspack support#582

Closed
craigmcnamara wants to merge 27 commits intoshakacode:mainfrom
mes-amis:rspack-support
Closed

Rspack support#582
craigmcnamara wants to merge 27 commits intoshakacode:mainfrom
mes-amis:rspack-support

Conversation

@craigmcnamara
Copy link
Copy Markdown

@craigmcnamara craigmcnamara commented Aug 19, 2025

Summary

Implement an option to use Rspack instead of webpacker.

Pull Request checklist

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file

Other Information

Are you interested in Rspack support, or is this too much of a departure and belongs as a fork?

Summary by CodeRabbit

  • New Features
    • Rspack added as an alternative bundler with config-driven selection, dedicated runner, dev-server support, environment configs, packaged Rspack configs/rules, and a new CLI shim.
  • Configuration
    • New bundler option added to default config (webpack or rspack); automatic bundler detection with webpack fallback; package requires Node >=16.
  • Documentation
    • Rspack integration guide and changelog entry with setup, migration, and troubleshooting.
  • Tests
    • Tests added for runner behavior, config generation, and bundler selection.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 19, 2025

Walkthrough

Adds 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

Cohort / File(s) Summary
Docs
CHANGELOG.md, docs/rspack.md
Add changelog entry and new docs describing Rspack integration, installation, configuration, migration, and troubleshooting.
Bin executables
lib/install/bin/shakapacker, lib/install/bin/shakapacker-rspack
Make runner selection config-driven; add dedicated shakapacker-rspack executable; lazy-require runners; use Pathname and env resolution.
Ruby config & runners
lib/shakapacker/configuration.rb, lib/shakapacker/runner.rb, lib/shakapacker/rspack_runner.rb, lib/shakapacker/dev_server_runner.rb
Add bundler getter methods, bundler-aware config discovery with rspack→webpack fallback, new RspackRunner, and dev-server command branching by bundler.
Install templates
lib/install/config/shakapacker.yml, lib/install/config/rspack/rspack.config.js, lib/install/config/webpack/rspack.config.js
Add bundler option (default "webpack") and Rspack config templates that export generated config via generateRspackConfig.
Package manifest
package.json
Add exports subpaths, bump Node engine to >=16, add dev/peer dependencies and peerDependenciesMeta entries for rspack packages.
Rspack package & builder
package/rspack/index.js, package/rspack/environments/*
Add generateRspackConfig API, export env/rules/helpers, and provide base/development/production/test environment configs with manifest, CSS extraction, minifiers, and optional SRI.
Rspack rules
package/rspack/rules/*
Add rules: builtin SWC JS, CSS, Sass, Less, Stylus, ERB preprocessing, file asset handling, and raw asset rule.
Shared CSS util & consumers
package/utils/getStyleRule.js, package/rules/*.js
Change getStyleRule to named export, add extraction-loader fallback and type: 'javascript/auto', and update consumers to destructure import.
Rspack package tests
test/package/rspack/index.test.js
Add tests verifying generateRspackConfig export, config shape, plugins, rules, and asset module behavior.
Ruby specs
spec/shakapacker/config_path_spec.rb, spec/shakapacker/rspack_runner_spec.rb
Add specs for bundler-based config resolution and RspackRunner command/env behavior across package managers.

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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • tomdracz
  • justin808
  • G-Rath

Poem

"I hop through configs, nibble a byte or two,
Rspack's in the garden, spry and new.
Webpack waves kindly, we share the lane,
Build, serve, and bundle — carrots for our brain.
Hop, compile, delight — a burrow full of light." 🥕✨

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 your engines field 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 ?raw imports.

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 exports

Our check shows only package/index.d.ts exists and there’s no typesVersions in package.json. Consumers importing:

  • shakapacker/rspack
  • shakapacker/swc
  • shakapacker/esbuild

will not get any .d.ts files.

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 typesVersions map in package.json to point TypeScript at these files.
  • Or embed JSDoc typedefs and generate .d.ts on 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 EOF

Keeps 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 correctness

Using 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 true

SWC 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-plugin optional, but it triggers global-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: true fall 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: false

Optional: 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 0ae9408 and 8f1d7e7.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • yarn.lock is 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.rb
  • lib/install/bin/shakapacker-rspack
  • spec/shakapacker/rspack_runner_spec.rb
  • lib/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.rb
  • spec/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.rb
  • lib/install/bin/shakapacker-rspack
  • spec/shakapacker/rspack_runner_spec.rb
  • lib/shakapacker/rspack_runner.rb
  • lib/shakapacker/runner.rb
  • lib/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.rb
  • lib/install/bin/shakapacker-rspack
  • spec/shakapacker/rspack_runner_spec.rb
  • lib/shakapacker/rspack_runner.rb
  • lib/shakapacker/runner.rb
  • lib/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 detected

I’ve confirmed that every require or import of getStyleRule uses destructuring, so switching to

module.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 of getStyleRule were 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 good

Minimal and conventional usage of generateRspackConfig(); no issues spotted with correctness.


1-6: Confirm intent: two Rspack config templates under different directories

This 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 devServer

Rspack’s devServer honors the webpack-dev-middleware-style writeToDisk option (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 for bail and production source maps.

  • bail: true is 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 --config

The guard currently excludes only help/version/info. According to the Rspack CLI docs, the preview command (which serves already-built output) does not accept a --config flag either. Please:

  • Run npx rspack --help and npx rspack preview --help to confirm which subcommands do not support --config
  • Update lib/shakapacker/rspack_runner.rb in the RSPACK_COMMANDS constant 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.

Comment on lines +91 to +102
def bundler
fetch(:bundler)
end

def rspack?
bundler == "rspack"
end

def webpack?
bundler == "webpack"
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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").

Comment on lines 78 to +91
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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
         end

Follow-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.

Suggested change
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.

Comment thread package/rspack/environments/base.js Outdated
Comment thread package/rspack/environments/development.js Outdated
Comment thread package/rspack/rules/file.js Outdated
@@ -0,0 +1,29 @@
const { dirname } = require("path")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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 path variable 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.

Comment on lines +13 to 19
// 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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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 extraction

Optionally, 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).

Comment on lines +27 to +31
it "uses webpack config path" do
runner = Shakapacker::WebpackRunner.new([])
expect(runner.instance_variable_get(:@webpack_config)).to match(/webpack\.config\.js$/)
end
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Comment on lines +44 to +48
context "with no config files present" do
before do
FileUtils.rm_f("config/webpack/webpack.config.js")
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Comment on lines +32 to +33
PackageJson.read.merge! { { "packageManager" => "#{manager_name}@#{manager_version}" } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Comment thread test/package/rspack/index.test.js Outdated
@@ -0,0 +1,54 @@
const { generateRspackConfig, config, rules } = require("../../../package/rspack")
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

@tomdracz
Copy link
Copy Markdown
Collaborator

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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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-loader isn’t installed, the current unguarded rule will break builds. Import canProcess and 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 canProcess to 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.erb which 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 inliningCss is true but style-loader isn’t installed, builds will fail. You can resolve it via canProcess('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.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f1d7e7 and 9d45f7d.

📒 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)

Comment thread package/rspack/rules/sass.js Outdated
Comment on lines +1 to +22
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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*\)'
done

Length 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.

Suggested change
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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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&raw precisely.

Apply this diff:

-  resourceQuery: /raw/,
+  resourceQuery: /^\?raw(?:$|&|=)/,

3-4: Optional: Limit “?raw” to text-like assets or document its scope

asset/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-plugin is 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 .map files 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 from isProduction or drop mode here.

Apply this diff:

-  mode: "production",
+  mode: isProduction ? "production" : "development",

Alternatively, remove the mode from 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 RuleSetRule type but from different sources (webpack vs @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 in peerDependenciesMeta.

   "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.

📥 Commits

Reviewing files that changed from the base of the PR and between 9d45f7d and 5c3dceb.

📒 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 move

Simple, 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 SwcJsMinimizerRspackPlugin and LightningCssMinimizerRspackPlugin is 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 CssExtractRspackPlugin supports emit, 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.SubresourceIntegrityPlugin gated by config and wiring output.crossOriginLoading is 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.

Comment thread package.json
Comment on lines +17 to +23
"exports": {
".": "./package/index.js",
"./rspack": "./package/rspack/index.js",
"./swc": "./package/swc/index.js",
"./esbuild": "./package/esbuild/index.js",
"./package.json": "./package.json"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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.

Comment thread package/index.d.ts
Comment on lines +84 to +96
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'
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Comment thread package/index.d.ts
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'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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 the export * from 'webpack-merge'
  • package/rspack/index.js: either add merge: rspackMerge to module.exports or 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.

Suggested change
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.

Comment on lines +44 to +46
const namespace = relative(join(rootPath), dirname(path))
const name = join(namespace, basename(path, extname(path)))
let assetPaths = resolve(path)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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).

Comment on lines +80 to +81
fileName: config.manifestPath.split("/").pop(), // Get just the filename
publicPath: config.publicPathWithoutCDN,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ 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.

Suggested change
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.

Comment on lines +2 to +5
module.exports = {
resourceQuery: /raw/,
type: "asset/source"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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.

Suggested change
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.

@craigmcnamara
Copy link
Copy Markdown
Author

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?

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.

@justin808
Copy link
Copy Markdown
Member

@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:

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 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.

image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@craigmcnamara does this really require a similar file or could this be handled with some conditionals?

Comment thread package.json
"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"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@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.

@Judahmeek
Copy link
Copy Markdown
Contributor

@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?).

@Judahmeek
Copy link
Copy Markdown
Contributor

Closing this since we haven't heard back from @craigmcnamara

@Judahmeek Judahmeek closed this Sep 19, 2025
@justin808 justin808 reopened this Sep 19, 2025
@justin808
Copy link
Copy Markdown
Member

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

@justin808 justin808 mentioned this pull request Sep 19, 2025
@justin808
Copy link
Copy Markdown
Member

See #590

@craigmcnamara
Copy link
Copy Markdown
Author

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!

@justisb justisb mentioned this pull request Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants