shakapacker 9.0.0.beta.3 → 9.0.0.beta.5

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dummy.yml +4 -0
  3. data/.github/workflows/generator.yml +7 -0
  4. data/.github/workflows/node.yml +22 -0
  5. data/.github/workflows/ruby.yml +11 -0
  6. data/.github/workflows/test-bundlers.yml +18 -0
  7. data/.gitignore +20 -0
  8. data/.yalcignore +26 -0
  9. data/CHANGELOG.md +58 -19
  10. data/Gemfile.lock +1 -1
  11. data/README.md +3 -1
  12. data/docs/peer-dependencies.md +23 -3
  13. data/docs/transpiler-performance.md +179 -0
  14. data/docs/typescript.md +99 -0
  15. data/docs/v9_upgrade.md +58 -2
  16. data/lib/install/config/shakapacker.yml +4 -2
  17. data/lib/install/package.json +8 -0
  18. data/lib/install/template.rb +128 -51
  19. data/lib/shakapacker/configuration.rb +58 -1
  20. data/lib/shakapacker/doctor.rb +752 -0
  21. data/lib/shakapacker/swc_migrator.rb +292 -0
  22. data/lib/shakapacker/version.rb +1 -1
  23. data/lib/shakapacker.rb +1 -0
  24. data/lib/tasks/shakapacker/doctor.rake +8 -0
  25. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  26. data/lib/tasks/shakapacker.rake +1 -0
  27. data/package/config.ts +162 -0
  28. data/package/{dev_server.js → dev_server.ts} +8 -5
  29. data/package/env.ts +67 -0
  30. data/package/environments/base.js +21 -31
  31. data/package/environments/base.ts +137 -0
  32. data/package/index.d.ts +3 -150
  33. data/package/{index.js → index.ts} +17 -8
  34. data/package/loaders.d.ts +27 -0
  35. data/package/types.ts +108 -0
  36. data/package/utils/configPath.ts +6 -0
  37. data/package/utils/{debug.js → debug.ts} +7 -7
  38. data/package/utils/defaultConfigPath.ts +4 -0
  39. data/package/utils/errorHelpers.ts +77 -0
  40. data/package/utils/{getStyleRule.js → getStyleRule.ts} +17 -20
  41. data/package/utils/helpers.ts +85 -0
  42. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  43. data/package/utils/{requireOrError.js → requireOrError.ts} +2 -2
  44. data/package/utils/snakeToCamelCase.ts +5 -0
  45. data/package/utils/typeGuards.ts +228 -0
  46. data/package/utils/{validateDependencies.js → validateDependencies.ts} +4 -4
  47. data/package/webpack-types.d.ts +32 -0
  48. data/package/webpackDevServerConfig.ts +117 -0
  49. data/package.json +6 -2
  50. data/test/package/rules/babel.test.js +16 -0
  51. data/test/typescript/build.test.js +117 -0
  52. data/tsconfig.json +39 -0
  53. data/yarn.lock +5 -5
  54. metadata +32 -18
  55. data/package/config.js +0 -80
  56. data/package/env.js +0 -48
  57. data/package/utils/configPath.js +0 -4
  58. data/package/utils/defaultConfigPath.js +0 -2
  59. data/package/utils/helpers.js +0 -62
  60. data/package/utils/snakeToCamelCase.js +0 -5
  61. data/package/utils/validateCssModulesConfig.js +0 -91
  62. data/package/webpackDevServerConfig.js +0 -73
  63. data/package-lock.json +0 -11966
data/docs/v9_upgrade.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # Shakapacker v9 Upgrade Guide
2
2
 
3
- This guide outlines breaking changes and migration steps for upgrading from Shakapacker v8 to v9.
3
+ This guide outlines new features, breaking changes, and migration steps for upgrading from Shakapacker v8 to v9.
4
+
5
+ ## New Features
6
+
7
+ ### TypeScript Support
8
+
9
+ Shakapacker v9 includes TypeScript definitions for better IDE support and type safety.
10
+
11
+ - **No breaking changes** - JavaScript configs continue to work
12
+ - **Optional** - Use TypeScript only if you want it
13
+ - **Type safety** - Catch configuration errors at compile-time
14
+ - **IDE support** - Full autocomplete for all options
15
+
16
+ See the [TypeScript Documentation](./typescript.md) for usage examples.
4
17
 
5
18
  ## Breaking Changes
6
19
 
@@ -65,7 +78,50 @@ javascript_transpiler: 'babel'
65
78
 
66
79
  **Note:** The old `webpack_loader` option is deprecated but still supported with a warning.
67
80
 
