shakapacker 9.1.0 → 9.2.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.
@@ -17,12 +17,72 @@
17
17
 
18
18
  4. You can also pass additional options to the command to run the webpack-dev-server and start the webpack-dev-server with the option `--debug-shakapacker`
19
19
 
20
- 5. ChatGPT and other AI tools can consume this output file. Change the NODE_ENV per your needs. Then upload the file to your favorite AI tool.
20
+ 5. **Export your full webpack/rspack configuration for analysis**: Use the `bin/export-bundler-config` utility to export your complete resolved configuration. This is especially helpful for:
21
+
22
+ - **Migrations**: Comparing configurations before and after migrating between webpack and rspack, or between different Shakapacker versions
23
+ - **Debugging**: Inspecting the exact configuration webpack/rspack is using, including all merged settings
24
+ - **AI Analysis**: Uploading the exported config to ChatGPT or other AI tools for troubleshooting
25
+
26
+ **Installation**: The utility is installed when you run `rake shakapacker:binstubs` or can be used directly via `rake shakapacker:export_bundler_config`.
27
+
28
+ **RECOMMENDED - Quick troubleshooting:**
29
+
30
+ ```bash
31
+ # Install the binstub (one-time setup)
32
+ rake shakapacker:binstubs
33
+
34
+ # Export EVERYTHING for troubleshooting (dev + prod, annotated YAML)
35
+ bin/export-bundler-config --doctor
36
+ # Creates: webpack-development-client.yaml, webpack-development-server.yaml,
37
+ # webpack-production-client.yaml, webpack-production-server.yaml
38
+ ```
39
+
40
+ **Other usage examples:**
41
+
42
+ ```bash
43
+ # Save current environment configs with auto-generated names
44
+ bin/export-bundler-config --save
45
+ # Creates: webpack-development-client.yaml, webpack-development-server.yaml
46
+
47
+ # Save to specific directory
48
+ bin/export-bundler-config --save --save-dir=./debug-configs
49
+
50
+ # Export only client config for production
51
+ bin/export-bundler-config --save --env=production --client-only
52
+ # Creates: webpack-production-client.yaml
53
+
54
+ # Compare development vs production configs
55
+ bin/export-bundler-config --save --save-dir=./configs
56
+ diff configs/webpack-development-client.yaml configs/webpack-production-client.yaml
57
+
58
+ # View config in terminal (no files created)
59
+ bin/export-bundler-config
60
+
61
+ # Export without inline documentation annotations
62
+ bin/export-bundler-config --save --no-annotate
63
+
64
+ # Export in JSON format for programmatic analysis
65
+ bin/export-bundler-config --save --format=json
21
66
  ```
67
+
68
+ **Config files are automatically named:** `{bundler}-{env}-{type}.{ext}`
69
+
70
+ - Examples: `webpack-development-client.yaml`, `rspack-production-server.yaml`
71
+ - YAML format includes inline documentation explaining each config key
72
+ - Separate files for client and server bundles (cleaner than combined)
73
+
74
+ See `bin/export-bundler-config --help` for all available options.
75
+
76
+ 6. Generate webpack stats for build analysis (useful for bundle size optimization):
77
+
78
+ ```bash
22
79
  NODE_ENV=development bin/shakapacker --profile --json > /tmp/webpack-stats.json
23
80
  ```
24
81
 
82
+ ChatGPT and other AI tools can consume this output file. Change the NODE_ENV per your needs.
83
+
25
84
  ## Incorrect peer dependencies
85
+
26
86
  Shakapacker uses peer dependencies to make it easier to manage what versions are being used for your app, which is especially
27
87
  useful for patching security vulnerabilities. However, not all package managers will actually enforce these versions - notably,
28
88
  Yarn will omit a warning rather than erroring if you forget to update a peer dependency:
@@ -32,6 +92,7 @@ warning " > shakapacker@6.1.1" has incorrect peer dependency "compression-webpac
32
92
  ```
33
93
 
34
94
  This omission resulted in an error in the browser:
95
+
35
96
  ```
