shakapacker 9.3.0.beta.1 → 9.3.0.beta.4

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.
data/docs/v9_upgrade.md CHANGED
@@ -4,6 +4,15 @@ This guide outlines new features, breaking changes, and migration steps for upgr
4
4
 
5
5
  **📖 For detailed configuration options, see the [Configuration Guide](./configuration.md)**
6
6
 
7
+ > **⚠️ Important:** Shakapacker is both a Ruby gem AND an npm package. **You must update BOTH**:
8
+ >
9
+ > - Update the version in `Gemfile`
10
+ > - Update the version in `package.json`
11
+ > - Run `bundle update shakapacker`
12
+ > - Run your package manager install command (`yarn install`, `npm install`, or `pnpm install`)
13
+ >
14
+ > See [Migration Steps](#migration-steps) below for detailed instructions including version format differences and testing.
15
+
7
16
  > **⚠️ Important for v9.1.0 Users:** If you're upgrading to v9.1.0 or later, please note the [SWC Configuration Breaking Change](#swc-loose-mode-breaking-change-v910) below. This affects users who previously configured SWC in v9.0.0.
8
17
 
9
18
  ## New Features
@@ -264,15 +273,49 @@ You won't get warnings about missing Babel, Rspack, or esbuild packages.
264
273
 
265
274
  ## Migration Steps
266
275
 
267
- ### Step 1: Update Dependencies
276
+ > **💡 Tip:** For general upgrade instructions applicable to all Shakapacker versions, see [Upgrading Shakapacker](./common-upgrades.md#upgrading-shakapacker) in the Common Upgrades guide.
277
+
278
+ ### Step 1: Update Gemfile
279
+
280
+ Update the shakapacker version in your `Gemfile`:
281
+
282
+ ```ruby
283
+ # Gemfile
284
+ gem "shakapacker", "9.3.0" # or latest version
285
+ ```
286
+
287
+ **Note:** Ruby gems use dot notation for pre-release versions (e.g., `9.3.0.beta.1`), while npm uses hyphen notation (e.g., `9.3.0-beta.1`). See [Version Format Differences](#version-format-differences) below.
288
+
289
+ ### Step 2: Update package.json
290
+
291
+ Update the shakapacker version in your `package.json`:
292
+
293
+ ```json
294
+ {
295
+ "dependencies": {
296
+ "shakapacker": "9.3.0"
297
+ }
298
+ }
299
+ ```
300
+
301
+ **Note:** For pre-release versions, npm uses hyphen notation (e.g., `"shakapacker": "9.3.0-beta.1"`).
302
+
303
+ ### Step 3: Install Updates
304
+
305
+ Run both bundler and your package manager:
268
306
 
269
307
  ```bash
270
- npm update shakapacker@^9.0.0
271
- # or
272
- yarn upgrade shakapacker@^9.0.0
308
+ # Update Ruby gem
309
+ bundle update shakapacker
310
+
311
+ # Update npm package (choose one based on your package manager)
312
+ yarn install # if using Yarn
313
+ npm install # if using npm
314
+ pnpm install # if using pnpm
315
+ bun install # if using Bun
273
316
  ```
274
317
 
275
- ### Step 2: Update CSS Module Imports
318
+ ### Step 4: Update CSS Module Imports
276
319
 
277
320
  #### For each CSS module import:
278
321
 
@@ -299,7 +342,7 @@ declare module "*.module.css" {
299
342
  }
300
343
  ```
301
344
 
302
- ### Step 3: Handle Kebab-Case Class Names
345
+ ### Step 5: Handle Kebab-Case Class Names
303
346
 
304
347
  v9 automatically converts kebab-case to camelCase with `exportLocalsConvention: 'camelCaseOnly'`:
305
348
 
@@ -340,7 +383,7 @@ const buttonClass = styles['my-button'];
340
383
 
341
384
  **Note:** With `'camelCaseOnly'` (default) or `'dashesOnly'`, only one version is exported. If you need both the original and camelCase versions, you would need to use `'camelCase'` instead, but this requires `namedExport: false` (v8 behavior). See the [CSS Modules Export Mode documentation](./css-modules-export-mode.md) for details on reverting to v8 behavior.
342
385
 
343
- ### Step 4: Update Configuration Files
386
+ ### Step 6: Update Configuration Files
344
387
 
345
388
  If you have `webpack_loader` in your configuration:
346
389
 
@@ -353,7 +396,7 @@ If you have `webpack_loader` in your configuration:
353
396
  javascript_transpiler: "babel"
354
397
  ```
355
398
 
356
- ### Step 5: Run Tests
399
+ ### Step 7: Run Tests
357
400
 
358
401
  ```bash
359
402
  # Run your test suite
@@ -366,6 +409,45 @@ bin/shakapacker
366
409
  bin/shakapacker-dev-server
367
410
  ```
368
411
 
412
+ ## Version Format Differences
413
+
414
+ Shakapacker version numbers differ between the Ruby gem and npm package for pre-release versions:
415
+
416
+ | Version Type | Ruby Gem (Gemfile) | npm Package (package.json) |
417
+ | ------------ | ------------------ | -------------------------- |
418
+ | Stable | `"9.3.0"` | `"9.3.0"` |
419
+ | Pre-release | `"9.3.0.beta.1"` | `"9.3.0-beta.1"` |
420
+
421
+ **Examples:**
422
+
423
+ ```ruby
424
+ # Gemfile - uses dots for pre-release versions
425
+ gem "shakapacker", "9.3.0" # stable
426
+ gem "shakapacker", "9.3.0.beta.1" # pre-release
427
+ ```
428
+
429
+ Stable version in package.json:
430
+
431
+ ```json
432
+ {
433
+ "dependencies": {
434
+ "shakapacker": "9.3.0"
435
+ }
436
+ }
437
+ ```
438
+
439
+ Pre-release version in package.json:
440
+
441
+ ```json
442
+ {
443
+ "dependencies": {
444
+ "shakapacker": "9.3.0-beta.1"
445
+ }
446
+ }
447
+ ```
448
+
449
+ This is due to different versioning conventions between RubyGems (which uses dots) and npm (which follows semantic versioning with hyphens for pre-release identifiers).
450
+
369
451
  ## Troubleshooting
370
452
 
371
453
  ### CSS Classes Not Applying
data/eslint.config.js CHANGED
@@ -14,11 +14,16 @@ module.exports = [
14
14
  // Global ignores (replaces .eslintignore)
15
15
  {
16
16
  ignores: [
17
- "lib/**",
18
- "**/node_modules/**",
19
- "vendor/**",
20
- "spec/**",
21
- "package/**" // TODO: Remove after issue #644 is resolved (lints package/ TS source files)
17
+ "lib/**", // Ruby files, not JavaScript
18
+ "**/node_modules/**", // Third-party dependencies
19
+ "vendor/**", // Vendored dependencies
20
+ "spec/**", // Ruby specs, not JavaScript
21
+ "package/**/*.js", // Generated/compiled JavaScript from TypeScript
22
+ "package/**/*.d.ts", // Generated TypeScript declaration files
23
+ // Temporarily ignore TypeScript files until technical debt is resolved
24
+ // See ESLINT_TECHNICAL_DEBT.md for tracking
25
+ // TODO: Remove this once ESLint issues are fixed (tracked in #723)
26
+ "package/**/*.ts"
22
27
  ]
23
28
  },
24
29
 
@@ -52,7 +57,11 @@ module.exports = [
52
57
  "import/no-extraneous-dependencies": "off",
53
58
  // TypeScript handles extensions, not needed for JS imports
54
59
  "import/extensions": "off",
55
- indent: ["error", 2]
60
+ indent: ["error", 2],
61
+ // Allow for...of loops - modern JS syntax, won't pollute client code
62
+ "no-restricted-syntax": "off",
63
+ // Allow console statements - used for debugging/logging throughout
64
+ "no-console": "off"
56
65
  },
57
66
  settings: {
58
67
  react: {
@@ -131,7 +140,110 @@ module.exports = [
131
140
  // Strict: no 'any' types allowed - use 'unknown' or specific types instead
132
141
  "@typescript-eslint/no-explicit-any": "error",
133
142
  // Allow implicit return types - TypeScript can infer them
134
- "@typescript-eslint/explicit-module-boundary-types": "off"
143
+ "@typescript-eslint/explicit-module-boundary-types": "off",
144
+ // Disable no-undef for TypeScript - TypeScript compiler handles this
145
+ // This prevents false positives for ambient types like NodeJS.ProcessEnv
146
+ "no-undef": "off"
147
+ }
148
+ },
149
+
150
+ // Temporary overrides for files with remaining errors
151
+ // See ESLINT_TECHNICAL_DEBT.md for detailed documentation
152
+ //
153
+ // These overrides suppress ~172 errors that require either:
154
+ // 1. Major type refactoring (any/unsafe-* rules)
155
+ // 2. Potential breaking changes (module system)
156
+ // 3. Significant code restructuring
157
+ //
158
+ // GitHub Issues tracking this technical debt:
159
+ // - #707: TypeScript: Refactor configExporter module for type safety
160
+ // - #708: Module System: Modernize to ES6 modules with codemod
161
+ // - #709: Code Style: Fix remaining ESLint style issues
162
+ {
163
+ // Consolidated override for package/config.ts and package/babel/preset.ts
164
+ // Combines rules from both previous override blocks to avoid duplication
165
+ files: ["package/babel/preset.ts", "package/config.ts"],
166
+ rules: {
167
+ // From first override block
168
+ "@typescript-eslint/no-require-imports": "off",
169
+ "@typescript-eslint/no-unused-vars": "off",
170
+ "@typescript-eslint/no-unsafe-call": "off",
171
+ "import/order": "off",
172
+ "import/newline-after-import": "off",
173
+ "import/first": "off",
174
+ // Additional rules that were in the second override for config.ts
175
+ "@typescript-eslint/no-unsafe-assignment": "off",
176
+ "@typescript-eslint/no-unsafe-member-access": "off",
177
+ "@typescript-eslint/no-unsafe-argument": "off",
178
+ "@typescript-eslint/no-explicit-any": "off",
179
+ "no-useless-escape": "off",
180
+ "no-continue": "off"
181
+ }
182
+ },
183
+ {
184
+ files: ["package/configExporter/**/*.ts"],
185
+ rules: {
186
+ "@typescript-eslint/no-explicit-any": "off",
187
+ "@typescript-eslint/no-unsafe-assignment": "off",
188
+ "@typescript-eslint/no-unsafe-member-access": "off",
189
+ "@typescript-eslint/no-unsafe-call": "off",
190
+ "@typescript-eslint/no-unsafe-return": "off",
191
+ "@typescript-eslint/no-unsafe-argument": "off",
192
+ "@typescript-eslint/no-unsafe-function-type": "off",
193
+ "@typescript-eslint/no-unused-vars": "off",
194
+ "@typescript-eslint/require-await": "off",
195
+ "no-await-in-loop": "off",
196
+ "import/prefer-default-export": "off",
197
+ "global-require": "off",
198
+ "no-underscore-dangle": "off"
199
+ }
200
+ },
201
+ {
202
+ // Remaining utils files (removed package/config.ts from this block)
203
+ files: [
204
+ "package/utils/inliningCss.ts",
205
+ "package/utils/errorCodes.ts",
206
+ "package/utils/errorHelpers.ts",
207
+ "package/utils/pathValidation.ts"
208
+ ],
209
+ rules: {
210
+ "@typescript-eslint/no-unsafe-assignment": "off",
211
+ "@typescript-eslint/no-unsafe-member-access": "off",
212
+ "@typescript-eslint/no-unsafe-argument": "off",
213
+ "@typescript-eslint/no-explicit-any": "off",
214
+ "no-useless-escape": "off",
215
+ "no-continue": "off"
216
+ }
217
+ },
218
+ {
219
+ files: ["package/plugins/**/*.ts", "package/optimization/**/*.ts"],
220
+ rules: {
221
+ "@typescript-eslint/no-unsafe-assignment": "off",
222
+ "@typescript-eslint/no-unsafe-call": "off",
223
+ "@typescript-eslint/no-redundant-type-constituents": "off",
224
+ "import/prefer-default-export": "off"
225
+ }
226
+ },
227
+ {
228
+ files: [
229
+ "package/environments/**/*.ts",
230
+ "package/index.ts",
231
+ "package/rspack/index.ts",
232
+ "package/rules/**/*.ts",
233
+ "package/swc/index.ts",
234
+ "package/esbuild/index.ts",
235
+ "package/dev_server.ts",
236
+ "package/env.ts"
237
+ ],
238
+ rules: {
239
+ "@typescript-eslint/no-unsafe-assignment": "off",
240
+ "@typescript-eslint/no-unsafe-call": "off",
241
+ "@typescript-eslint/no-unsafe-return": "off",
242
+ "@typescript-eslint/no-redundant-type-constituents": "off",
243
+ "@typescript-eslint/no-unused-vars": "off",
244
+ "@typescript-eslint/no-unsafe-function-type": "off",
245
+ "import/prefer-default-export": "off",
246
+ "no-underscore-dangle": "off"
135
247
  }
136
248
  },
137
249
 
@@ -0,0 +1,147 @@
1
+ require "yaml"
2
+
3
+ module Shakapacker
4
+ class BuildConfigLoader
5
+ attr_reader :config_file_path
6
+
7
+ def initialize(config_file_path = nil)
8
+ @config_file_path = config_file_path || File.join(Dir.pwd, "config", "shakapacker-builds.yml")
9
+ end
10
+
11
+ def exists?
12
+ File.exist?(@config_file_path)
13
+ end
14
+
15
+ def load_build(build_name)
16
+ unless exists?
17
+ raise ArgumentError, "Config file not found: #{@config_file_path}\n" \
18
+ "Run 'bin/shakapacker --init' to generate a sample config file."
19
+ end
20
+
21
+ config = load_config
22
+ fetch_build_or_raise(config, build_name)
23
+ end
24
+
25
+ def resolve_build_config(build_name, default_bundler: "webpack")
26
+ config = load_config
27
+ build = fetch_build_or_raise(config, build_name)
28
+
29
+ # Resolve bundler with precedence: build.bundler > config.default_bundler > default_bundler
30
+ bundler = build["bundler"] || config["default_bundler"] || default_bundler
31
+
32
+ # Get environment variables
33
+ environment = build["environment"] || {}
34
+
35
+ # Get config file path if specified
36
+ config_file = build["config"]
37
+ if config_file
38
+ # Expand ${BUNDLER} variable
39
+ config_file = config_file.gsub("${BUNDLER}", bundler)
40
+ end
41
+
42
+ # Get bundler_env for --env flags
43
+ bundler_env = build["bundler_env"] || {}
44
+
45
+ # Get outputs
46
+ outputs = build["outputs"] || []
47
+
48
+ # Validate outputs
49
+ if outputs.empty?
50
+ raise ArgumentError, "Build '#{build_name}' has empty outputs array. " \
51
+ "Please specify at least one output type (client, server, or all)."
52
+ end
53
+
54
+ {
55
+ name: build_name,
56
+ description: build["description"],
57
+ bundler: bundler,
58
+ dev_server: build["dev_server"],
59
+ environment: environment,
60
+ bundler_env: bundler_env,
61
+ outputs: outputs,
62
+ config_file: config_file
63
+ }
64
+ end
65
+
66
+ def uses_dev_server?(build_config)
67
+ # Check explicit dev_server flag first (preferred)
68
+ # Only return early if the value is explicitly set (not nil)
69
+ return build_config[:dev_server] unless build_config[:dev_server].nil?
70
+
71
+ # Fallback: check environment variables for backward compatibility
72
+ env = build_config[:environment]
73
+ return false unless env
74
+
75
+ # Handle both string "true" and boolean true from YAML
76
+ %w[WEBPACK_SERVE HMR].any? do |key|
77
+ value = env[key]
78
+ value.to_s.strip.casecmp("true").zero?
79
+ end
80
+ end
81
+
82
+ def list_builds
83
+ config = load_config
84
+ builds = config["builds"]
85
+
86
+ puts "\nAvailable builds in #{@config_file_path}:\n\n"
87
+
88
+ builds.each do |name, build|
89
+ bundler = build["bundler"] || config["default_bundler"] || "webpack (default)"
90
+ outputs = build["outputs"] ? build["outputs"].join(", ") : "missing (invalid)"
91
+
92
+ puts " #{name}"
93
+ puts " Description: #{build["description"]}" if build["description"]
94
+ puts " Bundler: #{bundler}"
95
+ puts " Outputs: #{outputs}"
96
+ puts ""
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def fetch_build_or_raise(config, build_name)
103
+ build = config["builds"][build_name]
104
+ unless build
105
+ available = config["builds"].keys.join(", ")
106
+ raise ArgumentError, "Build '#{build_name}' not found in config file.\n" \
107
+ "Available builds: #{available}\n" \
108
+ "Use 'bin/shakapacker --list-builds' to see all available builds."
109
+ end
110
+ build
111
+ end
112
+
113
+ # Load YAML config file safely with Ruby version compatibility
114
+ # Ruby 3.1+ supports safe_load_file with aliases, older versions need safe_load
115
+ def load_config
116
+ begin
117
+ config = if YAML.respond_to?(:safe_load_file)
118
+ # Ruby 3.1+: Use safe_load_file with aliases enabled
119
+ YAML.safe_load_file(@config_file_path, aliases: true)
120
+ else
121
+ # Ruby 2.7-3.0: Use safe_load with aliases enabled
122
+ YAML.safe_load(
123
+ File.read(@config_file_path),
124
+ permitted_classes: [],
125
+ permitted_symbols: [],
126
+ aliases: true
127
+ )
128
+ end
129
+ rescue ArgumentError
130
+ # Fallback for older Psych versions without aliases support
131
+ config = YAML.safe_load(
132
+ File.read(@config_file_path),
133
+ permitted_classes: [],
134
+ permitted_symbols: []
135
+ )
136
+ end
137
+
138
+ unless config["builds"]&.is_a?(Hash)
139
+ raise ArgumentError, "Config file must contain a 'builds' object"
140
+ end
141
+
142
+ config
143
+ rescue Psych::SyntaxError => e
144
+ raise ArgumentError, "Invalid YAML in config file: #{e.message}"
145
+ end
146
+ end
147
+ end
@@ -8,12 +8,13 @@ class Shakapacker::Configuration
8
8
  attr_accessor :installing
9
9
  end
10
10
 
11
- attr_reader :root_path, :config_path, :env
11
+ attr_reader :root_path, :config_path, :env, :bundler_override
12
12
 
13
- def initialize(root_path:, config_path:, env:)
13
+ def initialize(root_path:, config_path:, env:, bundler_override: nil)
14
14
  @root_path = root_path
15
15
  @env = env
16
16
  @config_path = config_path
17
+ @bundler_override = bundler_override
17
18
  end
18
19
 
19
20
  def dev_server
@@ -97,6 +98,9 @@ class Shakapacker::Configuration
97
98
  end
98
99
 
99
100
  def assets_bundler
101
+ # CLI --bundler flag takes highest precedence
102
+ return @bundler_override if @bundler_override
103
+
100
104
  # Show deprecation warning if using old 'bundler' key
101
105
  if data.has_key?(:bundler) && !data.has_key?(:assets_bundler)
102
106
  $stderr.puts "⚠️ DEPRECATION WARNING: The 'bundler' configuration option is deprecated. Please use 'assets_bundler' instead to avoid confusion with Ruby's Bundler gem manager."
@@ -18,9 +18,71 @@ module Shakapacker
18
18
  exit(0)
19
19
  end
20
20
 
21
+ # Check for --build flag
22
+ build_index = argv.index("--build")
23
+ if build_index
24
+ build_name = argv[build_index + 1]
25
+
26
+ unless build_name
27
+ $stderr.puts "[Shakapacker] Error: --build requires a build name"
28
+ $stderr.puts "Usage: bin/shakapacker-dev-server --build <name>"
29
+ exit(1)
30
+ end
31
+
32
+ loader = BuildConfigLoader.new
33
+
34
+ unless loader.exists?
35
+ $stderr.puts "[Shakapacker] Config file not found: #{loader.config_file_path}"
36
+ $stderr.puts "Run 'bin/shakapacker --init' to create one"
37
+ exit(1)
38
+ end
39
+
40
+ begin
41
+ build_config = loader.resolve_build_config(build_name)
42
+
43
+ # Check if this build is meant for dev server
44
+ unless loader.uses_dev_server?(build_config)
45
+ $stderr.puts "[Shakapacker] Error: Build '#{build_name}' is not configured for dev server (dev_server: false)"
46
+ $stderr.puts "[Shakapacker] Use this command instead:"
47
+ $stderr.puts " bin/shakapacker --build #{build_name}"
48
+ exit(1)
49
+ end
50
+
51
+ # Remove --build and build name from argv
52
+ remaining_argv = argv.dup
53
+ remaining_argv.delete_at(build_index + 1)
54
+ remaining_argv.delete_at(build_index)
55
+
56
+ run_with_build_config(remaining_argv, build_config)
57
+ return
58
+ rescue ArgumentError => e
59
+ $stderr.puts "[Shakapacker] #{e.message}"
60
+ exit(1)
61
+ end
62
+ end
63
+
21
64
  new(argv).run
22
65
  end
23
66
 
67
+ def self.run_with_build_config(argv, build_config)
68
+ # Apply build config environment variables
69
+ build_config[:environment].each do |key, value|
70
+ ENV[key] = value.to_s
71
+ end
72
+
73
+ # Set SHAKAPACKER_ASSETS_BUNDLER so JS/TS config files use the correct bundler
74
+ # This ensures the bundler override (from --bundler or build config) is respected
75
+ ENV["SHAKAPACKER_ASSETS_BUNDLER"] = build_config[:bundler]
76
+
77
+ puts "[Shakapacker] Running dev server for build: #{build_config[:name]}"
78
+ puts "[Shakapacker] Description: #{build_config[:description]}" if build_config[:description]
79
+ puts "[Shakapacker] Bundler: #{build_config[:bundler]}"
80
+ puts "[Shakapacker] Config file: #{build_config[:config_file]}" if build_config[:config_file]
81
+
82
+ # Pass bundler override so Configuration.assets_bundler reflects the build
83
+ new(argv, build_config, build_config[:bundler]).run
84
+ end
85
+
24
86
  def self.print_help
25
87
  puts <<~HELP
26
88
  ================================================================================
@@ -33,6 +95,17 @@ module Shakapacker
33
95
  -h, --help Show this help message
34
96
  -v, --version Show Shakapacker version
35
97
  --debug-shakapacker Enable Node.js debugging (--inspect-brk)
98
+ --build <name> Run a specific build configuration
99
+
100
+ Build configurations (config/shakapacker-builds.yml):
101
+ bin/shakapacker-dev-server --build dev-hmr # Run the 'dev-hmr' build
102
+
103
+ To manage builds:
104
+ bin/shakapacker --init # Create config file
105
+ bin/shakapacker --list-builds # List available builds
106
+
107
+ Note: You can also use bin/shakapacker --build with a build that has
108
+ WEBPACK_SERVE=true, and it will automatically use the dev server.
36
109
 
37
110
  Examples:
38
111
  bin/shakapacker-dev-server # Start dev server