shakapacker 9.0.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "fileutils"
5
+
6
+ module Shakapacker
7
+ # Provides functionality to switch between webpack and rspack bundlers
8
+ class BundlerSwitcher
9
+ SHAKAPACKER_CONFIG = "config/shakapacker.yml"
10
+ CUSTOM_DEPS_CONFIG = ".shakapacker-switch-bundler-dependencies.yml"
11
+
12
+ # Default dependencies for each bundler (package names only, no versions)
13
+ DEFAULT_RSPACK_DEPS = {
14
+ dev: %w[@rspack/cli @rspack/plugin-react-refresh],
15
+ prod: %w[@rspack/core rspack-manifest-plugin]
16
+ }.freeze
17
+
18
+ DEFAULT_WEBPACK_DEPS = {
19
+ dev: %w[webpack webpack-cli webpack-dev-server @pmmmwh/react-refresh-webpack-plugin @swc/core swc-loader],
20
+ prod: %w[webpack-assets-manifest webpack-merge]
21
+ }.freeze
22
+
23
+ attr_reader :root_path
24
+
25
+ def initialize(root_path = nil)
26
+ @root_path = root_path || (defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd))
27
+ end
28
+
29
+ def current_bundler
30
+ config = load_yaml_config(config_path)
31
+ config.dig("default", "assets_bundler") || "webpack"
32
+ end
33
+
34
+ def switch_to(bundler, install_deps: false, no_uninstall: false)
35
+ unless %w[webpack rspack].include?(bundler)
36
+ raise ArgumentError, "Invalid bundler: #{bundler}. Must be 'webpack' or 'rspack'"
37
+ end
38
+
39
+ current = current_bundler
40
+ if current == bundler && !install_deps
41
+ puts "✅ Already using #{bundler}"
42
+ return
43
+ end
44
+
45
+ if current == bundler && install_deps
46
+ puts "✅ Already using #{bundler} - reinstalling dependencies as requested"
47
+ manage_dependencies(bundler, install_deps, switching: false, no_uninstall: no_uninstall)
48
+ return
49
+ end
50
+
51
+ update_config(bundler)
52
+
53
+ puts "✅ Switched from #{current} to #{bundler}"
54
+ puts ""
55
+ puts "📝 Configuration updated in #{SHAKAPACKER_CONFIG}"
56
+
57
+ manage_dependencies(bundler, install_deps, no_uninstall: no_uninstall)
58
+
59
+ puts ""
60
+ puts "🎯 Next steps:"
61
+ puts " 1. Restart your dev server: bin/dev"
62
+ puts " 2. Verify build works: bin/shakapacker"
63
+ puts ""
64
+ puts "💡 Tip: Both webpack and rspack can coexist in package.json during migration"
65
+ puts " Use --install-deps to automatically manage dependencies, or manage manually"
66
+ puts " Use --no-uninstall to skip removing old bundler packages (faster switching)"
67
+ end
68
+
69
+ def init_config
70
+ if File.exist?(custom_config_path)
71
+ puts "⚠️ #{CUSTOM_DEPS_CONFIG} already exists"
72
+ return
73
+ end
74
+
75
+ config = {
76
+ "rspack" => {
77
+ "devDependencies" => DEFAULT_RSPACK_DEPS[:dev],
78
+ "dependencies" => DEFAULT_RSPACK_DEPS[:prod]
79
+ },
80
+ "webpack" => {
81
+ "devDependencies" => DEFAULT_WEBPACK_DEPS[:dev],
82
+ "dependencies" => DEFAULT_WEBPACK_DEPS[:prod]
83
+ }
84
+ }
85
+
86
+ File.write(custom_config_path, YAML.dump(config))
87
+ puts "✅ Created #{CUSTOM_DEPS_CONFIG}"
88
+ puts ""
89
+ puts "You can now customize the dependencies for each bundler in this file."
90
+ puts "The script will automatically use these custom dependencies when switching bundlers."
91
+ end
92
+
93
+ def show_usage
94
+ current = current_bundler
95
+ puts "Current bundler: #{current}"
96
+ puts ""
97
+ puts "Usage:"
98
+ puts " rails shakapacker:switch_bundler [webpack|rspack] [OPTIONS]"
99
+ puts " rake shakapacker:switch_bundler [webpack|rspack] -- [OPTIONS]"
100
+ puts ""
101
+ puts "Options:"
102
+ puts " --install-deps Automatically install/uninstall dependencies"
103
+ puts " --no-uninstall Skip uninstalling old bundler packages (faster, keeps both bundlers)"
104
+ puts " --init-config Create #{CUSTOM_DEPS_CONFIG} with default dependencies"
105
+ puts " --help, -h Show this help message"
106
+ puts ""
107
+ puts "Examples:"
108
+ puts " # Using rails command"
109
+ puts " rails shakapacker:switch_bundler rspack --install-deps"
110
+ puts " rails shakapacker:switch_bundler webpack --install-deps --no-uninstall"
111
+ puts " rails shakapacker:switch_bundler --init-config"
112
+ puts ""
113
+ puts " # Using rake command (note the -- separator)"
114
+ puts " rake shakapacker:switch_bundler rspack -- --install-deps"
115
+ puts " rake shakapacker:switch_bundler webpack -- --install-deps --no-uninstall"
116
+ puts " rake shakapacker:switch_bundler -- --init-config"
117
+ end
118
+
119
+ private
120
+
121
+ def config_path
122
+ root_path.join(SHAKAPACKER_CONFIG)
123
+ end
124
+
125
+ def custom_config_path
126
+ root_path.join(CUSTOM_DEPS_CONFIG)
127
+ end
128
+
129
+ def load_dependencies
130
+ if File.exist?(custom_config_path)
131
+ puts "📝 Using custom dependencies from #{CUSTOM_DEPS_CONFIG}"
132
+ begin
133
+ custom = load_yaml_config(custom_config_path)
134
+ rescue Psych::SyntaxError => e
135
+ puts "❌ Error parsing #{CUSTOM_DEPS_CONFIG}: #{e.message}"
136
+ puts " Please fix the YAML syntax or delete the file to use defaults"
137
+ raise
138
+ end
139
+ rspack_deps = {
140
+ dev: custom.dig("rspack", "devDependencies") || DEFAULT_RSPACK_DEPS[:dev],
141
+ prod: custom.dig("rspack", "dependencies") || DEFAULT_RSPACK_DEPS[:prod]
142
+ }
143
+ webpack_deps = {
144
+ dev: custom.dig("webpack", "devDependencies") || DEFAULT_WEBPACK_DEPS[:dev],
145
+ prod: custom.dig("webpack", "dependencies") || DEFAULT_WEBPACK_DEPS[:prod]
146
+ }
147
+ [rspack_deps, webpack_deps]
148
+ else
149
+ [DEFAULT_RSPACK_DEPS, DEFAULT_WEBPACK_DEPS]
150
+ end
151
+ end
152
+
153
+ def update_config(bundler)
154
+ content = File.read(config_path)
155
+
156
+ # Replace assets_bundler value (handles spaces, tabs, and various quote styles)
157
+ # Only matches uncommented lines
158
+ content.gsub!(/^([ \t]*assets_bundler:[ \t]*['"]?)(webpack|rspack)(['"]?)/, "\\1#{bundler}\\3")
159
+
160
+ # Update javascript_transpiler recommendation for rspack
161
+ # Only update if not already set to swc and only on uncommented lines
162
+ if bundler == "rspack" && content !~ /^[ \t]*javascript_transpiler:[ \t]*['"]?swc['"]?/
163
+ content.gsub!(/^([ \t]*javascript_transpiler:[ \t]*['"]?)\w+(['"]?)/, "\\1swc\\2")
164
+ end
165
+
166
+ File.write(config_path, content)
167
+ end
168
+
169
+ def manage_dependencies(bundler, install_deps, switching: true, no_uninstall: false)
170
+ rspack_deps, webpack_deps = load_dependencies
171
+ deps_to_install = bundler == "rspack" ? rspack_deps : webpack_deps
172
+ deps_to_remove = bundler == "rspack" ? webpack_deps : rspack_deps
173
+
174
+ if install_deps
175
+ puts ""
176
+ puts "📦 Managing dependencies..."
177
+ puts ""
178
+
179
+ # Show what will be removed (only when switching and not no_uninstall)
180
+ if switching && !no_uninstall && (!deps_to_remove[:dev].empty? || !deps_to_remove[:prod].empty?)
181
+ puts " 🗑️ Removing:"
182
+ deps_to_remove[:dev].each { |dep| puts " - #{dep} (dev)" }
183
+ deps_to_remove[:prod].each { |dep| puts " - #{dep} (prod)" }
184
+ puts ""
185
+ elsif switching && no_uninstall
186
+ puts " ⏭️ Skipping uninstall (--no-uninstall)"
187
+ puts ""
188
+ end
189
+
190
+ # Show what will be installed
191
+ if !deps_to_install[:dev].empty? || !deps_to_install[:prod].empty?
192
+ puts " 📦 Installing:"
193
+ deps_to_install[:dev].each { |dep| puts " - #{dep} (dev)" }
194
+ deps_to_install[:prod].each { |dep| puts " - #{dep} (prod)" }
195
+ puts ""
196
+ end
197
+
198
+ # Remove old bundler dependencies (only when switching and not no_uninstall)
199
+ if switching && !no_uninstall
200
+ remove_dependencies(deps_to_remove)
201
+ end
202
+
203
+ # Install new bundler dependencies
204
+ install_dependencies(deps_to_install)
205
+
206
+ puts " ✅ Dependencies updated"
207
+ else
208
+ print_manual_dependency_instructions(bundler, deps_to_install, deps_to_remove)
209
+ end
210
+ end
211
+
212
+ def remove_dependencies(deps)
213
+ package_json = get_package_json
214
+
215
+ unless deps[:dev].empty?
216
+ unless package_json.manager.remove(deps[:dev])
217
+ puts " ⚠️ Warning: Failed to uninstall some dev dependencies"
218
+ end
219
+ end
220
+
221
+ unless deps[:prod].empty?
222
+ unless package_json.manager.remove(deps[:prod])
223
+ puts " ⚠️ Warning: Failed to uninstall some prod dependencies"
224
+ end
225
+ end
226
+ end
227
+
228
+ def install_dependencies(deps)
229
+ package_json = get_package_json
230
+
231
+ unless deps[:dev].empty?
232
+ unless package_json.manager.add(deps[:dev], type: :dev)
233
+ puts "❌ Failed to install dev dependencies"
234
+ raise "Failed to install dev dependencies"
235
+ end
236
+ end
237
+
238
+ unless deps[:prod].empty?
239
+ unless package_json.manager.add(deps[:prod], type: :production)
240
+ puts "❌ Failed to install prod dependencies"
241
+ raise "Failed to install prod dependencies"
242
+ end
243
+ end
244
+ end
245
+
246
+ def get_package_json
247
+ require "package_json"
248
+ PackageJson.read(root_path)
249
+ end
250
+
251
+ def print_manual_dependency_instructions(bundler, deps_to_install, deps_to_remove)
252
+ puts ""
253
+ puts "⚠️ Dependencies not automatically installed (use --install-deps to auto-install)"
254
+ puts ""
255
+
256
+ package_manager = detect_package_manager
257
+ target_name = bundler == "rspack" ? "rspack" : "webpack"
258
+ old_name = bundler == "rspack" ? "webpack" : "rspack"
259
+
260
+ puts "📦 To install #{target_name} dependencies, run:"
261
+ print_install_commands(package_manager, deps_to_install)
262
+ puts ""
263
+ puts "🗑️ To remove #{old_name} dependencies, run:"
264
+ print_uninstall_commands(package_manager, deps_to_remove)
265
+ end
266
+
267
+ def detect_package_manager
268
+ get_package_json.manager.binary
269
+ rescue StandardError
270
+ "npm" # Fallback to npm if detection fails
271
+ end
272
+
273
+ def print_install_commands(package_manager, deps)
274
+ case package_manager
275
+ when "yarn"
276
+ puts " yarn add --dev #{deps[:dev].join(' ')}" unless deps[:dev].empty?
277
+ puts " yarn add #{deps[:prod].join(' ')}" unless deps[:prod].empty?
278
+ when "pnpm"
279
+ puts " pnpm add -D #{deps[:dev].join(' ')}" unless deps[:dev].empty?
280
+ puts " pnpm add #{deps[:prod].join(' ')}" unless deps[:prod].empty?
281
+ when "bun"
282
+ puts " bun add --dev #{deps[:dev].join(' ')}" unless deps[:dev].empty?
283
+ puts " bun add #{deps[:prod].join(' ')}" unless deps[:prod].empty?
284
+ else # npm
285
+ puts " npm install --save-dev #{deps[:dev].join(' ')}" unless deps[:dev].empty?
286
+ puts " npm install --save #{deps[:prod].join(' ')}" unless deps[:prod].empty?
287
+ end
288
+ end
289
+
290
+ def print_uninstall_commands(package_manager, deps)
291
+ case package_manager
292
+ when "yarn"
293
+ puts " yarn remove #{deps[:dev].join(' ')}" unless deps[:dev].empty?
294
+ puts " yarn remove #{deps[:prod].join(' ')}" unless deps[:prod].empty?
295
+ when "pnpm"
296
+ puts " pnpm remove #{deps[:dev].join(' ')}" unless deps[:dev].empty?
297
+ puts " pnpm remove #{deps[:prod].join(' ')}" unless deps[:prod].empty?
298
+ when "bun"
299
+ puts " bun remove #{deps[:dev].join(' ')}" unless deps[:dev].empty?
300
+ puts " bun remove #{deps[:prod].join(' ')}" unless deps[:prod].empty?
301
+ else # npm
302
+ puts " npm uninstall #{deps[:dev].join(' ')}" unless deps[:dev].empty?
303
+ puts " npm uninstall #{deps[:prod].join(' ')}" unless deps[:prod].empty?
304
+ end
305
+ end
306
+
307
+ # Load YAML config file with Ruby version compatibility
308
+ # Ruby 3.1+ supports aliases: keyword, older versions need YAML.safe_load
309
+ def load_yaml_config(path)
310
+ if YAML.respond_to?(:unsafe_load_file)
311
+ # Ruby 3.1+: Use unsafe_load_file to support aliases/anchors
312
+ YAML.unsafe_load_file(path)
313
+ else
314
+ # Ruby 2.7-3.0: Use safe_load with aliases enabled
315
+ YAML.safe_load(File.read(path), permitted_classes: [], permitted_symbols: [], aliases: true)
316
+ end
317
+ rescue ArgumentError
318
+ # Ruby 2.7 doesn't support aliases keyword - fall back to YAML.load
319
+ YAML.load(File.read(path)) # rubocop:disable Security/YAMLLoad
320
+ end
321
+ end
322
+ end
@@ -65,12 +65,12 @@ module Shakapacker
65
65
 
66
66
  def check_entry_points
67
67
  # Check for invalid configuration first
68
- if config.data[:source_entry_path] == "/" && config.nested_entries?
68
+ if config.fetch(:source_entry_path) == "/" && config.nested_entries?
69
69
  @issues << "Invalid configuration: cannot use '/' as source_entry_path with nested_entries: true"
70
70
  return # Don't try to check files when config is invalid
71
71
  end
72
72
 
73
- source_entry_path = config.source_path.join(config.data[:source_entry_path] || "packs")
73
+ source_entry_path = config.source_path.join(config.fetch(:source_entry_path) || "packs")
74
74
 
75
75
  unless source_entry_path.exist?
76
76
  @issues << "Source entry path #{source_entry_path} does not exist"
@@ -173,7 +173,8 @@ module Shakapacker
173
173
  end
174
174
 
175
175
  def check_sri_dependencies
176
- return unless config.data.dig(:integrity, :enabled)
176
+ integrity_config = config.integrity
177
+ return unless integrity_config&.dig(:enabled)
177
178
 
178
179
  bundler = config.assets_bundler
179
180
  if bundler == "webpack"
@@ -183,7 +184,7 @@ module Shakapacker
183
184
  end
184
185
 
185
186
  # Validate hash functions
186
- hash_functions = config.data.dig(:integrity, :hash_functions) || ["sha384"]
187
+ hash_functions = integrity_config.dig(:hash_functions) || ["sha384"]
187
188
  invalid_functions = hash_functions - ["sha256", "sha384", "sha512"]
188
189
  unless invalid_functions.empty?
189
190
  @issues << "Invalid SRI hash functions: #{invalid_functions.join(', ')}"
@@ -453,9 +454,53 @@ module Shakapacker
453
454
 
454
455
  if swc_config_path.exist?
455
456
  @info << "SWC configuration: Using config/swc.config.js (recommended). This config is merged with Shakapacker's defaults."
457
+ check_swc_config_settings(swc_config_path)
456
458
  end
457
459
  end
458
460
 
461
+ def check_swc_config_settings(config_path)
462
+ config_content = File.read(config_path, encoding: "UTF-8")
463
+
464
+ # Check for loose: true (deprecated default)
465
+ if config_content.match?(/loose\s*:\s*true/)
466
+ @warnings << "SWC configuration: 'loose: true' detected in config/swc.config.js. " \
467
+ "This can cause silent failures with Stimulus controllers and incorrect spread operator behavior. " \
468
+ "Consider removing this setting to use Shakapacker's default 'loose: false' (spec-compliant). " \
469
+ "See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md#using-swc-with-stimulus"
470
+ end
471
+
472
+ # Check for missing keepClassNames with Stimulus
473
+ if stimulus_likely_used? && !config_content.match?(/keepClassNames\s*:\s*true/)
474
+ @warnings << "SWC configuration: Stimulus appears to be in use, but 'keepClassNames: true' is not set in config/swc.config.js. " \
475
+ "Without this setting, Stimulus controllers will fail silently. " \
476
+ "Add 'keepClassNames: true' to jsc config. " \
477
+ "See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md#using-swc-with-stimulus"
478
+ elsif config_content.match?(/keepClassNames\s*:\s*true/)
479
+ @info << "SWC configuration: 'keepClassNames: true' is set (good for Stimulus compatibility)"
480
+ end
481
+
482
+ # Check for jsc.target and env conflict
483
+ # Use word boundary to avoid false positives with transform.target or other nested properties
484
+ if config_content.match?(/jsc\s*:\s*\{[^}]*\btarget\s*:/) && config_content.match?(/env\s*:\s*\{/)
485
+ @issues << "SWC configuration: Both 'jsc.target' and 'env' are configured. These cannot be used together. " \
486
+ "Remove 'jsc.target' and use only 'env' (Shakapacker sets this automatically). " \
487
+ "See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md#using-swc-with-stimulus"
488
+ end
489
+ rescue => e
490
+ # Don't fail doctor if SWC config check has issues
491
+ @warnings << "Unable to validate SWC configuration: #{e.message}"
492
+ end
493
+
494
+ def stimulus_likely_used?
495
+ return false unless package_json_exists?
496
+
497
+ package_json = read_package_json
498
+ dependencies = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
499
+
500
+ # Check for @hotwired/stimulus or stimulus package
501
+ dependencies.key?("@hotwired/stimulus") || dependencies.key?("stimulus")
502
+ end
503
+
459
504
  def check_css_dependencies
460
505
  check_dependency("css-loader", @issues, "CSS")
461
506
  check_dependency("style-loader", @issues, "CSS (style-loader)")
@@ -51,11 +51,19 @@ module Shakapacker
51
51
  // This file is merged with Shakapacker's default SWC configuration
52
52
  // See: https://swc.rs/docs/configuration/compilation
53
53
 
54
+ const { env } = require('shakapacker');
55
+
54
56
  module.exports = {
55
- jsc: {
56
- transform: {
57
- react: {
58
- runtime: "automatic"
57
+ options: {
58
+ jsc: {
59
+ // CRITICAL for Stimulus compatibility: Prevents SWC from mangling class names
60
+ // which breaks Stimulus's class-based controller discovery mechanism
61
+ keepClassNames: true,
62
+ transform: {
63
+ react: {
64
+ runtime: "automatic",
65
+ refresh: env.isDevelopment && env.runningWebpackDevServer
66
+ }
59
67
  }
60
68
  }
61
69
  }
@@ -222,8 +230,8 @@ module Shakapacker
222
230
  settings.delete("babel")
223
231
  end
224
232
 
225
- settings["swc"] = true
226
- logger.info " - Enabled SWC for #{env} environment"
233
+ settings["javascript_transpiler"] = "swc"
234
+ logger.info " - Set javascript_transpiler to 'swc' for #{env} environment"
227
235
  end
228
236
 
229
237
  File.write(config_path, config.to_yaml)
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.0.0".freeze
3
+ VERSION = "9.1.0".freeze
4
4
  end
@@ -0,0 +1,82 @@
1
+ require "shakapacker/bundler_switcher"
2
+
3
+ namespace :shakapacker do
4
+ desc <<~DESC
5
+ Switch between webpack and rspack bundlers
6
+
7
+ Easily switch your Shakapacker configuration between webpack and rspack bundlers.
8
+ This task updates config/shakapacker.yml and optionally manages npm dependencies.
9
+
10
+ Usage:
11
+ rails shakapacker:switch_bundler [webpack|rspack] [OPTIONS]
12
+ rake shakapacker:switch_bundler [webpack|rspack] -- [OPTIONS]
13
+
14
+ Options:
15
+ --install-deps Automatically install/uninstall bundler dependencies
16
+ --no-uninstall Skip uninstalling old bundler packages (faster, keeps both bundlers)
17
+ --init-config Create custom dependencies configuration file
18
+ --help, -h Show detailed help message
19
+
20
+ Examples:
21
+ # Switch to rspack with automatic dependency management
22
+ rails shakapacker:switch_bundler rspack --install-deps
23
+ rake shakapacker:switch_bundler rspack -- --install-deps
24
+
25
+ # Fast switching without uninstalling (keeps both bundlers)
26
+ rails shakapacker:switch_bundler webpack --install-deps --no-uninstall
27
+ rake shakapacker:switch_bundler rspack -- --install-deps --no-uninstall
28
+
29
+ # Switch to rspack (manual dependency management)
30
+ rails shakapacker:switch_bundler rspack
31
+ rake shakapacker:switch_bundler rspack
32
+
33
+ # Switch back to webpack with dependency management
34
+ rails shakapacker:switch_bundler webpack --install-deps
35
+ rake shakapacker:switch_bundler webpack -- --install-deps
36
+
37
+ # Create custom dependencies config file
38
+ rails shakapacker:switch_bundler --init-config
39
+ rake shakapacker:switch_bundler -- --init-config
40
+
41
+ # Show current bundler and usage help
42
+ rails shakapacker:switch_bundler --help
43
+ rake shakapacker:switch_bundler -- --help
44
+
45
+ Note: When using 'rake', you must use '--' to separate rake options from task arguments.
46
+
47
+ What it does:
48
+ - Updates 'assets_bundler' in config/shakapacker.yml
49
+ - Preserves YAML comments and structure
50
+ - Updates 'javascript_transpiler' to 'swc' when switching to rspack
51
+ - With --install-deps: installs/uninstalls npm dependencies automatically
52
+ - Without --install-deps: shows manual installation commands
53
+
54
+ Custom Dependencies:
55
+ Create .shakapacker-switch-bundler-dependencies.yml to customize which
56
+ npm packages are installed/uninstalled during bundler switching.
57
+
58
+ See docs/rspack_migration_guide.md for more information.
59
+ DESC
60
+ task :switch_bundler do
61
+ switcher = Shakapacker::BundlerSwitcher.new
62
+
63
+ if ARGV.empty? || ARGV.include?("--help") || ARGV.include?("-h")
64
+ switcher.show_usage
65
+ elsif ARGV.include?("--init-config")
66
+ switcher.init_config
67
+ else
68
+ bundler = ARGV[1]
69
+ install_deps = ARGV.include?("--install-deps")
70
+ no_uninstall = ARGV.include?("--no-uninstall")
71
+
72
+ if bundler.nil? || bundler.start_with?("-")
73
+ switcher.show_usage
74
+ else
75
+ switcher.switch_to(bundler, install_deps: install_deps, no_uninstall: no_uninstall)
76
+ end
77
+ end
78
+
79
+ # Prevent rake from trying to execute arguments as tasks
80
+ ARGV.each { |arg| task arg.to_sym {} }
81
+ end
82
+ end
@@ -9,7 +9,8 @@ tasks = {
9
9
  "shakapacker:check_binstubs" => "Verifies that bin/shakapacker is present",
10
10
  "shakapacker:binstubs" => "Installs Shakapacker binstubs in this application",
11
11
  "shakapacker:verify_install" => "Verifies if Shakapacker is installed",
12
- "shakapacker:doctor" => "Checks for configuration issues and missing dependencies"
12
+ "shakapacker:doctor" => "Checks for configuration issues and missing dependencies",
13
+ "shakapacker:switch_bundler" => "Switch between webpack and rspack bundlers"
13
14
  }.freeze
14
15
 
15
16
  desc "Lists all available tasks in Shakapacker"
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Compile-time type tests for RspackPlugin backward compatibility
3
+ * This file ensures that RspackPlugin is correctly aliased to RspackPluginInstance
4
+ * and maintains backward compatibility for existing consumers.
5
+ *
6
+ * These tests will fail at compile time if the types are not compatible.
7
+ */
8
+
9
+ import type { RspackPlugin, RspackPluginInstance } from '../types'
10
+
11
+ // Test 1: RspackPlugin should be assignable to RspackPluginInstance
12
+ const testPluginToInstance = (plugin: RspackPlugin): RspackPluginInstance => plugin
13
+
14
+ // Test 2: RspackPluginInstance should be assignable to RspackPlugin
15
+ const testInstanceToPlugin = (instance: RspackPluginInstance): RspackPlugin => instance
16
+
17
+ // Test 3: Array compatibility
18
+ const testArrayCompatibility = (plugins: RspackPlugin[]): RspackPluginInstance[] => plugins
19
+ const testArrayCompatibilityReverse = (
20
+ instances: RspackPluginInstance[]
21
+ ): RspackPlugin[] => instances
22
+
23
+ // Test 4: Optional parameter compatibility
24
+ const testOptionalParam = (plugin?: RspackPlugin): RspackPluginInstance | undefined => plugin
25
+ const testOptionalParamReverse = (
26
+ instance?: RspackPluginInstance
27
+ ): RspackPlugin | undefined => instance
28
+
29
+ // Export a dummy value to make this a module
30
+ export const __typeTestsComplete = true
@@ -3,8 +3,12 @@
3
3
  * These types are exported for consumer use
4
4
  */
5
5
 
6
- import type { Configuration as WebpackConfiguration, WebpackPluginInstance } from "webpack"
6
+ import type {
7
+ Configuration as WebpackConfiguration,
8
+ WebpackPluginInstance
9
+ } from "webpack"
7
10
  import type { Configuration as DevServerConfiguration } from "webpack-dev-server"
11
+ import type { RspackPluginInstance as ImportedRspackPluginInstance } from "@rspack/core"
8
12
 
9
13
  /**
10
14
  * Webpack configuration extended with dev server support
@@ -15,15 +19,17 @@ export interface WebpackConfigWithDevServer extends WebpackConfiguration {
15
19
  }
16
20
 
17
21
  /**
18
- * Rspack plugin interface
19
- * Rspack plugins follow a similar pattern to webpack but may have different internals
22
+ * Rspack plugin instance interface
23
+ * Uses the RspackPluginInstance type from @rspack/core
20
24
  */
21
- export interface RspackPlugin {
22
- new(...args: any[]): {
23
- apply(compiler: any): void
24
- [key: string]: any
25
- }
26
- }
25
+ export type RspackPluginInstance = ImportedRspackPluginInstance
26
+
27
+ /**
28
+ * Rspack plugin type alias
29
+ * @deprecated Use RspackPluginInstance instead
30
+ * Retained for backward compatibility with existing code
31
+ */
32
+ export type RspackPlugin = RspackPluginInstance
27
33
 
28
34
  /**
29
35
  * Rspack dev server configuration
@@ -52,7 +58,7 @@ export interface RspackConfigWithDevServer {
52
58
  mode?: "development" | "production" | "none"
53
59
  devtool?: string | false
54
60
  devServer?: RspackDevServerConfig
55
- plugins?: RspackPlugin[]
61
+ plugins?: RspackPluginInstance[]
56
62
  module?: WebpackConfiguration["module"]
57
63
  resolve?: WebpackConfiguration["resolve"]
58
64
  entry?: WebpackConfiguration["entry"]
@@ -76,15 +82,17 @@ export interface CompressionPluginOptions {
76
82
  /**
77
83
  * Compression plugin constructor type
78
84
  */
79
- export type CompressionPluginConstructor = new (options: CompressionPluginOptions) => WebpackPluginInstance
85
+ export type CompressionPluginConstructor = new (
86
+ options: CompressionPluginOptions
87
+ ) => WebpackPluginInstance
80
88
 
81
89
  /**
82
90
  * React Refresh plugin types
83
91
  */
84
92
  export interface ReactRefreshWebpackPlugin {
85
- new(options?: Record<string, unknown>): WebpackPluginInstance
93
+ new (options?: Record<string, unknown>): WebpackPluginInstance
86
94
  }
87
95
 
88
96
  export interface ReactRefreshRspackPlugin {
89
- new(options?: Record<string, unknown>): RspackPlugin
90
- }
97
+ new (options?: Record<string, unknown>): RspackPluginInstance
98
+ }
data/package/index.ts CHANGED
@@ -16,18 +16,21 @@ const inliningCss = require("./utils/inliningCss")
16
16
  const rulesPath = resolve(__dirname, "rules", `${config.assets_bundler}.js`)
17
17
  const rules = require(rulesPath)
18
18
 
19
- const generateWebpackConfig = (extraConfig: Configuration = {}, ...extraArgs: any[]): Configuration => {
19
+ const generateWebpackConfig = (
20
+ extraConfig: Configuration = {},
21
+ ...extraArgs: unknown[]
22
+ ): Configuration => {
20
23
  if (extraArgs.length > 0) {
21
24
  throw new Error(
22
25
  `Invalid usage: generateWebpackConfig() accepts only one configuration object.\n\n` +
23
- `You passed ${extraArgs.length + 1} arguments. Only one extra config may be passed here - use webpack-merge to merge configs before passing them to Shakapacker.\n\n` +
24
- `Example:\n` +
25
- ` const { merge } = require('webpack-merge')\n` +
26
- ` const mergedConfig = merge(config1, config2, config3)\n` +
27
- ` const finalConfig = generateWebpackConfig(mergedConfig)\n\n` +
28
- `Or if using ES6:\n` +
29
- ` import { merge } from 'webpack-merge'\n` +
30
- ` const finalConfig = generateWebpackConfig(merge(config1, config2))`
26
+ `You passed ${extraArgs.length + 1} arguments. Only one extra config may be passed here - use webpack-merge to merge configs before passing them to Shakapacker.\n\n` +
27
+ `Example:\n` +
28
+ ` const { merge } = require('webpack-merge')\n` +
29
+ ` const mergedConfig = merge(config1, config2, config3)\n` +
30
+ ` const finalConfig = generateWebpackConfig(mergedConfig)\n\n` +
31
+ `Or if using ES6:\n` +
32
+ ` import { merge } from 'webpack-merge'\n` +
33
+ ` const finalConfig = generateWebpackConfig(merge(config1, config2))`
31
34
  )
32
35
  }
33
36