shakapacker 8.3.0 → 9.0.0.beta.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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/STATUS.md +1 -0
  3. data/.github/workflows/dummy.yml +1 -1
  4. data/.github/workflows/generator.yml +4 -14
  5. data/.github/workflows/node.yml +1 -1
  6. data/CHANGELOG.md +17 -1
  7. data/Gemfile.lock +3 -3
  8. data/README.md +3 -3
  9. data/docs/css-modules-export-mode.md +288 -0
  10. data/docs/peer-dependencies.md +40 -0
  11. data/docs/react.md +2 -10
  12. data/docs/rspack.md +190 -0
  13. data/docs/subresource_integrity.md +54 -0
  14. data/docs/troubleshooting.md +5 -0
  15. data/lib/install/bin/shakapacker +14 -2
  16. data/lib/install/bin/shakapacker-rspack +13 -0
  17. data/lib/install/config/rspack/rspack.config.js +6 -0
  18. data/lib/install/config/shakapacker.yml +16 -3
  19. data/lib/install/package.json +30 -0
  20. data/lib/install/template.rb +13 -3
  21. data/lib/shakapacker/configuration.rb +16 -0
  22. data/lib/shakapacker/dev_server_runner.rb +17 -7
  23. data/lib/shakapacker/helper.rb +40 -4
  24. data/lib/shakapacker/manifest.rb +9 -3
  25. data/lib/shakapacker/rspack_runner.rb +57 -0
  26. data/lib/shakapacker/runner.rb +48 -2
  27. data/lib/shakapacker/version.rb +1 -1
  28. data/package/config.js +2 -0
  29. data/package/environments/base.js +16 -48
  30. data/package/environments/development.js +18 -3
  31. data/package/environments/production.js +24 -51
  32. data/package/environments/test.js +15 -1
  33. data/package/index.d.ts +14 -0
  34. data/package/index.js +4 -2
  35. data/package/optimization/rspack.js +25 -0
  36. data/package/optimization/webpack.js +49 -0
  37. data/package/plugins/rspack.js +104 -0
  38. data/package/plugins/webpack.js +62 -0
  39. data/package/rules/css.js +1 -1
  40. data/package/rules/file.js +11 -5
  41. data/package/rules/less.js +1 -1
  42. data/package/rules/raw.js +11 -1
  43. data/package/rules/rspack.js +96 -0
  44. data/package/rules/sass.js +6 -2
  45. data/package/rules/stylus.js +1 -1
  46. data/package/utils/getStyleRule.js +16 -3
  47. data/package/utils/requireOrError.js +15 -0
  48. data/package.json +19 -26
  49. data/test/package/config.test.js +40 -0
  50. data/test/package/environments/base.test.js +1 -1
  51. data/test/package/rules/{index.test.js → webpack.test.js} +1 -1
  52. data/yarn.lock +2146 -724
  53. metadata +22 -11
  54. /data/package/rules/{index.js → webpack.js} +0 -0