68
- ### 3. Rspack Support Added
81
+ ### 3. SWC is Now the Default JavaScript Transpiler
82
+
83
+ **What changed:** SWC replaces Babel as the default JavaScript transpiler. Babel is no longer included in peer dependencies.
84
+
85
+ **Why:** SWC is 20x faster than Babel while maintaining compatibility with most JavaScript and TypeScript code.
86
+
87
+ **Impact on existing projects:**
88
+ - Your project will continue using Babel if you already have babel packages in package.json
89
+ - To switch to SWC for better performance, see migration options below
90
+
91
+ **Impact on new projects:**
92
+ - New installations will use SWC by default
93
+ - Babel dependencies won't be installed unless explicitly configured
94
+
95
+ ### Migration Options
96
+
97
+ #### Option 1 (Recommended): Switch to SWC
98
+ ```yml
99
+ # config/shakapacker.yml
100
+ javascript_transpiler: 'swc'
101
+ ```
102
+ Then install SWC:
103
+ ```bash
104
+ npm install @swc/core swc-loader
105
+ ```
106
+
107
+ #### Option 2: Keep using Babel
108
+ ```yml
109
+ # config/shakapacker.yml
110
+ javascript_transpiler: 'babel'
111
+ ```
112
+ No other changes needed - your existing babel packages will continue to work.
113
+
114
+ #### Option 3: Use esbuild
115
+ ```yml
116
+ # config/shakapacker.yml
117
+ javascript_transpiler: 'esbuild'
118
+ ```
119
+ Then install esbuild:
120
+ ```bash
121
+ npm install esbuild esbuild-loader
122
+ ```
123
+
124
+ ### 4. Rspack Support Added
69
125
 
70
126
  **New feature:** Shakapacker v9 adds support for Rspack as an alternative bundler to webpack.
71
127
 
@@ -40,8 +40,10 @@ default: &default
40
40
  # Reload manifest.json on all requests so we reload latest compiled packs
41
41
  cache_manifest: false
42
42
 
43
- # Select JavaScript transpiler to use, available options are 'babel' (default), 'swc' or 'esbuild'
44
- javascript_transpiler: 'babel'
43
+ # Select JavaScript transpiler to use
44
+ # Available options: 'swc' (default, 20x faster), 'babel', or 'esbuild'
45
+ # Note: When using rspack, swc is used automatically regardless of this setting
46
+ javascript_transpiler: 'swc'
45
47
 
46
48
  # Select assets bundler to use
47
49
  # Available options: 'webpack' (default) or 'rspack'
@@ -26,5 +26,13 @@
26
26
  "@babel/preset-env": "^7.16.11",
27
27
  "@babel/runtime": "^7.17.9",
28
28
  "babel-loader": "^8.2.4 || ^9.0.0 || ^10.0.0"
29
+ },
30
+ "swc": {
31
+ "@swc/core": "^1.3.0",
32
+ "swc-loader": "^0.2.0"
33
+ },
34
+ "esbuild": {
35
+ "esbuild": "^0.24.0",
36
+ "esbuild-loader": "^4.0.0"
29
37
  }
30
38
  }
@@ -2,25 +2,56 @@ require "shakapacker/utils/misc"
2
2
  require "shakapacker/utils/manager"
3
3
  require "shakapacker/utils/version_syntax_converter"
4
4
  require "package_json"
5
+ require "yaml"
6
+ require "json"
5
7
 
6
8
  # Install Shakapacker
7
9
 
8
10
  force_option = ENV["FORCE"] ? { force: true } : {}
9
11
 
10
- copy_file "#{__dir__}/config/shakapacker.yml", "config/shakapacker.yml", force_option
12
+ # Initialize variables for use throughout the template
13
+ # Using instance variable to avoid method definition issues in Rails templates
14
+ @package_json ||= PackageJson.new
15
+ install_dir = File.expand_path(File.dirname(__FILE__))
16
+
17
+ # Installation strategy:
18
+ # - USE_BABEL_PACKAGES installs both babel AND swc for compatibility
19
+ # - Otherwise install only the specified transpiler
20
+ if ENV["USE_BABEL_PACKAGES"] == "true" || ENV["USE_BABEL_PACKAGES"] == "1"
21
+ @transpiler_to_install = "babel"
22
+ say "📦 Installing Babel packages (USE_BABEL_PACKAGES is set)", :yellow
23
+ say "✨ Also installing SWC packages for default config compatibility", :green
24
+ elsif ENV["JAVASCRIPT_TRANSPILER"]
25
+ @transpiler_to_install = ENV["JAVASCRIPT_TRANSPILER"]
26
+ say "📦 Installing #{@transpiler_to_install} packages", :blue
27
+ else
28
+ # Default to swc (matches the default in shakapacker.yml)
29
+ @transpiler_to_install = "swc"
30
+ say "✨ Installing SWC packages (20x faster than Babel)", :green
31
+ end
32
+
33
+ # Copy config file
34
+ copy_file "#{install_dir}/config/shakapacker.yml", "config/shakapacker.yml", force_option
35
+
36
+ # Update config if USE_BABEL_PACKAGES is set to ensure babel is used at runtime
37
+ if @transpiler_to_install == "babel" && !ENV["JAVASCRIPT_TRANSPILER"]
38
+ # When USE_BABEL_PACKAGES is set, update the config to use babel
39
+ gsub_file "config/shakapacker.yml", "javascript_transpiler: 'swc'", "javascript_transpiler: 'babel'"
40
+ say " 📝 Updated config/shakapacker.yml to use Babel transpiler", :green
41
+ end
11
42
 
