shakapacker 8.4.0 → 9.0.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.
- checksums.yaml +4 -4
- data/.eslintignore +1 -0
- data/.eslintrc.fast.js +40 -0
- data/.eslintrc.js +48 -0
- data/.github/STATUS.md +1 -0
- data/.github/workflows/claude-code-review.yml +54 -0
- data/.github/workflows/claude.yml +50 -0
- data/.github/workflows/dummy.yml +8 -4
- data/.github/workflows/generator.yml +17 -14
- data/.github/workflows/node.yml +23 -1
- data/.github/workflows/ruby.yml +11 -0
- data/.github/workflows/test-bundlers.yml +170 -0
- data/.gitignore +17 -0
- data/.husky/pre-commit +2 -0
- data/.npmignore +56 -0
- data/.prettierignore +3 -0
- data/.rubocop.yml +1 -0
- data/.yalcignore +26 -0
- data/CHANGELOG.md +156 -18
- data/CLAUDE.md +29 -0
- data/CONTRIBUTING.md +138 -20
- data/Gemfile.lock +3 -3
- data/README.md +130 -5
- data/Rakefile +39 -4
- data/TODO.md +50 -0
- data/TODO_v9.md +87 -0
- data/conductor-setup.sh +70 -0
- data/conductor.json +7 -0
- data/docs/cdn_setup.md +379 -0
- data/docs/css-modules-export-mode.md +512 -0
- data/docs/deployment.md +10 -1
- data/docs/optional-peer-dependencies.md +198 -0
- data/docs/peer-dependencies.md +60 -0
- data/docs/rspack.md +190 -0
- data/docs/rspack_migration_guide.md +202 -0
- data/docs/transpiler-migration.md +188 -0
- data/docs/transpiler-performance.md +179 -0
- data/docs/troubleshooting.md +5 -0
- data/docs/typescript-migration.md +378 -0
- data/docs/typescript.md +99 -0
- data/docs/using_esbuild_loader.md +3 -3
- data/docs/using_swc_loader.md +5 -3
- data/docs/v6_upgrade.md +10 -0
- data/docs/v9_upgrade.md +413 -0
- data/lib/install/bin/shakapacker +3 -5
- data/lib/install/config/rspack/rspack.config.js +6 -0
- data/lib/install/config/rspack/rspack.config.ts +7 -0
- data/lib/install/config/shakapacker.yml +12 -2
- data/lib/install/config/webpack/webpack.config.ts +7 -0
- data/lib/install/package.json +38 -0
- data/lib/install/template.rb +194 -44
- data/lib/shakapacker/configuration.rb +141 -0
- data/lib/shakapacker/dev_server_runner.rb +25 -5
- data/lib/shakapacker/doctor.rb +844 -0
- data/lib/shakapacker/manifest.rb +4 -2
- data/lib/shakapacker/rspack_runner.rb +19 -0
- data/lib/shakapacker/runner.rb +144 -4
- data/lib/shakapacker/swc_migrator.rb +376 -0
- data/lib/shakapacker/utils/manager.rb +2 -0
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/version_checker.rb +1 -1
- data/lib/shakapacker/webpack_runner.rb +4 -42
- data/lib/shakapacker.rb +2 -1
- data/lib/tasks/shakapacker/doctor.rake +8 -0
- data/lib/tasks/shakapacker/install.rake +12 -2
- data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
- data/lib/tasks/shakapacker.rake +1 -0
- data/package/.npmignore +4 -0
- data/package/babel/preset.ts +56 -0
- data/package/config.ts +175 -0
- data/package/{dev_server.js → dev_server.ts} +8 -5
- data/package/env.ts +92 -0
- data/package/environments/base.ts +138 -0
- data/package/environments/development.ts +90 -0
- data/package/environments/production.ts +80 -0
- data/package/environments/test.ts +53 -0
- data/package/environments/types.ts +90 -0
- data/package/esbuild/index.ts +42 -0
- data/package/index.d.ts +3 -97
- data/package/index.ts +52 -0
- data/package/loaders.d.ts +28 -0
- data/package/optimization/rspack.ts +36 -0
- data/package/optimization/webpack.ts +57 -0
- data/package/plugins/rspack.ts +103 -0
- data/package/plugins/webpack.ts +62 -0
- data/package/rspack/index.ts +64 -0
- data/package/rules/{babel.js → babel.ts} +2 -2
- data/package/rules/{coffee.js → coffee.ts} +1 -1
- data/package/rules/css.ts +3 -0
- data/package/rules/{erb.js → erb.ts} +1 -1
- data/package/rules/esbuild.ts +10 -0
- data/package/rules/file.ts +40 -0
- data/package/rules/{jscommon.js → jscommon.ts} +4 -4
- data/package/rules/{less.js → less.ts} +4 -4
- data/package/rules/raw.ts +25 -0
- data/package/rules/rspack.ts +176 -0
- data/package/rules/{sass.js → sass.ts} +7 -3
- data/package/rules/{stylus.js → stylus.ts} +4 -8
- data/package/rules/swc.ts +10 -0
- data/package/rules/{index.js → webpack.ts} +1 -1
- data/package/swc/index.ts +54 -0
- data/package/types/README.md +87 -0
- data/package/types/index.ts +60 -0
- data/package/types.ts +108 -0
- data/package/utils/configPath.ts +6 -0
- data/package/utils/debug.ts +49 -0
- data/package/utils/defaultConfigPath.ts +4 -0
- data/package/utils/errorCodes.ts +219 -0
- data/package/utils/errorHelpers.ts +143 -0
- data/package/utils/getStyleRule.ts +64 -0
- data/package/utils/helpers.ts +85 -0
- data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
- data/package/utils/pathValidation.ts +139 -0
- data/package/utils/requireOrError.ts +15 -0
- data/package/utils/snakeToCamelCase.ts +5 -0
- data/package/utils/typeGuards.ts +342 -0
- data/package/utils/validateDependencies.ts +61 -0
- data/package/webpack-types.d.ts +33 -0
- data/package/webpackDevServerConfig.ts +117 -0
- data/package.json +134 -9
- data/scripts/remove-use-strict.js +45 -0
- data/scripts/type-check-no-emit.js +27 -0
- data/test/package/config.test.js +3 -0
- data/test/package/env.test.js +42 -7
- data/test/package/environments/base.test.js +5 -1
- data/test/package/rules/babel.test.js +16 -0
- data/test/package/rules/esbuild.test.js +1 -1
- data/test/package/rules/raw.test.js +40 -7
- data/test/package/rules/swc.test.js +1 -1
- data/test/package/rules/webpack.test.js +35 -0
- data/test/package/staging.test.js +4 -3
- data/test/package/transpiler-defaults.test.js +127 -0
- data/test/peer-dependencies.sh +85 -0
- data/test/scripts/remove-use-strict.test.js +125 -0
- data/test/typescript/build.test.js +118 -0
- data/test/typescript/environments.test.js +107 -0
- data/test/typescript/pathValidation.test.js +142 -0
- data/test/typescript/securityValidation.test.js +182 -0
- data/tools/README.md +124 -0
- data/tools/css-modules-v9-codemod.js +179 -0
- data/tsconfig.eslint.json +16 -0
- data/tsconfig.json +38 -0
- data/yarn.lock +2704 -767
- metadata +111 -41
- data/package/babel/preset.js +0 -48
- data/package/config.js +0 -56
- data/package/env.js +0 -48
- data/package/environments/base.js +0 -171
- data/package/environments/development.js +0 -13
- data/package/environments/production.js +0 -88
- data/package/environments/test.js +0 -3
- data/package/esbuild/index.js +0 -40
- data/package/index.js +0 -40
- data/package/rules/css.js +0 -3
- data/package/rules/esbuild.js +0 -10
- data/package/rules/file.js +0 -29
- data/package/rules/raw.js +0 -5
- data/package/rules/swc.js +0 -10
- data/package/swc/index.js +0 -50
- data/package/utils/configPath.js +0 -4
- data/package/utils/defaultConfigPath.js +0 -2
- data/package/utils/getStyleRule.js +0 -40
- data/package/utils/helpers.js +0 -62
- data/package/utils/snakeToCamelCase.js +0 -5
- data/package/webpackDevServerConfig.js +0 -71
- data/test/package/rules/index.test.js +0 -16
data/lib/shakapacker/manifest.rb
CHANGED
|
@@ -108,15 +108,17 @@ class Shakapacker::Manifest
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def missing_file_from_manifest_error(bundle_name)
|
|
111
|
+
bundler_name = config.assets_bundler
|
|
111
112
|
<<-MSG
|
|
112
113
|
Shakapacker can't find #{bundle_name} in #{config.manifest_path}. Possible causes:
|
|
113
114
|
1. You forgot to install javascript packages or are running an incompatible javascript runtime version
|
|
114
115
|
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`
|
|
115
116
|
3. You have set compile: false (see `config/shakapacker.yml`) for this environment
|
|
116
117
|
(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?)
|
|
117
|
-
4.
|
|
118
|
+
4. Your #{bundler_name} has not yet FINISHED running to reflect updates.
|
|
118
119
|
5. You have misconfigured Shakapacker's `config/shakapacker.yml` file.
|
|
119
|
-
6. Your
|
|
120
|
+
6. Your #{bundler_name} configuration is not creating a manifest with the expected structure.
|
|
121
|
+
7. Ensure the 'assets_bundler' in config/shakapacker.yml is set correctly (currently: #{bundler_name}).
|
|
120
122
|
|
|
121
123
|
Your manifest contains:
|
|
122
124
|
#{JSON.pretty_generate(@data)}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "shellwords"
|
|
2
|
+
|
|
3
|
+
require_relative "runner"
|
|
4
|
+
|
|
5
|
+
module Shakapacker
|
|
6
|
+
class RspackRunner < Shakapacker::Runner
|
|
7
|
+
def self.run(argv)
|
|
8
|
+
$stdout.sync = true
|
|
9
|
+
ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
|
|
10
|
+
new(argv).run
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def build_cmd
|
|
16
|
+
package_json.manager.native_exec_command("rspack")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/shakapacker/runner.rb
CHANGED
|
@@ -1,14 +1,60 @@
|
|
|
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
|
|
10
|
+
attr_reader :config
|
|
11
|
+
|
|
12
|
+
# Common commands that don't work with --config option
|
|
13
|
+
BASE_COMMANDS = [
|
|
14
|
+
"help",
|
|
15
|
+
"h",
|
|
16
|
+
"--help",
|
|
17
|
+
"-h",
|
|
18
|
+
"version",
|
|
19
|
+
"v",
|
|
20
|
+
"--version",
|
|
21
|
+
"-v",
|
|
22
|
+
"info",
|
|
23
|
+
"i"
|
|
24
|
+
].freeze
|
|
8
25
|
def self.run(argv)
|
|
9
26
|
$stdout.sync = true
|
|
10
27
|
ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
|
|
11
|
-
|
|
28
|
+
|
|
29
|
+
# Create a single runner instance to avoid loading configuration twice.
|
|
30
|
+
# We extend it with the appropriate build command based on the bundler type.
|
|
31
|
+
runner = new(argv)
|
|
32
|
+
|
|
33
|
+
if runner.config.rspack?
|
|
34
|
+
require_relative "rspack_runner"
|
|
35
|
+
# Extend the runner instance with rspack-specific methods
|
|
36
|
+
# This avoids creating a new RspackRunner which would reload the configuration
|
|
37
|
+
runner.extend(Module.new do
|
|
38
|
+
def build_cmd
|
|
39
|
+
package_json.manager.native_exec_command("rspack")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def assets_bundler_commands
|
|
43
|
+
BASE_COMMANDS + %w[build watch]
|
|
44
|
+
end
|
|
45
|
+
end)
|
|
46
|
+
runner.run
|
|
47
|
+
else
|
|
48
|
+
require_relative "webpack_runner"
|
|
49
|
+
# Extend the runner instance with webpack-specific methods
|
|
50
|
+
# This avoids creating a new WebpackRunner which would reload the configuration
|
|
51
|
+
runner.extend(Module.new do
|
|
52
|
+
def build_cmd
|
|
53
|
+
package_json.manager.native_exec_command("webpack")
|
|
54
|
+
end
|
|
55
|
+
end)
|
|
56
|
+
runner.run
|
|
57
|
+
end
|
|
12
58
|
end
|
|
13
59
|
|
|
14
60
|
def initialize(argv)
|
|
@@ -16,7 +62,12 @@ module Shakapacker
|
|
|
16
62
|
|
|
17
63
|
@app_path = File.expand_path(".", Dir.pwd)
|
|
18
64
|
@shakapacker_config = ENV["SHAKAPACKER_CONFIG"] || File.join(@app_path, "config/shakapacker.yml")
|
|
19
|
-
@
|
|
65
|
+
@config = Configuration.new(
|
|
66
|
+
root_path: Pathname.new(@app_path),
|
|
67
|
+
config_path: Pathname.new(@shakapacker_config),
|
|
68
|
+
env: ENV["RAILS_ENV"] || ENV["NODE_ENV"] || "development"
|
|
69
|
+
)
|
|
70
|
+
@webpack_config = find_assets_bundler_config
|
|
20
71
|
|
|
21
72
|
Shakapacker::Utils::Manager.error_unless_package_manager_is_obvious!
|
|
22
73
|
end
|
|
@@ -25,16 +76,105 @@ module Shakapacker
|
|
|
25
76
|
@package_json ||= PackageJson.read(@app_path)
|
|
26
77
|
end
|
|
27
78
|
|
|
79
|
+
def run
|
|
80
|
+
puts "[Shakapacker] Preparing environment for assets bundler execution..."
|
|
81
|
+
env = Shakapacker::Compiler.env
|
|
82
|
+
env["SHAKAPACKER_CONFIG"] = @shakapacker_config
|
|
83
|
+
env["NODE_OPTIONS"] = ENV["NODE_OPTIONS"] || ""
|
|
84
|
+
|
|
85
|
+
cmd = build_cmd
|
|
86
|
+
puts "[Shakapacker] Base command: #{cmd.join(" ")}"
|
|
87
|
+
|
|
88
|
+
if @argv.delete("--debug-shakapacker")
|
|
89
|
+
puts "[Shakapacker] Debug mode enabled (--debug-shakapacker)"
|
|
90
|
+
env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --inspect-brk"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
if @argv.delete "--trace-deprecation"
|
|
94
|
+
puts "[Shakapacker] Trace deprecation enabled (--trace-deprecation)"
|
|
95
|
+
env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --trace-deprecation"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if @argv.delete "--no-deprecation"
|
|
99
|
+
puts "[Shakapacker] Deprecation warnings disabled (--no-deprecation)"
|
|
100
|
+
env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --no-deprecation"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Commands are not compatible with --config option.
|
|
104
|
+
if (@argv & assets_bundler_commands).empty?
|
|
105
|
+
puts "[Shakapacker] Adding config file: #{@webpack_config}"
|
|
106
|
+
cmd += ["--config", @webpack_config]
|
|
107
|
+
else
|
|
108
|
+
puts "[Shakapacker] Skipping config file (running assets bundler command: #{(@argv & assets_bundler_commands).join(", ")})"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
cmd += @argv
|
|
112
|
+
puts "[Shakapacker] Final command: #{cmd.join(" ")}"
|
|
113
|
+
puts "[Shakapacker] Working directory: #{@app_path}"
|
|
114
|
+
|
|
115
|
+
Dir.chdir(@app_path) do
|
|
116
|
+
Kernel.exec env, *cmd
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
protected
|
|
121
|
+
|
|
122
|
+
def assets_bundler_commands
|
|
123
|
+
BASE_COMMANDS
|
|
124
|
+
end
|
|
125
|
+
|
|
28
126
|
private
|
|
127
|
+
def find_assets_bundler_config
|
|
128
|
+
if @config.rspack?
|
|
129
|
+
find_rspack_config_with_fallback
|
|
130
|
+
else
|
|
131
|
+
find_webpack_config
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def find_rspack_config_with_fallback
|
|
136
|
+
# First try rspack-specific paths
|
|
137
|
+
rspack_paths = %w[ts js].map do |ext|
|
|
138
|
+
File.join(@app_path, "config/rspack/rspack.config.#{ext}")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
puts "[Shakapacker] Looking for Rspack config in: #{rspack_paths.join(", ")}"
|
|
142
|
+
rspack_path = rspack_paths.find { |f| File.exist?(f) }
|
|
143
|
+
if rspack_path
|
|
144
|
+
puts "[Shakapacker] Found Rspack config: #{rspack_path}"
|
|
145
|
+
return rspack_path
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Fallback to webpack config with deprecation warning
|
|
149
|
+
webpack_paths = %w[ts js].map do |ext|
|
|
150
|
+
File.join(@app_path, "config/webpack/webpack.config.#{ext}")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
puts "[Shakapacker] Rspack config not found, checking for webpack config fallback..."
|
|
154
|
+
webpack_path = webpack_paths.find { |f| File.exist?(f) }
|
|
155
|
+
if webpack_path
|
|
156
|
+
$stderr.puts "⚠️ DEPRECATION WARNING: Using webpack config file for Rspack assets bundler."
|
|
157
|
+
$stderr.puts " Please create config/rspack/rspack.config.js and migrate your configuration."
|
|
158
|
+
$stderr.puts " Using: #{webpack_path}"
|
|
159
|
+
return webpack_path
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# No config found
|
|
163
|
+
$stderr.puts "[Shakapacker] ERROR: 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."
|
|
164
|
+
exit(1)
|
|
165
|
+
end
|
|
166
|
+
|
|
29
167
|
def find_webpack_config
|
|
30
168
|
possible_paths = %w[ts js].map do |ext|
|
|
31
169
|
File.join(@app_path, "config/webpack/webpack.config.#{ext}")
|
|
32
170
|
end
|
|
171
|
+
puts "[Shakapacker] Looking for Webpack config in: #{possible_paths.join(", ")}"
|
|
33
172
|
path = possible_paths.find { |f| File.exist?(f) }
|
|
34
173
|
unless path
|
|
35
|
-
$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
|
|
174
|
+
$stderr.puts "[Shakapacker] ERROR: 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."
|
|
175
|
+
exit(1)
|
|
37
176
|
end
|
|
177
|
+
puts "[Shakapacker] Found Webpack config: #{path}"
|
|
38
178
|
path
|
|
39
179
|
end
|
|
40
180
|
end
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
require "json"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "logger"
|
|
5
|
+
require "pathname"
|
|
6
|
+
|
|
7
|
+
module Shakapacker
|
|
8
|
+
class SwcMigrator
|
|
9
|
+
attr_reader :root_path, :logger
|
|
10
|
+
|
|
11
|
+
# Babel packages safe to remove when migrating to SWC
|
|
12
|
+
# Note: @babel/core and @babel/eslint-parser are excluded as they may be needed for ESLint
|
|
13
|
+
BABEL_PACKAGES = [
|
|
14
|
+
"@babel/plugin-proposal-class-properties",
|
|
15
|
+
"@babel/plugin-proposal-object-rest-spread",
|
|
16
|
+
"@babel/plugin-syntax-dynamic-import",
|
|
17
|
+
"@babel/plugin-transform-destructuring",
|
|
18
|
+
"@babel/plugin-transform-regenerator",
|
|
19
|
+
"@babel/plugin-transform-runtime",
|
|
20
|
+
"@babel/preset-env",
|
|
21
|
+
"@babel/preset-react",
|
|
22
|
+
"@babel/preset-typescript",
|
|
23
|
+
"@babel/runtime",
|
|
24
|
+
"babel-loader",
|
|
25
|
+
"babel-plugin-macros",
|
|
26
|
+
"babel-plugin-transform-react-remove-prop-types"
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
# Babel packages that may be needed for ESLint - only remove if user explicitly confirms
|
|
30
|
+
ESLINT_BABEL_PACKAGES = [
|
|
31
|
+
"@babel/core",
|
|
32
|
+
"@babel/eslint-parser"
|
|
33
|
+
].freeze
|
|
34
|
+
|
|
35
|
+
SWC_PACKAGES = {
|
|
36
|
+
"@swc/core" => "^1.7.39",
|
|
37
|
+
"swc-loader" => "^0.2.6"
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
ESLINT_CONFIG_FILES = %w[
|
|
41
|
+
.eslintrc
|
|
42
|
+
.eslintrc.js
|
|
43
|
+
.eslintrc.cjs
|
|
44
|
+
.eslintrc.yaml
|
|
45
|
+
.eslintrc.yml
|
|
46
|
+
.eslintrc.json
|
|
47
|
+
].freeze
|
|
48
|
+
|
|
49
|
+
DEFAULT_SWC_CONFIG = <<~JS.freeze
|
|
50
|
+
// config/swc.config.js
|
|
51
|
+
// This file is merged with Shakapacker's default SWC configuration
|
|
52
|
+
// See: https://swc.rs/docs/configuration/compilation
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
jsc: {
|
|
56
|
+
transform: {
|
|
57
|
+
react: {
|
|
58
|
+
runtime: "automatic"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
JS
|
|
64
|
+
|
|
65
|
+
def initialize(root_path, logger: nil)
|
|
66
|
+
@root_path = Pathname.new(root_path)
|
|
67
|
+
@logger = logger || Logger.new($stdout)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def migrate_to_swc(run_installer: true)
|
|
71
|
+
logger.info "🔄 Starting migration from Babel to SWC..."
|
|
72
|
+
|
|
73
|
+
results = {
|
|
74
|
+
config_updated: update_shakapacker_config,
|
|
75
|
+
packages_installed: install_swc_packages,
|
|
76
|
+
swc_config_created: create_swc_config,
|
|
77
|
+
babel_packages_found: find_babel_packages
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
logger.info "🎉 Migration to SWC complete!"
|
|
81
|
+
logger.info " Note: SWC is approximately 20x faster than Babel for transpilation."
|
|
82
|
+
logger.info " Please test your application thoroughly after migration."
|
|
83
|
+
logger.info "\n📝 Configuration Info:"
|
|
84
|
+
logger.info " - config/swc.config.js is merged with Shakapacker's default SWC configuration"
|
|
85
|
+
logger.info " - You can customize config/swc.config.js to add additional options"
|
|
86
|
+
logger.info " - Avoid using .swcrc as it overrides Shakapacker defaults completely"
|
|
87
|
+
|
|
88
|
+
# Show cleanup recommendations if babel packages found
|
|
89
|
+
if results[:babel_packages_found].any?
|
|
90
|
+
logger.info "\n🧹 Cleanup Recommendations:"
|
|
91
|
+
logger.info " Found the following Babel packages in your package.json:"
|
|
92
|
+
results[:babel_packages_found].each do |package|
|
|
93
|
+
logger.info " - #{package}"
|
|
94
|
+
end
|
|
95
|
+
logger.info "\n To remove them, run:"
|
|
96
|
+
logger.info " rails shakapacker:clean_babel_packages"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Suggest running doctor to verify configuration
|
|
100
|
+
logger.info "\n🩺 Run 'rails shakapacker:doctor' to verify your configuration"
|
|
101
|
+
|
|
102
|
+
# Run package manager install if packages were added
|
|
103
|
+
if run_installer && results[:packages_installed].any?
|
|
104
|
+
run_package_manager_install
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
results
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def clean_babel_packages(run_installer: true)
|
|
111
|
+
logger.info "🧹 Removing Babel packages..."
|
|
112
|
+
|
|
113
|
+
package_json_path = root_path.join("package.json")
|
|
114
|
+
unless package_json_path.exist?
|
|
115
|
+
logger.error "❌ No package.json found"
|
|
116
|
+
return { removed_packages: [], config_files_deleted: [], preserved_packages: [] }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Check if ESLint uses Babel parser
|
|
120
|
+
preserved_for_eslint = []
|
|
121
|
+
if eslint_uses_babel?
|
|
122
|
+
logger.info "\n⚠️ ESLint configuration detected that uses Babel parser"
|
|
123
|
+
logger.info " Preserving @babel/core and @babel/eslint-parser for ESLint compatibility"
|
|
124
|
+
logger.info " To switch ESLint parser:"
|
|
125
|
+
logger.info " 1. For TypeScript: use @typescript-eslint/parser"
|
|
126
|
+
logger.info " 2. For JavaScript: use espree (ESLint's default parser)"
|
|
127
|
+
preserved_for_eslint = ESLINT_BABEL_PACKAGES
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
removed_packages = remove_babel_from_package_json(package_json_path, preserve: preserved_for_eslint)
|
|
131
|
+
deleted_files = delete_babel_config_files
|
|
132
|
+
|
|
133
|
+
if removed_packages.any?
|
|
134
|
+
logger.info "✅ Babel packages removed successfully!"
|
|
135
|
+
run_package_manager_install if run_installer
|
|
136
|
+
else
|
|
137
|
+
logger.info "ℹ️ No Babel packages found to remove"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
{ removed_packages: removed_packages, config_files_deleted: deleted_files, preserved_packages: preserved_for_eslint }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def find_babel_packages
|
|
144
|
+
package_json_path = root_path.join("package.json")
|
|
145
|
+
return [] unless package_json_path.exist?
|
|
146
|
+
|
|
147
|
+
begin
|
|
148
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
149
|
+
dependencies = package_json["dependencies"] || {}
|
|
150
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
151
|
+
all_deps = dependencies.merge(dev_dependencies)
|
|
152
|
+
|
|
153
|
+
# Find all babel packages (including ESLint-related ones for display)
|
|
154
|
+
all_babel_packages = BABEL_PACKAGES + ESLINT_BABEL_PACKAGES
|
|
155
|
+
found_packages = all_babel_packages.select { |pkg| all_deps.key?(pkg) }
|
|
156
|
+
found_packages
|
|
157
|
+
rescue JSON::ParserError => e
|
|
158
|
+
logger.error "Failed to parse package.json: #{e.message}"
|
|
159
|
+
[]
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def eslint_uses_babel?
|
|
166
|
+
# Check for ESLint config files
|
|
167
|
+
# Note: This is a heuristic check that may have false positives (e.g., in comments),
|
|
168
|
+
# but false positives only result in an extra warning, which is safer than silently
|
|
169
|
+
# breaking ESLint configurations.
|
|
170
|
+
ESLINT_CONFIG_FILES.each do |config_file|
|
|
171
|
+
config_path = root_path.join(config_file)
|
|
172
|
+
next unless config_path.exist?
|
|
173
|
+
|
|
174
|
+
content = File.read(config_path)
|
|
175
|
+
# Check for Babel parser references
|
|
176
|
+
return true if content.match?(/@babel\/eslint-parser|babel-eslint/)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Check package.json for eslintConfig
|
|
180
|
+
package_json_path = root_path.join("package.json")
|
|
181
|
+
if package_json_path.exist?
|
|
182
|
+
begin
|
|
183
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
184
|
+
if package_json["eslintConfig"]
|
|
185
|
+
# Check parser field explicitly
|
|
186
|
+
parser = package_json["eslintConfig"]["parser"]
|
|
187
|
+
return true if parser && parser.match?(/@babel\/eslint-parser|babel-eslint/)
|
|
188
|
+
|
|
189
|
+
# Also check entire config for babel parser references (catches nested configs)
|
|
190
|
+
return true if package_json["eslintConfig"].to_json.match?(/@babel\/eslint-parser|babel-eslint/)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Check if Babel ESLint packages are installed
|
|
194
|
+
dependencies = package_json["dependencies"] || {}
|
|
195
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
196
|
+
all_deps = dependencies.merge(dev_dependencies)
|
|
197
|
+
return true if all_deps.key?("@babel/eslint-parser") || all_deps.key?("babel-eslint")
|
|
198
|
+
rescue JSON::ParserError => e
|
|
199
|
+
logger.debug "Could not parse package.json for ESLint detection: #{e.message}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
false
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def update_shakapacker_config
|
|
207
|
+
config_path = root_path.join("config/shakapacker.yml")
|
|
208
|
+
return false unless config_path.exist?
|
|
209
|
+
|
|
210
|
+
logger.info "📝 Updating shakapacker.yml..."
|
|
211
|
+
config = begin
|
|
212
|
+
YAML.load_file(config_path, aliases: true)
|
|
213
|
+
rescue ArgumentError
|
|
214
|
+
YAML.load_file(config_path)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
config.each do |env, settings|
|
|
218
|
+
next unless settings.is_a?(Hash)
|
|
219
|
+
|
|
220
|
+
if settings["babel"]
|
|
221
|
+
logger.info " - Removing babel config from #{env} environment"
|
|
222
|
+
settings.delete("babel")
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
settings["swc"] = true
|
|
226
|
+
logger.info " - Enabled SWC for #{env} environment"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
File.write(config_path, config.to_yaml)
|
|
230
|
+
logger.info "✅ shakapacker.yml updated"
|
|
231
|
+
true
|
|
232
|
+
rescue StandardError => e
|
|
233
|
+
logger.error "Failed to update config: #{e.message}"
|
|
234
|
+
false
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def install_swc_packages
|
|
238
|
+
package_json_path = root_path.join("package.json")
|
|
239
|
+
return {} unless package_json_path.exist?
|
|
240
|
+
|
|
241
|
+
logger.info "📦 Installing SWC dependencies..."
|
|
242
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
243
|
+
|
|
244
|
+
dependencies = package_json["dependencies"] || {}
|
|
245
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
246
|
+
installed = {}
|
|
247
|
+
|
|
248
|
+
SWC_PACKAGES.each do |package, version|
|
|
249
|
+
unless dependencies[package] || dev_dependencies[package]
|
|
250
|
+
logger.info " - Adding #{package}@#{version}"
|
|
251
|
+
dev_dependencies[package] = version
|
|
252
|
+
installed[package] = version
|
|
253
|
+
else
|
|
254
|
+
logger.info " - #{package} already installed"
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
if installed.any?
|
|
259
|
+
package_json["devDependencies"] = dev_dependencies
|
|
260
|
+
File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
|
|
261
|
+
logger.info "✅ package.json updated with SWC dependencies"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
installed
|
|
265
|
+
rescue StandardError => e
|
|
266
|
+
logger.error "Failed to install packages: #{e.message}"
|
|
267
|
+
{}
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def create_swc_config
|
|
271
|
+
config_dir = root_path.join("config")
|
|
272
|
+
swc_config_path = config_dir.join("swc.config.js")
|
|
273
|
+
|
|
274
|
+
if swc_config_path.exist?
|
|
275
|
+
logger.info "ℹ️ config/swc.config.js already exists"
|
|
276
|
+
return false
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
FileUtils.mkdir_p(config_dir) unless config_dir.exist?
|
|
280
|
+
|
|
281
|
+
logger.info "📄 Creating config/swc.config.js..."
|
|
282
|
+
File.write(swc_config_path, DEFAULT_SWC_CONFIG)
|
|
283
|
+
logger.info "✅ config/swc.config.js created"
|
|
284
|
+
true
|
|
285
|
+
rescue StandardError => e
|
|
286
|
+
logger.error "Failed to create config/swc.config.js: #{e.message}"
|
|
287
|
+
false
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def remove_babel_from_package_json(package_json_path, preserve: [])
|
|
291
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
292
|
+
dependencies = package_json["dependencies"] || {}
|
|
293
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
294
|
+
removed_packages = []
|
|
295
|
+
|
|
296
|
+
BABEL_PACKAGES.each do |package|
|
|
297
|
+
next if preserve.include?(package)
|
|
298
|
+
|
|
299
|
+
if dependencies.delete(package)
|
|
300
|
+
removed_packages << package
|
|
301
|
+
logger.info " - Removed #{package} from dependencies"
|
|
302
|
+
end
|
|
303
|
+
if dev_dependencies.delete(package)
|
|
304
|
+
removed_packages << package
|
|
305
|
+
logger.info " - Removed #{package} from devDependencies"
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Log preserved packages
|
|
310
|
+
preserve.each do |package|
|
|
311
|
+
if dependencies[package] || dev_dependencies[package]
|
|
312
|
+
logger.info " - Preserved #{package} (needed for ESLint)"
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
if removed_packages.any?
|
|
317
|
+
package_json["dependencies"] = dependencies
|
|
318
|
+
package_json["devDependencies"] = dev_dependencies
|
|
319
|
+
File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
|
|
320
|
+
logger.info "✅ package.json updated"
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
removed_packages.uniq
|
|
324
|
+
rescue StandardError => e
|
|
325
|
+
logger.error "Failed to remove packages: #{e.message}"
|
|
326
|
+
[]
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def delete_babel_config_files
|
|
330
|
+
deleted_files = []
|
|
331
|
+
babel_config_files = [".babelrc", "babel.config.js", ".babelrc.js", "babel.config.json"]
|
|
332
|
+
|
|
333
|
+
babel_config_files.each do |file|
|
|
334
|
+
file_path = root_path.join(file)
|
|
335
|
+
if file_path.exist?
|
|
336
|
+
logger.info " - Removing #{file}"
|
|
337
|
+
File.delete(file_path)
|
|
338
|
+
deleted_files << file
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
deleted_files
|
|
343
|
+
rescue StandardError => e
|
|
344
|
+
logger.error "Failed to delete config files: #{e.message}"
|
|
345
|
+
[]
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def run_package_manager_install
|
|
349
|
+
logger.info "\n🔧 Running npm/yarn install..."
|
|
350
|
+
|
|
351
|
+
yarn_lock = root_path.join("yarn.lock")
|
|
352
|
+
pnpm_lock = root_path.join("pnpm-lock.yaml")
|
|
353
|
+
|
|
354
|
+
if yarn_lock.exist?
|
|
355
|
+
system("yarn install")
|
|
356
|
+
elsif pnpm_lock.exist?
|
|
357
|
+
system("pnpm install")
|
|
358
|
+
else
|
|
359
|
+
system("npm install")
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
def package_manager
|
|
364
|
+
yarn_lock = root_path.join("yarn.lock")
|
|
365
|
+
pnpm_lock = root_path.join("pnpm-lock.yaml")
|
|
366
|
+
|
|
367
|
+
if yarn_lock.exist?
|
|
368
|
+
"yarn"
|
|
369
|
+
elsif pnpm_lock.exist?
|
|
370
|
+
"pnpm"
|
|
371
|
+
else
|
|
372
|
+
"npm"
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
data/lib/shakapacker/version.rb
CHANGED
|
@@ -4,48 +4,10 @@ require_relative "runner"
|
|
|
4
4
|
|
|
5
5
|
module Shakapacker
|
|
6
6
|
class WebpackRunner < Shakapacker::Runner
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
10
|
-
|
|
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
|
-
# Webpack commands are not compatible with --config option.
|
|
40
|
-
if (@argv & WEBPACK_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
|
|
7
|
+
def self.run(argv)
|
|
8
|
+
$stdout.sync = true
|
|
9
|
+
ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
|
|
10
|
+
new(argv).run
|
|
49
11
|
end
|
|
50
12
|
|
|
51
13
|
private
|