@@ -0,0 +1,54 @@
1
+ # Subresource integrity
2
+ It's a cryptographic hash that helps browsers check that the served js or css file has not been tampered in any way.
3
+
4
+ [MDN - Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)
5
+
6
+ ## Important notes
7
+ - If you somehow modify the file after the hash was generated, it will automatically be considered as tampered, and the browser will not allow it to be executed.
8
+ - Enabling subresource integrity generation, will change the structure of `manifest.json`. Keep that in mind if you utilize this file in any other custom implementation.
9
+
10
+ Before:
11
+ ```json
12
+ {
13
+ "application.js": "/path_to_asset"
14
+ }
15
+ ```
16
+
17
+ After:
18
+ ```json
19
+ {
20
+ "application.js": {
21
+ "src": "/path_to_asset",
22
+ "integrity": "<sha256-hash> <sha384-hash> <sha512-hash>"
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Possible CORS issues
28
+ Enabling subresource integrity for an asset, actually enforces CORS checks on that resource too. Which means that
29
+ if you haven't set that up properly beforehand, it will probably lead to CORS errors with cached assets.
30
+
31
+ [MDN - How browsers handle Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity#how_browsers_handle_subresource_integrity)
32
+
33
+ ## Configuration
34
+
35
+ By default, this setting is disabled, to ensure backwards compatibility, and let developers adapt at their own pace.
36
+ This may change in the future, as it is a very nice security feature, and it should be enabled by default.
37
+
38
+ To enable it, just add this in `shakapacker.yml`
39
+ ```yml
40
+ integrity:
41
+ enabled: true
42
+ ```
43
+
44
+ For further customization, you can also utilize the options `hash_functions` that control the functions used to generate
45
+ integrity hashes. And `cross_origin` that sets the cross-origin loading attribute.
46
+
47
+ ```yml
48
+ integrity:
49
+ enabled: true
50
+ hash_functions: ["sha256", "sha384", "sha512"]
51
+ cross_origin: "anonymous" # or "use-credentials"
52
+ ```
53
+
54
+ This will utilize under the hood webpack-subresource-integrity plugin and will modify `manifest.json` to include integrity hashes.
@@ -17,6 +17,11 @@
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.
21
+ ```
22
+ NODE_ENV=development bin/shakapacker --profile --json > /tmp/webpack-stats.json
23
+ ```
24
+
20
25
  ## Incorrect peer dependencies
21
26
  Shakapacker uses peer dependencies to make it easier to manage what versions are being used for your app, which is especially
22
27
  useful for patching security vulnerabilities. However, not all package managers will actually enforce these versions - notably,
@@ -4,10 +4,22 @@ ENV["RAILS_ENV"] ||= "development"
4
4
  ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
5
5
 
6
6
  require "bundler/setup"
7
+ require "pathname"
7
8
  require "shakapacker"
8
- require "shakapacker/webpack_runner"
9
9
 
10
10
  APP_ROOT = File.expand_path("..", __dir__)
11
11
  Dir.chdir(APP_ROOT) do
12
- Shakapacker::WebpackRunner.run(ARGV)
12
+ config = Shakapacker::Configuration.new(
13
+ root_path: Pathname.new(APP_ROOT),
14
+ config_path: Pathname.new(File.join(APP_ROOT, "config/shakapacker.yml")),
15
+ env: ENV["RAILS_ENV"] || ENV["NODE_ENV"] || "development"
16
+ )
17
+
18
+ if config.rspack?
19
+ require "shakapacker/rspack_runner"
20
+ Shakapacker::RspackRunner.run(ARGV)
21
+ else
22
+ require "shakapacker/webpack_runner"
23
+ Shakapacker::WebpackRunner.run(ARGV)
24
+ end
13
25
  end
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ENV["RAILS_ENV"] ||= "development"
4
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
5
+
6
+ require "bundler/setup"
7
+ require "shakapacker"
8
+ require "shakapacker/rspack_runner"
9
+
10
+ APP_ROOT = File.expand_path("..", __dir__)
11
+ Dir.chdir(APP_ROOT) do
12
+ Shakapacker::RspackRunner.run(ARGV)
13
+ end
@@ -0,0 +1,6 @@
1
+ // See the shakacode/shakapacker README and docs directory for advice on customizing your rspackConfig.
2
+ const { generateRspackConfig } = require('shakapacker/rspack')
3
+
4
+ const rspackConfig = generateRspackConfig()
5
+
6
+ module.exports = rspackConfig
@@ -39,10 +39,13 @@ default: &default
39
39
  # Select loader to use, available options are 'babel' (default), 'swc' or 'esbuild'
40
40
  webpack_loader: 'babel'
41
41
 
42
+ # Select bundler to use, available options are 'webpack' (default) or 'rspack'
43
+ bundler: 'webpack'
44
+
42
45
  # Raises an error if there is a mismatch in the shakapacker gem and npm package being used
43
46
  ensure_consistent_versioning: true
44
47
 
45
- # Select whether the compiler will use SHA digest ('digest' option) or most most recent modified timestamp ('mtime') to determine freshness
48
+ # Select whether the compiler will use SHA digest ('digest' option) or most recent modified timestamp ('mtime') to determine freshness
46
49
  compiler_strategy: digest
47
50
 
48
51
  # Select whether the compiler will always use a content hash and not just in production
@@ -55,6 +58,16 @@ default: &default
55
58
  # SHAKAPACKER_ASSET_HOST will override both configurations.
56
59
  # asset_host: custom-path
57
60
 
61
+ # Utilizing webpack-subresource-integrity plugin, will generate integrity hashes for all entries in manifest.json
62
+ # https://github.com/waysact/webpack-subresource-integrity/tree/main/webpack-subresource-integrity
63
+ integrity:
64
+ enabled: false
65
+ # Which cryptographic function(s) to use, for generating the integrity hash(es). Default sha-384. Other possible values sha256, sha512
66
+ hash_functions: ["sha384"]
67
+ # Default "anonymous". Other possible value "use-credentials"
68
+ # https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity#cross-origin_resource_sharing_and_subresource_integrity
69
+ cross_origin: "anonymous"
70
+
58
71
  development:
59
72
  <<: *default
60
73
  compile: true
@@ -71,7 +84,7 @@ development:
71
84
  # Hot Module Replacement updates modules while the application is running without a full reload
72
85
  # Used instead of the `hot` key in https://webpack.js.org/configuration/dev-server/#devserverhot
73
86
  hmr: false
74
- # If HMR is on, CSS will by inlined by delivering it as part of the script payload via style-loader. Be sure
87
+ # If HMR is on, CSS will be inlined by delivering it as part of the script payload via style-loader. Be sure
75
88
  # that you add style-loader to your project dependencies.
76
89
  #
77
90
  # If you want to instead deliver CSS via <link> with the mini-css-extract-plugin, set inline_css to false.
@@ -114,7 +127,7 @@ production:
114
127
  # Production depends on precompilation of packs prior to booting for performance.
115
128
  compile: false
116
129
 
117
- # Use content hash for naming assets. Cannot be overridden by for production.
130
+ # Use content hash for naming assets. Cannot be overridden in production.
118
131
  useContentHash: true
119
132
 
120
133
  # Cache manifest.json for performance
@@ -0,0 +1,30 @@
1
+ {
2
+ "rspack": {
3
+ "@rspack/cli": "^1.0.0",
4
+ "@rspack/core": "^1.0.0",
5
+ "rspack-manifest-plugin": "^5.0.0"
6
+ },
7
+ "webpack": {
8
+ "mini-css-extract-plugin": "^2.0.0",
9
+ "terser-webpack-plugin": "^5.3.1",
10
+ "webpack": "^5.76.0",
11
+ "webpack-assets-manifest": "^5.0.6 || ^6.0.0",
12
+ "webpack-cli": "^4.9.2 || ^5.0.0 || ^6.0.0",
13
+ "webpack-dev-server": "^4.15.2 || ^5.2.2",
14
+ "webpack-merge": "^5.8.0 || ^6.0.0",
15
+ "webpack-subresource-integrity": "^5.1.0"
16
+ },
17
+ "common": {
18
+ "compression-webpack-plugin": "^9.0.0 || ^10.0.0|| ^11.0.0",
19
+ "css-loader": "^6.0.0 || ^7.0.0",
20
+ "sass-loader": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
21
+ "style-loader": "^3.0.0 || ^4.0.0"
22
+ },
23
+ "babel": {
24
+ "@babel/core": "^7.17.9",
25
+ "@babel/plugin-transform-runtime": "^7.17.0",
26
+ "@babel/preset-env": "^7.16.11",
27
+ "@babel/runtime": "^7.17.9",
28
+ "babel-loader": "^8.2.4 || ^9.0.0 || ^10.0.0"
29
+ }
30
+ }
@@ -8,7 +8,6 @@ require "package_json"
8
8
  force_option = ENV["FORCE"] ? { force: true } : {}
9
9
 
10
10
  copy_file "#{__dir__}/config/shakapacker.yml", "config/shakapacker.yml", force_option
11
- remove_file "#{__dir__}/package.json" if force_option[:force]
12
11
 
13
12
  say "Copying webpack core config"
14
13
  directory "#{__dir__}/config/webpack", "config/webpack", force_option
@@ -112,7 +111,15 @@ rescue PackageJson::Error
112
111
  end
113
112
 
114
113
  def fetch_peer_dependencies
115
- PackageJson.read("#{__dir__}/../../").fetch("peerDependencies")
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") : {}
116
123
  end
117
124
 
118
125
  Dir.chdir(Rails.root) do
@@ -130,13 +137,16 @@ Dir.chdir(Rails.root) do
130
137
  end
131
138
 
132
139
  peers = fetch_peer_dependencies
140
+ peers = peers.merge(fetch_common_dependencies)
141
+ peers = peers.merge(fetch_babel_dependencies)
142
+
133
143
  dev_dependency_packages = ["webpack-dev-server"]
134
144
 
135
145
  dependencies_to_add = []
136
146
  dev_dependencies_to_add = []
137
147
 
138
148
  peers.each do |(package, version)|
139
- major_version = version.match(/(\d+)/)[1]
149
+ major_version = version.split("||").last.match(/(\d+)/)[1]
140
150
  entry = "#{package}@#{major_version}"
141
151
 
142
152
  if dev_dependency_packages.include? package
@@ -88,6 +88,18 @@ class Shakapacker::Configuration
88
88
  fetch(:compiler_strategy)
89
89
  end
90
90
 
91
+ def bundler
92
+ fetch(:bundler) || "webpack"
93
+ end
94
+
95
+ def rspack?
96
+ bundler == "rspack"
97
+ end
98
+
99
+ def webpack?
100
+ bundler == "webpack"
101
+ end
102
+
91
103
  def fetch(key)
92
104
  data.fetch(key, defaults[key])
93
105
  end
@@ -99,6 +111,10 @@ class Shakapacker::Configuration
99
111
  )
100
112
  end
101
113
 
114
+ def integrity
115
+ fetch(:integrity)
116
+ end
117
+
102
118
  private
103
119
  def data
104
120
  @data ||= load
@@ -75,12 +75,20 @@ module Shakapacker
75
75
  env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --inspect-brk --trace-warnings"
76
76
  end
77
77
 
78
- cmd += ["--config", @webpack_config]
79
- cmd += ["--progress", "--color"] if @pretty
80
-
81
- # Default behavior of webpack-dev-server is @hot = true
82
- cmd += ["--hot", "only"] if @hot == "only"
83
- cmd += ["--no-hot"] if !@hot
78
+ # Add bundler-specific flags and config
79
+ bundler = get_bundler_type
80
+ if bundler == "webpack"
81
+ cmd += ["--config", @webpack_config]
82
+ cmd += ["--progress", "--color"] if @pretty
83
+ # Default behavior of webpack-dev-server is @hot = true
84
+ cmd += ["--hot", "only"] if @hot == "only"
85
+ cmd += ["--no-hot"] if !@hot
86
+ elsif bundler == "rspack"
87
+ # Only add config for rspack if it's not a rspack-specific command
88
+ cmd += ["--config", @webpack_config]
89
+ # Rspack supports --hot but not --no-hot or --progress/--color
90
+ cmd += ["--hot"] if @hot && @hot != false
91
+ end
84
92
 
85
93
  cmd += @argv
86
94
 
@@ -90,7 +98,9 @@ module Shakapacker
90
98
  end
91
99
 
92
100
  def build_cmd
93
- package_json.manager.native_exec_command("webpack", ["serve"])
101
+ bundler = get_bundler_type
102
+ command = bundler == "rspack" ? "rspack" : "webpack"
103
+ package_json.manager.native_exec_command(command, ["serve"])
94
104
  end
95
105
  end
96
106
  end
@@ -109,11 +109,11 @@ module Shakapacker::Helper
109
109
  @javascript_pack_tag_loaded = true
110
110
 
111
111
  capture do
112
- concat javascript_include_tag(*async, **options.dup.tap { |o| o[:async] = true })
112
+ render_tags(async, :javascript, **options.dup.tap { |o| o[:async] = true })
113
113
  concat "\n" if async.any? && deferred.any?
114
- concat javascript_include_tag(*deferred, **options.dup.tap { |o| o[:defer] = true })
114
+ render_tags(deferred, :javascript, **options.dup.tap { |o| o[:defer] = true })
115
115
  concat "\n" if sync.any? && deferred.any?
116
- concat javascript_include_tag(*sync, **options)
116
+ render_tags(sync, :javascript, options)
117
117
  end
118
118
  end
119
119
 
@@ -166,7 +166,9 @@ module Shakapacker::Helper
166
166
 
167
167
  @stylesheet_pack_tag_loaded = true
168
168
 
169
- stylesheet_link_tag(*(requested_packs | appended_packs), **options)
169
+ capture do
170
+ render_tags(requested_packs | appended_packs, :stylesheet, options)
171
+ end
170
172
  end
171
173
 
172
174
  def append_stylesheet_pack_tag(*names)
@@ -238,4 +240,38 @@ module Shakapacker::Helper
238
240
  rescue
239
241
  path_to_asset(current_shakapacker_instance.manifest.lookup!(name), options)
240
242
  end
243
+
244
+ def lookup_integrity(source)
245
+ (source.respond_to?(:dig) && source.dig("integrity")) || nil
246
+ end
247
+
248
+ def lookup_source(source)
249
+ (source.respond_to?(:dig) && source.dig("src")) || source
250
+ end
251
+
252
+ # Handles rendering javascript and stylesheet tags with integrity, if that's enabled.
253
+ def render_tags(sources, type, options)
254
+ return unless sources.present? || type.present?
255
+
256
+ sources.each.with_index do |source, index|
257
+ tag_source = lookup_source(source)
258
+
259
+ if current_shakapacker_instance.config.integrity[:enabled]
260
+ integrity = lookup_integrity(source)
261
+
262
+ if integrity.present?
263
+ options[:integrity] = integrity
264
+ options[:crossorigin] = current_shakapacker_instance.config.integrity[:cross_origin]
265
+ end
266
+ end
267
+
268
+ if type == :javascript
269
+ concat javascript_include_tag(tag_source, **options)
270
+ else
271
+ concat stylesheet_link_tag(tag_source, **options)
272
+ end
273
+
274
+ concat "\n" unless index == sources.size - 1
275
+ end
276
+ end
241
277
  end
@@ -67,7 +67,12 @@ class Shakapacker::Manifest
67
67
  end
68
68
 
69
69
  def find(name)
70
- data[name.to_s].presence
70
+ return nil unless data[name.to_s].present?
71
+
72
+ return data[name.to_s] unless data[name.to_s].respond_to?(:dig)
73
+
74
+ # Try to return src, if that fails, (ex. entrypoints object) return the whole object.
75
+ data[name.to_s].dig("src") || data[name.to_s]
71
76
  end
72
77
 
73
78
  def full_pack_name(name, pack_type)
@@ -109,9 +114,10 @@ Shakapacker can't find #{bundle_name} in #{config.manifest_path}. Possible cause
109
114
  2. Your app has code with a non-standard extension (like a `.jsx` file) but the extension is not in the `extensions` config in `config/shakapacker.yml`
110
115
  3. You have set compile: false (see `config/shakapacker.yml`) for this environment
111
116
  (unless you are using the `bin/shakapacker -w` or the `bin/shakapacker-dev-server`, in which case maybe you aren't running the dev server in the background?)
112
- 4. webpack has not yet FINISHED running to reflect updates.
117
+ 4. Your bundler (webpack/rspack) has not yet FINISHED running to reflect updates.
113
118
  5. You have misconfigured Shakapacker's `config/shakapacker.yml` file.
114
- 6. Your webpack configuration is not creating a manifest.
119
+ 6. Your bundler configuration is not creating a manifest with the expected structure.
120
+ 7. There's a mismatch between your bundler choice (webpack vs rspack) and the manifest format.
115
121
 
116
122
  Your manifest contains:
117
123
  #{JSON.pretty_generate(@data)}
@@ -0,0 +1,57 @@
1
+ require "shellwords"
2
+
3
+ require_relative "runner"
4
+
5
+ module Shakapacker
6
+ class RspackRunner < Shakapacker::Runner
7
+ RSPACK_COMMANDS = [
8
+ "help",
9
+ "h",
10
+ "--help",
11
+ "-h",
12
+ "version",
13
+ "v",
14
+ "--version",
15
+ "-v",
16
+ "info",
17
+ "i"
18
+ ].freeze
19
+
20
+ def run
21
+ env = Shakapacker::Compiler.env
22
+ env["SHAKAPACKER_CONFIG"] = @shakapacker_config
23
+ env["NODE_OPTIONS"] = ENV["NODE_OPTIONS"] || ""
24
+
25
+ cmd = build_cmd
26
+
27
+ if @argv.delete("--debug-shakapacker")
28
+ env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --inspect-brk"
29
+ end
30
+
31
+ if @argv.delete "--trace-deprecation"
32
+ env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --trace-deprecation"
33
+ end
34
+
35
+ if @argv.delete "--no-deprecation"
36
+ env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --no-deprecation"
37
+ end
38
+
39
+ # Rspack commands are not compatible with --config option.
40
+ if (@argv & RSPACK_COMMANDS).empty?
41
+ cmd += ["--config", @webpack_config]
42
+ end
43
+
44
+ cmd += @argv
45
+
46
+ Dir.chdir(@app_path) do
47
+ Kernel.exec env, *cmd
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def build_cmd
54
+ package_json.manager.native_exec_command("rspack")
55
+ end
56
+ end
57
+ end
@@ -1,7 +1,9 @@
1
1
  require_relative "utils/misc"
2
2
  require_relative "utils/manager"
3
+ require_relative "configuration"
3
4
 
4
5
  require "package_json"
6
+ require "pathname"
5
7
 
6
8
  module Shakapacker
7
9
  class Runner
@@ -16,7 +18,12 @@ module Shakapacker
16
18
 
17
19
  @app_path = File.expand_path(".", Dir.pwd)
18
20
  @shakapacker_config = ENV["SHAKAPACKER_CONFIG"] || File.join(@app_path, "config/shakapacker.yml")
19
- @webpack_config = find_webpack_config
21
+ @config = Configuration.new(
22
+ root_path: Pathname.new(@app_path),
23
+ config_path: Pathname.new(@shakapacker_config),
24
+ env: ENV["RAILS_ENV"] || ENV["NODE_ENV"] || "development"
25
+ )
26
+ @webpack_config = find_bundler_config
20
27
 
21
28
  Shakapacker::Utils::Manager.error_unless_package_manager_is_obvious!
22
29
  end
@@ -26,6 +33,45 @@ module Shakapacker
26
33
  end
27
34
 
28
35
  private
36
+ def find_bundler_config
37
+ if @config.rspack?
38
+ find_rspack_config_with_fallback
39
+ else
40
+ find_webpack_config
41
+ end
42
+ end
43
+
44
+ def get_bundler_type
45
+ @config.bundler
46
+ end
47
+
48
+ def find_rspack_config_with_fallback
49
+ # First try rspack-specific paths
50
+ rspack_paths = %w[ts js].map do |ext|
51
+ File.join(@app_path, "config/rspack/rspack.config.#{ext}")
52
+ end
53
+
54
+ rspack_path = rspack_paths.find { |f| File.exist?(f) }
55
+ return rspack_path if rspack_path
56
+
57
+ # Fallback to webpack config with deprecation warning
58
+ webpack_paths = %w[ts js].map do |ext|
59
+ File.join(@app_path, "config/webpack/webpack.config.#{ext}")
60
+ end
61
+
62
+ webpack_path = webpack_paths.find { |f| File.exist?(f) }
63
+ if webpack_path
64
+ $stderr.puts "⚠️ DEPRECATION WARNING: Using webpack config file for Rspack bundler."
65
+ $stderr.puts " Please create config/rspack/rspack.config.js and migrate your configuration."
66
+ $stderr.puts " Using: #{webpack_path}"
67
+ return webpack_path
68
+ end
69
+
70
+ # No config found
71
+ $stderr.puts "rspack config #{rspack_paths.last} not found, please run 'bundle exec rails shakapacker:install' to install Shakapacker with default configs or create the missing config file."
72
+ exit(1)
73
+ end
74
+
29
75
  def find_webpack_config
30
76
  possible_paths = %w[ts js].map do |ext|
31
77
  File.join(@app_path, "config/webpack/webpack.config.#{ext}")
@@ -33,7 +79,7 @@ module Shakapacker
33
79
  path = possible_paths.find { |f| File.exist?(f) }
34
80
  unless path
35
81
  $stderr.puts "webpack config #{possible_paths.last} not found, please run 'bundle exec rails shakapacker:install' to install Shakapacker with default configs or add the missing config file for your custom environment."
36
- exit!
82
+ exit(1)
37
83
  end
38
84
  path
39
85
  end
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "8.3.0".freeze
3
+ VERSION = "9.0.0.beta.0".freeze
4
4
  end
data/package/config.js CHANGED
@@ -50,5 +50,7 @@ if (config.manifest_path) {
50
50
  } else {
51
51
  config.manifestPath = resolve(config.outputPath, "manifest.json")
52
52
  }
53
+ // Ensure no duplicate hash functions exist in the returned config object
54
+ config.integrity.hash_functions = [...new Set(config.integrity.hash_functions)]
53
55
 
54
56
  module.exports = config
@@ -1,16 +1,20 @@
1
1
  /* eslint global-require: 0 */
2
2
  /* eslint import/no-dynamic-require: 0 */
3
3
 
4
- const { existsSync, readdirSync } = require("fs")
5
4
  const { basename, dirname, join, relative, resolve } = require("path")
5
+ const { existsSync, readdirSync } = require("fs")
6
6
  const extname = require("path-complete-extname")
7
- // TODO: Change to `const { WebpackAssetsManifest }` when dropping 'webpack-assets-manifest < 6.0.0' (Node >=20.10.0) support
8
- const WebpackAssetsManifest = require("webpack-assets-manifest")
9
- const webpack = require("webpack")
10
- const rules = require("../rules")
11
7
  const config = require("../config")
12
8
  const { isProduction } = require("../env")
13
- const { moduleExists } = require("../utils/helpers")
9
+
10
+ const pluginsPath = resolve(__dirname, "..", "plugins", `${config.bundler}.js`)
11
+ const { getPlugins } = require(pluginsPath)
12
+ const rulesPath = resolve(__dirname, "..", "rules", `${config.bundler}.js`)
13
+ const rules = require(rulesPath)
14
+
15
+ // Don't use contentHash except for production for performance
16
+ // https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
17
+ const hash = isProduction || config.useContentHash ? "-[contenthash]" : ""
14
18
 
15
19
  const getFilesInDirectory = (dir, includeNested) => {
16
20
  if (!existsSync(dir)) {
@@ -73,45 +77,6 @@ const getModulePaths = () => {
73
77
  return result
74
78
  }
75
79
 
76
- // TODO: Remove WebpackAssetsManifestConstructor workaround when dropping 'webpack-assets-manifest < 6.0.0' (Node >=20.10.0) support
77
- const WebpackAssetsManifestConstructor =
78
- "WebpackAssetsManifest" in WebpackAssetsManifest
79
- ? WebpackAssetsManifest.WebpackAssetsManifest
80
- : WebpackAssetsManifest
81
- const getPlugins = () => {
82
- const plugins = [
83
- new webpack.EnvironmentPlugin(process.env),
84
- new WebpackAssetsManifestConstructor({
85
- entrypoints: true,
86
- writeToDisk: true,
87
- output: config.manifestPath,
88
- entrypointsUseAssets: true,
89
- publicPath: config.publicPathWithoutCDN
90
- })
91
- ]
92
-
93
- if (moduleExists("css-loader") && moduleExists("mini-css-extract-plugin")) {
94
- const hash = isProduction || config.useContentHash ? "-[contenthash:8]" : ""
95
- const MiniCssExtractPlugin = require("mini-css-extract-plugin")
96
- plugins.push(
97
- new MiniCssExtractPlugin({
98
- filename: `css/[name]${hash}.css`,
99
- chunkFilename: `css/[id]${hash}.css`,
100
- // For projects where css ordering has been mitigated through consistent use of scoping or naming conventions,
101
- // the css order warnings can be disabled by setting the ignoreOrder flag.
102
- // Read: https://stackoverflow.com/questions/51971857/mini-css-extract-plugin-warning-in-chunk-chunkname-mini-css-extract-plugin-con
103
- ignoreOrder: config.css_extract_ignore_order_warnings
104
- })
105
- )
106
- }
107
-
108
- return plugins
109
- }
110
-
111
- // Don't use contentHash except for production for performance
112
- // https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
113
- const hash = isProduction || config.useContentHash ? "-[contenthash]" : ""
114
-
115
80
  module.exports = {
116
81
  mode: "production",
117
82
  output: {
@@ -121,7 +86,12 @@ module.exports = {
121
86
  // https://webpack.js.org/configuration/output/#outputhotupdatechunkfilename
122
87
  hotUpdateChunkFilename: "js/[id].[fullhash].hot-update.js",
123
88
  path: config.outputPath,
124
- publicPath: config.publicPath
89
+ publicPath: config.publicPath,
90
+
91
+ // This is required for SRI to work.
92
+ crossOriginLoading: config.integrity.enabled
93
+ ? config.integrity.cross_origin
94
+ : false
125
95
  },
126
96
  entry: getEntryObject(),
127
97
  resolve: {
@@ -137,12 +107,10 @@ module.exports = {
137
107
 
138
108
  optimization: {
139
109
  splitChunks: { chunks: "all" },
140
-
141
110
  runtimeChunk: "single"
142
111
  },
143
112
 
144
113
  module: {
145
- strictExportPresence: true,
146
114
  rules
147
115
  }
148
116
  }