12
43
  say "Copying webpack core config"
13
- directory "#{__dir__}/config/webpack", "config/webpack", force_option
44
+ directory "#{install_dir}/config/webpack", "config/webpack", force_option
14
45
 
15
46
  if Dir.exist?(Shakapacker.config.source_path)
16
47
  say "The packs app source directory already exists"
17
48
  else
18
49
  say "Creating packs app source directory"
19
50
  empty_directory "app/javascript/packs"
20
- copy_file "#{__dir__}/application.js", "app/javascript/packs/application.js"
51
+ copy_file "#{install_dir}/application.js", "app/javascript/packs/application.js"
21
52
  end
22
53
 
23
- apply "#{__dir__}/binstubs.rb"
54
+ apply "#{install_dir}/binstubs.rb"
24
55
 
25
56
  git_ignore_path = Rails.root.join(".gitignore")
26
57
  if File.exist?(git_ignore_path)
@@ -43,17 +74,8 @@ else
43
74
  say %( Add <%= javascript_pack_tag "application" %> within the <head> tag in your custom layout.)
44
75
  end
45
76
 
46
- def package_json
47
- @package_json ||= PackageJson.new
48
- end
49
-
50
77
  # setup the package manager with default values
51
- package_json.merge! do |pj|
52
- babel = pj.fetch("babel", {})
53
-
54
- babel["presets"] ||= []
55
- babel["presets"].push("./node_modules/shakapacker/package/babel/preset.js")
56
-
78
+ @package_json.merge! do |pj|
57
79
  package_manager = pj.fetch("packageManager") do
58
80
  "#{Shakapacker::Utils::Manager.guess_binary}@#{Shakapacker::Utils::Manager.guess_version}"
59
81
  end
@@ -62,7 +84,6 @@ package_json.merge! do |pj|
62
84
  "name" => "app",
63
85
  "private" => true,
64
86
  "version" => "0.1.0",
65
- "babel" => babel,
66
87
  "browserslist" => [
67
88
  "defaults"
68
89
  ],
@@ -74,7 +95,7 @@ Shakapacker::Utils::Manager.error_unless_package_manager_is_obvious!
74
95
 
75
96
  # Ensure there is `system!("bin/yarn")` command in `./bin/setup` file
76
97
  if (setup_path = Rails.root.join("bin/setup")).exist?
77
- native_install_command = package_json.manager.native_install_command.join(" ")
98
+ native_install_command = @package_json.manager.native_install_command.join(" ")
78
99
 
79
100
  say "Run #{native_install_command} during bin/setup"
80
101
 
@@ -103,42 +124,61 @@ if (setup_path = Rails.root.join("bin/setup")).exist?
103
124
  end
104
125
  end
105
126
 
106
- def add_dependencies(dependencies, type)
107
- package_json.manager.add!(dependencies, type: type)
108
- rescue PackageJson::Error
109
- say "Shakapacker installation failed 😭 See above for details.", :red
110
- exit 1
111
- end
112
-
113
- def fetch_peer_dependencies
114
- PackageJson.read("#{__dir__}").fetch(ENV["SHAKAPACKER_BUNDLER"] || "webpack")
115
- end
116
-
117
- def fetch_common_dependencies
118
- ENV["SKIP_COMMON_LOADERS"] ? {} : PackageJson.read("#{__dir__}").fetch("common")
119
- end
120
-
121
- def fetch_babel_dependencies
122
- ENV["USE_BABEL_PACKAGES"] ? PackageJson.read("#{__dir__}").fetch("babel") : {}
123
- end
124
-
125
127
  Dir.chdir(Rails.root) do
126
128
  npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
127
129
  say "Installing shakapacker@#{npm_version}"