36
97
  Failed to load resource: net::ERR_CONTENT_DECODING_FAILED
37
98
  ```
@@ -40,6 +101,40 @@ The error was caused by an old version of the peer dependency `webpack-compressi
40
101
 
41
102
  So, be sure to investigate warnings from `yarn install`!
42
103
 
104
+ ## NoMethodError: undefined method 'deep_symbolize_keys' for nil:NilClass
105
+
106
+ If you see this error during deployment (especially on Heroku with a staging environment):
107
+
108
+ ```
109
+ NoMethodError: undefined method 'deep_symbolize_keys' for nil:NilClass
110
+ from shakapacker/configuration.rb:XXX:in 'load'
111
+ ```
112
+
113
+ This happens when deploying to a custom Rails environment (like `staging`) that isn't explicitly defined in your `config/shakapacker.yml` file.
114
+
115
+ **Solution:** This was fixed in Shakapacker v9.1.1+. Upgrade to the latest version:
116
+
117
+ ```ruby
118
+ # Gemfile
119
+ gem 'shakapacker', '~> 9.1'
120
+ ```
121
+
122
+ After upgrading, Shakapacker will automatically fall back to sensible defaults when your environment isn't defined:
123
+
124
+ 1. First tries your environment (e.g., `staging`)
125
+ 2. Falls back to `production` configuration
126
+
127
+ **Alternative:** If you can't upgrade immediately, explicitly add your environment to `config/shakapacker.yml`:
128
+
129
+ ```yaml
130
+ staging:
131
+ <<: *default
132
+ compile: false
133
+ cache_manifest: true
134
+ ```
135
+
136
+ See the [deployment guide](./deployment.md#custom-rails-environments-eg-staging) for more details.
137
+
43
138
  ## ENOENT: no such file or directory - node-sass
44
139
 
45
140
  If you get the error `ENOENT: no such file or directory - node-sass` on deploy with
@@ -54,7 +149,7 @@ thing, like Heroku.
54
149
 
55
150
  However, if you get this on local development, or not during a deploy then you
56
151
  may need to rebuild `node-sass`. It's a bit of a weird error; basically, it
57
- can't find the `node-sass` binary. An easy solution is to create a postinstall
152
+ can't find the `node-sass` binary. An easy solution is to create a postinstall
58
153
  hook to ensure `node-sass` is rebuilt whenever new modules are installed.
59
154
 
60
155
  In `package.json`:
@@ -67,19 +162,18 @@ In `package.json`:
67
162
 
68
163
  ## Can't find hello_react.js in manifest.json
69
164
 
70
- * If you get this error `Can't find hello_react.js in manifest.json`
71
- when loading a view in the browser it's because webpack is still compiling packs.
72
- Shakapacker uses a `manifest.json` file to keep track of packs in all environments,
73
- however since this file is generated after packs are compiled by webpack. So,
74
- if you load a view in browser whilst webpack is compiling you will get this error.
75
- Therefore, make sure webpack
76
- (i.e `./bin/shakapacker-dev-server`) is running and has
77
- completed the compilation successfully before loading a view.
78
-
165
+ - If you get this error `Can't find hello_react.js in manifest.json`
166
+ when loading a view in the browser it's because webpack is still compiling packs.
167
+ Shakapacker uses a `manifest.json` file to keep track of packs in all environments,
168
+ however since this file is generated after packs are compiled by webpack. So,
169
+ if you load a view in browser whilst webpack is compiling you will get this error.
170
+ Therefore, make sure webpack
171
+ (i.e `./bin/shakapacker-dev-server`) is running and has
172
+ completed the compilation successfully before loading a view.
79
173
 
80
174
  ## throw er; // Unhandled 'error' event
81
175
 
82
- * If you get this error while trying to use Elm, try rebuilding Elm. You can do
176
+ - If you get this error while trying to use Elm, try rebuilding Elm. You can do
83
177
  so with a postinstall hook in your `package.json`:
84
178
 
85
179
  ```json
@@ -90,9 +184,9 @@ completed the compilation successfully before loading a view.
90
184
 
91
185
  ## webpack or webpack-dev-server not found
92
186
 
93
- * This could happen if `shakapacker:install` step is skipped. Please run `bundle exec rails shakapacker:install` to fix the issue.
187
+ - This could happen if `shakapacker:install` step is skipped. Please run `bundle exec rails shakapacker:install` to fix the issue.
94
188
 
95
- * If you encounter the above error on heroku after upgrading from Rails 4.x to 5.1.x, then the problem might be related to missing `yarn` binstub. Please run following commands to update/add binstubs:
189
+ - If you encounter the above error on heroku after upgrading from Rails 4.x to 5.1.x, then the problem might be related to missing `yarn` binstub. Please run following commands to update/add binstubs:
96
190
 
97
191
  ```bash
98
192
  bundle config --delete bin
@@ -137,6 +231,7 @@ chmod +x $HOME/your_rails_app/node_modules/.bin/elm-make
137
231
  ```
138
232
 
139
233
  ## Rake assets:precompile fails. ExecJS::RuntimeError
234
+
140
235
  This error occurs because you are trying to minify by `terser` a pack that's already been minified by Shakapacker. To avoid this conflict and prevent appearing of `ExecJS::RuntimeError` error, you will need to disable uglifier from Rails config:
141
236
 
142
237
  ```ruby
@@ -152,10 +247,11 @@ Rails.application.config.assets.js_compressor = Uglifier.new(harmony: true)
152
247
  ### Angular: WARNING in ./node_modules/@angular/core/esm5/core.js, Critical dependency: the request of a dependency is an expression
153
248
 
154
249
  To silent these warnings, please update `config/webpack/webpack.config.js`:
250
+
155
251
  ```js
156
- const webpack = require('webpack')
157
- const { resolve } = require('path')
158
- const { generateWebpackConfig } = require('shakapacker')
252
+ const webpack = require("webpack")
253
+ const { resolve } = require("path")
254
+ const { generateWebpackConfig } = require("shakapacker")
159
255
 
160
256
  module.exports = generateWebpackConfig({
161
257
  plugins: [
@@ -192,6 +288,7 @@ Thus ProvidePlugin manages build-time dependencies to global symbols whereas the
192
288
  **You don't need to assign dependencies on `window`.**
193
289
 
194
290
  For instance, with [jQuery](https://jquery.com/):
291
+
195
292
  ```diff
196
293
  // app/javascript/entrypoints/application.js
197
294
 
@@ -200,19 +297,20 @@ For instance, with [jQuery](https://jquery.com/):
200
297
  ```
201
298
 
202
299
  Instead do:
300
+
203
301
  ```js
204
302
  // config/webpack/webpack.config.js
205
303
 
206
- const webpack = require('webpack')
207
- const { generateWebpackConfig } = require('shakapacker')
304
+ const webpack = require("webpack")
305
+ const { generateWebpackConfig } = require("shakapacker")
208
306
 
209
307
  module.exports = generateWebpackConfig({
210
308
  plugins: [
211
309
  new webpack.ProvidePlugin({
212
- $: 'jquery',
213
- jQuery: 'jquery',
310
+ $: "jquery",
311
+ jQuery: "jquery"
214
312
  })
215
- ],
313
+ ]
216
314
  })
217
315
  ```
218
316
 
@@ -225,7 +323,7 @@ application is using your staging `config.asset_host` host when using
225
323
 
226
324
  This can be fixed by setting the environment variable `SHAKAPACKER_ASSET_HOST` to
227
325
  an empty string where your assets are compiled. On Heroku this is done under
228
- *Settings* -> *Config Vars*.
326
+ _Settings_ -> _Config Vars_.
229
327
 
230
328
  This way shakapacker won't hard-code the CDN host into the manifest file used by
231
329
  `javascript_pack_tag`, but instead fetch the CDN host at runtime, resolving the
@@ -243,6 +341,7 @@ In order to generate the storage path, we rely on the filename that's [provided
243
341
  This usually works out of the box. There's a potential problem however, if you use the [context setting](https://webpack.js.org/configuration/entry-context/#context) in your webpack config. By default this is set to current Node working directory/project root.
244
342
 
245
343
  If you were to override it like:
344
+
246
345
  ```
247
346
  {
248
347
  context: path.resolve(__dirname, '../../app/javascript')
@@ -252,12 +351,14 @@ If you were to override it like:
252
351
  Then the filename available in the rule generator will be relative to that directory.
253
352
 
254
353
  This means for example:
354
+
255
355
  - a static asset from `node_modules` folder could end up being referenced with path of `../../node_modules/some_module/static_file.jpg` rather than simply `node_modules/some_module/static_file.jpg`.
256
356
  - a static asset in one of the `additional_paths`, example `app/assets/images/image.jpg`, would end up being referenced with path of `../assets/images/image.jpg`.
257
357
 
258
358
  Those paths are later passed to [output path generation in the rule](https://github.com/shakacode/shakapacker/blob/e52b335dbabfb934fe7d3076a8322b97d5ef3470/package/rules/file.js#L25-L26), where we would end up with a path like `static/../../node_modules/some_module/static_file.jpg`, resulting in the file being output in a location two directories above the desired path.
259
359
 
260
360
  You can avoid this by:
361
+
261
362
  - not using overridden `context` in your webpack config, if there's no good reason for it.
262
363
  - using custom Webpack config to modify the static file rule, following a similar process as outlined in the [Webpack Configuration](https://github.com/shakacode/shakapacker/blob/main/README.md#webpack-configuration) section of the readme.
263
364
 
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Minimal shim - all logic is in the TypeScript module
4
+ const { run } = require('shakapacker/configExporter')
5
+
6
+ run(process.argv.slice(2))
7
+ .then((exitCode) => process.exit(exitCode))
8
+ .catch((error) => {
9
+ console.error(error.message)
10
+ process.exit(1)
11
+ })
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  ENV["RAILS_ENV"] ||= "development"
4
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
4
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
5
5
  ENV["APP_ROOT"] ||= File.expand_path("..", __dir__)
6
6
 
7
7
  require "bundler/setup"
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  ENV["RAILS_ENV"] ||= "development"
4
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
4
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
5
5
 
6
6
  require "bundler/setup"
7
7
  require "shakapacker"
@@ -241,6 +241,13 @@ module Shakapacker
241
241
  raise "Failed to install prod dependencies"
242
242
  end
243
243
  end
244
+
245
+ # Run a full install to ensure optional dependencies (like native bindings) are properly resolved
246
+ # This is especially important for packages like @rspack/core that use platform-specific native modules
247
+ unless package_json.manager.install
248
+ puts "❌ Failed to run full install to resolve optional dependencies"
249
+ raise "Failed to run full install"
250
+ end
244
251
  end
245
252
 
246
253
  def get_package_json
@@ -255,7 +255,21 @@ class Shakapacker::Configuration
255
255
  rescue ArgumentError
256
256
  YAML.load_file(config_path.to_s)
257
257
  end
258
- symbolized_config = config[env].deep_symbolize_keys
258
+
259
+ # Try to find environment-specific configuration with fallback
260
+ # Fallback order: requested env → production
261
+ if config[env]
262
+ env_config = config[env]
263
+ elsif config["production"]
264
+ log_fallback(env, "production")
265
+ env_config = config["production"]
266
+ else
267
+ # No suitable configuration found - rely on bundled defaults
268
+ log_fallback(env, "none (will use bundled defaults)")
269
+ env_config = nil
270
+ end
271
+
272
+ symbolized_config = env_config&.deep_symbolize_keys || {}
259
273
 
260
274
  return symbolized_config
261
275
  rescue Errno::ENOENT => e
@@ -280,7 +294,10 @@ class Shakapacker::Configuration
280
294
  rescue ArgumentError
281
295
  YAML.load_file(path)
282
296
  end
283
- HashWithIndifferentAccess.new(config[env] || config[Shakapacker::DEFAULT_ENV])
297
+ # Load defaults from bundled shakapacker.yml (always has all environments)
298
+ # Note: This differs from load() which reads user's config and may be missing environments
299
+ # Fallback to production ensures staging and other custom envs get production-like defaults
300
+ HashWithIndifferentAccess.new(config[env] || config["production"])
284
301
  end
285
302
  end
286
303
 
@@ -289,4 +306,13 @@ class Shakapacker::Configuration
289
306
 
290
307
  path
291
308
  end
309
+
310
+ def log_fallback(requested_env, fallback_env)
311
+ return unless Shakapacker.logger
312
+
313
+ Shakapacker.logger.info(
314
+ "Shakapacker environment '#{requested_env}' not found in #{config_path}, " \
315
+ "falling back to '#{fallback_env}'"
316
+ )
317
+ end
292
318
  end
@@ -327,6 +327,11 @@ module Shakapacker
327
327
  unless binstub_path.exist?
328
328
  @warnings << "Shakapacker binstub not found at bin/shakapacker. Run 'rails shakapacker:binstubs' to create it."
329
329
  end
330
+
331
+ export_config_binstub = root_path.join("bin/export-bundler-config")
332
+ unless export_config_binstub.exist?
333
+ @warnings << "Config export binstub not found at bin/export-bundler-config. Run 'rails shakapacker:binstubs' to create it."
334
+ end
330
335
  end
331
336
 
332
337
  def check_javascript_transpiler_dependencies
@@ -830,6 +835,11 @@ module Shakapacker
830
835
  if binstub_path.exist?
831
836
  puts "✓ Shakapacker binstub found"
832
837
  end
838
+
839
+ export_config_binstub = doctor.root_path.join("bin/export-bundler-config")
840
+ if export_config_binstub.exist?
841
+ puts "✓ Config export binstub found"
842
+ end
833
843
  end
834
844
 
835
845
  def print_info_messages
@@ -873,6 +883,12 @@ module Shakapacker
873
883
  package_manager = doctor.send(:package_manager)
874
884
  puts "To fix missing dependencies, run:"
875
885
  puts " #{package_manager_install_command(package_manager)}"
886
+ puts ""
887
+ puts "For debugging configuration issues, export your webpack/rspack config:"
888
+ puts " bin/export-bundler-config --doctor"
889
+ puts " (Exports annotated YAML configs for dev and production - best for troubleshooting)"
890
+ puts ""
891
+ puts " See 'bin/export-bundler-config --help' for more options"
876
892
  end
877
893
 
878
894
  def package_manager_install_command(manager)
@@ -6,7 +6,7 @@ module Shakapacker
6
6
  class RspackRunner < Shakapacker::Runner
7
7
  def self.run(argv)
8
8
  $stdout.sync = true
9
- ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
9
+ Shakapacker.ensure_node_env!
10
10
  new(argv).run
11
11
  end
12
12
 
@@ -24,7 +24,7 @@ module Shakapacker
24
24
  ].freeze
25
25
  def self.run(argv)
26
26
  $stdout.sync = true
27
- ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
27
+ Shakapacker.ensure_node_env!
28
28
 
29
29
  # Create a single runner instance to avoid loading configuration twice.
30
30
  # We extend it with the appropriate build command based on the bundler type.
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.1.0".freeze
3
+ VERSION = "9.2.0".freeze
4
4
  end
@@ -6,7 +6,7 @@ module Shakapacker
6
6
  class WebpackRunner < Shakapacker::Runner
7
7
  def self.run(argv)
8
8
  $stdout.sync = true
9
- ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
9
+ Shakapacker.ensure_node_env!
10
10
  new(argv).run
11
11
  end
12
12
 
data/lib/shakapacker.rb CHANGED
@@ -7,6 +7,9 @@ module Shakapacker
7
7
  extend self
8
8
 
9
9
  DEFAULT_ENV = "development".freeze
10
+ # Environments that use their RAILS_ENV value for NODE_ENV
11
+ # All other environments (production, staging, etc.) use "production" for webpack optimizations
12
+ DEV_TEST_ENVS = %w[development test].freeze
10
13
 
11
14
  def instance=(instance)
12
15
  @instance = instance
@@ -24,6 +27,13 @@ module Shakapacker
24
27
  ENV["NODE_ENV"] = original
25
28
  end
26
29
 
30
+ # Set NODE_ENV based on RAILS_ENV if not already set
31
+ # - development/test environments use their RAILS_ENV value
32
+ # - all other environments (production, staging, etc.) use "production" for webpack optimizations
33
+ def ensure_node_env!
34
+ ENV["NODE_ENV"] ||= DEV_TEST_ENVS.include?(ENV["RAILS_ENV"]) ? ENV["RAILS_ENV"] : "production"
35
+ end
36
+
27
37
  def ensure_log_goes_to_stdout
28
38
  old_logger = Shakapacker.logger
29
39
  Shakapacker.logger = Logger.new(STDOUT)
@@ -0,0 +1,72 @@
1
+ namespace :shakapacker do
2
+ desc <<~DESC
3
+ Export webpack or rspack configuration for debugging and analysis
4
+
5
+ Exports your resolved webpack/rspack configuration in human-readable formats.
6
+ Use this to debug configuration issues, compare environments, or analyze
7
+ client vs server bundle differences.
8
+
9
+ Usage:
10
+ rails shakapacker:export_bundler_config [OPTIONS]
11
+ rake shakapacker:export_bundler_config -- [OPTIONS]
12
+
13
+ Quick Start (Recommended):
14
+ rails shakapacker:export_bundler_config --doctor
15
+
16
+ This exports all configs (dev + prod, client + server) to shakapacker-config-exports/
17
+ directory in annotated YAML format - perfect for troubleshooting.
18
+
19
+ Common Options:
20
+ --doctor Export everything for troubleshooting (recommended)
21
+ --save Save current environment configs to files
22
+ --save-dir=<dir> Custom output directory (requires --save)
23
+ --env=development|production|test Specify environment
24
+ --client-only Export only client config
25
+ --server-only Export only server config
26
+ --format=yaml|json|inspect Output format
27
+ --help, -h Show detailed help
28
+
29
+ Examples:
30
+ # Export all configs for troubleshooting
31
+ rails shakapacker:export_bundler_config --doctor
32
+
33
+ # Save production client config
34
+ rails shakapacker:export_bundler_config --save --env=production --client-only
35
+
36
+ # View development config in terminal
37
+ rails shakapacker:export_bundler_config
38
+
39
+ # Show detailed help
40
+ rails shakapacker:export_bundler_config --help
41
+
42
+ Note: When using 'rake', you must use '--' to separate rake options from task arguments.
43
+ Example: rake shakapacker:export_bundler_config -- --doctor
44
+
45
+ The task automatically falls back to the gem version if bin/export-bundler-config
46
+ binstub is not installed. To install all binstubs, run: rails shakapacker:binstubs
47
+ DESC
48
+ task :export_bundler_config do
49
+ # Try to use the binstub if it exists, otherwise use the gem's version
50
+ bin_path = Rails.root.join("bin/export-bundler-config")
51
+
52
+ unless File.exist?(bin_path)
53
+ # Binstub not installed, use the gem's version directly
54
+ gem_bin_path = File.expand_path("../../install/bin/export-bundler-config", __dir__)
55
+
56
+ $stderr.puts "Note: bin/export-bundler-config binstub not found."
57
+ $stderr.puts "Using gem version directly. To install the binstub, run: rake shakapacker:binstubs"
58
+ $stderr.puts ""
59
+
60
+ Dir.chdir(Rails.root) do
61
+ exec("node", gem_bin_path, *ARGV[1..])
62
+ end
63
+ else
64
+ # Pass through command-line arguments after the task name
65
+ # Use exec to replace the rake process with the export script
66
+ # This ensures proper exit codes and signal handling
67
+ Dir.chdir(Rails.root) do
68
+ exec(bin_path.to_s, *ARGV[1..])
69
+ end
70
+ end
71
+ end
72
+ end