128
- add_dependencies(["shakapacker@#{npm_version}"], :production)
129
-
130
- package_json.merge! do |pj|
131
- {
132
- "dependencies" => pj["dependencies"].merge({
133
- # TODO: workaround for test suite - long-run need to actually account for diff pkg manager behaviour
134
- "shakapacker" => pj["dependencies"]["shakapacker"].delete_prefix("^")
135
- })
136
- }
130
+ begin
131
+ @package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
132
+ rescue PackageJson::Error
133
+ say "Shakapacker installation failed 😭 See above for details.", :red
134
+ exit 1
137
135
  end
138
136
 
139
- peers = fetch_peer_dependencies
140
- peers = peers.merge(fetch_common_dependencies)
141
- peers = peers.merge(fetch_babel_dependencies)
137
+ @package_json.merge! do |pj|
138
+ if pj["dependencies"] && pj["dependencies"]["shakapacker"]
139
+ {
140
+ "dependencies" => pj["dependencies"].merge({
141
+ # TODO: workaround for test suite - long-run need to actually account for diff pkg manager behaviour
142
+ "shakapacker" => pj["dependencies"]["shakapacker"].delete_prefix("^")
143
+ })
144
+ }
145
+ else
146
+ pj
147
+ end
148
+ end
149
+
150
+ # Inline fetch_peer_dependencies and fetch_common_dependencies
151
+ peers = PackageJson.read(install_dir).fetch(ENV["SHAKAPACKER_BUNDLER"] || "webpack")
152
+ common_deps = ENV["SKIP_COMMON_LOADERS"] ? {} : PackageJson.read(install_dir).fetch("common")
153
+ peers = peers.merge(common_deps)
154
+
155
+ # Add transpiler-specific dependencies based on detected/configured transpiler
156
+ # Inline the logic here since methods can't be called before they're defined in Rails templates
157
+
158
+ # Install transpiler-specific dependencies
159
+ # When USE_BABEL_PACKAGES is set, install both babel AND swc
160
+ # This ensures backward compatibility while supporting the default config
161
+ if @transpiler_to_install == "babel"
162
+ # Install babel packages
163
+ babel_deps = PackageJson.read(install_dir).fetch("babel")
164
+ peers = peers.merge(babel_deps)
165
+
166
+ # Also install SWC since that's what the default config uses
167
+ # This ensures the runtime works regardless of config
168
+ swc_deps = PackageJson.read(install_dir).fetch("swc")
169
+ peers = peers.merge(swc_deps)
170
+
171
+ say "ℹ️ Installing both Babel and SWC packages for compatibility:", :blue
172
+ say " - Babel packages are installed as requested via USE_BABEL_PACKAGES", :blue
173
+ say " - SWC packages are also installed to ensure the default config works", :blue
174
+ say " - Your actual transpiler will be determined by your shakapacker.yml configuration", :blue
175
+ elsif @transpiler_to_install == "swc"
176
+ swc_deps = PackageJson.read(install_dir).fetch("swc")
177
+ peers = peers.merge(swc_deps)
178
+ elsif @transpiler_to_install == "esbuild"
179
+ esbuild_deps = PackageJson.read(install_dir).fetch("esbuild")
180
+ peers = peers.merge(esbuild_deps)
181
+ end
142
182
 
143
183
  dev_dependency_packages = ["webpack-dev-server"]
144
184
 
@@ -146,8 +186,17 @@ Dir.chdir(Rails.root) do
146
186
  dev_dependencies_to_add = []
147
187
 
148
188
  peers.each do |(package, version)|
149
- major_version = version.split("||").last.match(/(\d+)/)[1]
150
- entry = "#{package}@#{major_version}"
189
+ # Handle versions like "^1.3.0" or ">= 4 || 5"
190
+ if version.start_with?("^", "~") || version.match?(/^\d+\.\d+/)
191
+ # Already has proper version format, use as-is
192
+ entry = "#{package}@#{version}"
193
+ else
194
+ # Extract major version from complex version strings like ">= 4 || 5"
195
+ # Fallback to "latest" if no version number found
196
+ version_match = version.split("||").last.match(/(\d+)/)
197
+ major_version = version_match ? version_match[1] : "latest"
198
+ entry = "#{package}@#{major_version}"
199
+ end
151
200
 
152
201
  if dev_dependency_packages.include? package
153
202
  dev_dependencies_to_add << entry
@@ -157,8 +206,36 @@ Dir.chdir(Rails.root) do
157
206
  end
158
207
 
159
208
  say "Adding shakapacker peerDependencies"
160
- add_dependencies(dependencies_to_add, :production)
209
+ begin
210
+ @package_json.manager.add!(dependencies_to_add, type: :production)
211
+ rescue PackageJson::Error
212
+ say "Shakapacker installation failed 😭 See above for details.", :red
213
+ exit 1
214
+ end
161
215
 
162
216
  say "Installing webpack-dev-server for live reloading as a development dependency"
163
- add_dependencies(dev_dependencies_to_add, :dev)
217
+ begin
218
+ @package_json.manager.add!(dev_dependencies_to_add, type: :dev)
219
+ rescue PackageJson::Error
220
+ say "Shakapacker installation failed 😭 See above for details.", :red
221
+ exit 1
222
+ end
223
+
224
+ # Configure babel preset in package.json if babel is being used
225
+ if @transpiler_to_install == "babel"
226
+ @package_json.merge! do |pj|
227
+ babel = pj.fetch("babel", {})
228
+ babel["presets"] ||= []
229
+ unless babel["presets"].include?("./node_modules/shakapacker/package/babel/preset.js")
230
+ babel["presets"].push("./node_modules/shakapacker/package/babel/preset.js")
231
+ end
232
+ { "babel" => babel }
233
+ end
234
+ end
235
+ end
236
+
237
+ # Helper methods defined at the end (Rails template convention)
238
+
239
+ def package_json
240
+ @package_json
164
241
  end
@@ -1,4 +1,5 @@
1
1
  require "yaml"
2
+ require "json"
2
3
  require "active_support/core_ext/hash/keys"
3
4
  require "active_support/core_ext/hash/indifferent_access"
4
5
 
@@ -123,7 +124,12 @@ class Shakapacker::Configuration
123
124
  end
124
125
 
125
126
  # Use explicit config if set, otherwise default based on bundler
126
- fetch(:javascript_transpiler) || fetch(:webpack_loader) || default_javascript_transpiler
127
+ transpiler = fetch(:javascript_transpiler) || fetch(:webpack_loader) || default_javascript_transpiler
128
+
129
+ # Validate transpiler configuration
130
+ validate_transpiler_configuration(transpiler) unless self.class.installing
131
+
132
+ transpiler
127
133
  end
128
134
 
129
135
  # Deprecated: Use javascript_transpiler instead
@@ -138,6 +144,57 @@ class Shakapacker::Configuration
138
144
  rspack? ? "swc" : "babel"
139
145
  end
140
146
 
147
+ def validate_transpiler_configuration(transpiler)
148
+ return unless ENV["NODE_ENV"] != "test" # Skip validation in test environment
149
+
150
+ # Check if package.json exists
151
+ package_json_path = root_path.join("package.json")
152
+ return unless package_json_path.exist?
153
+
154
+ begin
155
+ package_json = JSON.parse(File.read(package_json_path))
156
+ all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
157
+
158
+ # Check for transpiler mismatch
159
+ has_babel = all_deps.keys.any? { |pkg| pkg.start_with?("@babel/", "babel-") }
160
+ has_swc = all_deps.keys.any? { |pkg| pkg.include?("swc") }
161
+ has_esbuild = all_deps.keys.any? { |pkg| pkg.include?("esbuild") }
162
+
163
+ case transpiler
164
+ when "babel"
165
+ if !has_babel && has_swc
166
+ warn_transpiler_mismatch("Babel", "SWC packages found but Babel is configured")
167
+ end
168
+ when "swc"
169
+ if !has_swc && has_babel
170
+ warn_transpiler_mismatch("SWC", "Babel packages found but SWC is configured")
171
+ end
172
+ when "esbuild"
173
+ if !has_esbuild && (has_babel || has_swc)
174
+ other = has_babel ? "Babel" : "SWC"
175
+ warn_transpiler_mismatch("esbuild", "#{other} packages found but esbuild is configured")
176
+ end
177
+ end
178
+ rescue JSON::ParserError
179
+ # Ignore if package.json is malformed
180
+ end
181
+ end
182
+
183
+ def warn_transpiler_mismatch(configured, message)
184
+ $stderr.puts <<~WARNING
185
+ ⚠️ Transpiler Configuration Mismatch Detected:
186
+ #{message}
187
+ Configured transpiler: #{configured}
188
+ #{' '}
189
+ This might cause unexpected behavior or build failures.
190
+ #{' '}
191
+ To fix this:
192
+ 1. Run 'rails shakapacker:migrate_to_swc' to migrate to SWC (recommended for 20x faster builds)
193
+ 2. Or install the correct packages for #{configured}
194
+ 3. Or update your shakapacker.yml to match your installed packages
195
+ WARNING
196
+ end
197
+
141
198
  public
142
199
 
143
200
  def fetch(key)