shakapacker 8.0.2 → 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.
- 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 +9 -4
- data/.github/workflows/generator.yml +32 -10
- data/.github/workflows/node.yml +23 -1
- data/.github/workflows/ruby.yml +33 -2
- data/.github/workflows/test-bundlers.yml +170 -0
- data/.gitignore +20 -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 +302 -16
- data/CLAUDE.md +29 -0
- data/CONTRIBUTING.md +138 -20
- data/Gemfile.lock +83 -89
- data/README.md +343 -105
- data/Rakefile +39 -4
- data/TODO.md +50 -0
- data/TODO_v9.md +87 -0
- data/bin/export-bundler-config +11 -0
- data/conductor-setup.sh +70 -0
- data/conductor.json +7 -0
- data/docs/cdn_setup.md +379 -0
- data/docs/common-upgrades.md +615 -0
- data/docs/css-modules-export-mode.md +512 -0
- data/docs/deployment.md +62 -9
- data/docs/optional-peer-dependencies.md +198 -0
- data/docs/peer-dependencies.md +60 -0
- data/docs/react.md +6 -14
- data/docs/releasing.md +197 -0
- data/docs/rspack.md +190 -0
- data/docs/rspack_migration_guide.md +305 -0
- data/docs/subresource_integrity.md +54 -0
- data/docs/transpiler-migration.md +209 -0
- data/docs/transpiler-performance.md +179 -0
- data/docs/troubleshooting.md +157 -22
- data/docs/typescript-migration.md +379 -0
- data/docs/typescript.md +99 -0
- data/docs/using_esbuild_loader.md +3 -3
- data/docs/using_swc_loader.md +112 -10
- data/docs/v6_upgrade.md +10 -0
- data/docs/v8_upgrade.md +3 -5
- data/docs/v9_upgrade.md +458 -0
- data/gemfiles/Gemfile-rails.6.0.x +2 -1
- data/gemfiles/Gemfile-rails.6.1.x +1 -1
- data/gemfiles/Gemfile-rails.7.0.x +2 -2
- data/gemfiles/Gemfile-rails.7.1.x +1 -2
- data/gemfiles/Gemfile-rails.7.2.x +11 -0
- data/gemfiles/Gemfile-rails.8.0.x +11 -0
- data/lib/install/bin/export-bundler-config +11 -0
- data/lib/install/bin/shakapacker +4 -6
- data/lib/install/bin/shakapacker-dev-server +1 -1
- 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 +25 -5
- 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/bundler_switcher.rb +329 -0
- data/lib/shakapacker/compiler.rb +2 -1
- data/lib/shakapacker/compiler_strategy.rb +2 -2
- data/lib/shakapacker/configuration.rb +173 -2
- data/lib/shakapacker/dev_server_runner.rb +29 -8
- data/lib/shakapacker/digest_strategy.rb +2 -1
- data/lib/shakapacker/doctor.rb +905 -0
- data/lib/shakapacker/helper.rb +64 -16
- data/lib/shakapacker/manifest.rb +10 -3
- data/lib/shakapacker/mtime_strategy.rb +1 -1
- data/lib/shakapacker/railtie.rb +4 -4
- data/lib/shakapacker/rspack_runner.rb +19 -0
- data/lib/shakapacker/runner.rb +159 -10
- data/lib/shakapacker/swc_migrator.rb +384 -0
- data/lib/shakapacker/utils/manager.rb +15 -2
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/version_checker.rb +2 -2
- data/lib/shakapacker/webpack_runner.rb +6 -43
- data/lib/shakapacker.rb +22 -11
- data/lib/tasks/shakapacker/doctor.rake +8 -0
- data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
- data/lib/tasks/shakapacker/install.rake +12 -2
- data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
- data/lib/tasks/shakapacker/switch_bundler.rake +82 -0
- data/lib/tasks/shakapacker.rake +2 -0
- data/package/.npmignore +4 -0
- data/package/babel/preset.ts +56 -0
- data/package/config.ts +175 -0
- data/package/configExporter/cli.ts +683 -0
- data/package/configExporter/configDocs.ts +102 -0
- data/package/configExporter/fileWriter.ts +92 -0
- data/package/configExporter/index.ts +5 -0
- data/package/configExporter/types.ts +36 -0
- data/package/configExporter/yamlSerializer.ts +266 -0
- data/package/{dev_server.js → dev_server.ts} +8 -5
- data/package/env.ts +92 -0
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +30 -0
- data/package/environments/{base.js → base.ts} +56 -60
- 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 +98 -0
- data/package/esbuild/index.ts +42 -0
- data/package/index.d.ts +3 -60
- data/package/index.ts +55 -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/webpack.ts +16 -0
- data/package/swc/index.ts +56 -0
- data/package/types/README.md +88 -0
- data/package/types/index.ts +61 -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-lock.json +13047 -0
- data/package.json +154 -18
- data/scripts/remove-use-strict.js +45 -0
- data/scripts/type-check-no-emit.js +27 -0
- data/test/helpers.js +1 -1
- data/test/package/config.test.js +43 -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 +4165 -2706
- metadata +129 -41
- data/package/babel/preset.js +0 -37
- data/package/config.js +0 -54
- data/package/env.js +0 -48
- 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/index.js +0 -20
- 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 -58
- data/package/utils/snakeToCamelCase.js +0 -5
- data/package/webpackDevServerConfig.js +0 -71
- data/test/package/rules/index.test.js +0 -16
|
@@ -0,0 +1,384 @@
|
|
|
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
|
+
const { env } = require('shakapacker');
|
|
55
|
+
|
|
56
|
+
module.exports = {
|
|
57
|
+
options: {
|
|
58
|
+
jsc: {
|
|
59
|
+
// CRITICAL for Stimulus compatibility: Prevents SWC from mangling class names
|
|
60
|
+
// which breaks Stimulus's class-based controller discovery mechanism
|
|
61
|
+
keepClassNames: true,
|
|
62
|
+
transform: {
|
|
63
|
+
react: {
|
|
64
|
+
runtime: "automatic",
|
|
65
|
+
refresh: env.isDevelopment && env.runningWebpackDevServer
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
JS
|
|
72
|
+
|
|
73
|
+
def initialize(root_path, logger: nil)
|
|
74
|
+
@root_path = Pathname.new(root_path)
|
|
75
|
+
@logger = logger || Logger.new($stdout)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def migrate_to_swc(run_installer: true)
|
|
79
|
+
logger.info "🔄 Starting migration from Babel to SWC..."
|
|
80
|
+
|
|
81
|
+
results = {
|
|
82
|
+
config_updated: update_shakapacker_config,
|
|
83
|
+
packages_installed: install_swc_packages,
|
|
84
|
+
swc_config_created: create_swc_config,
|
|
85
|
+
babel_packages_found: find_babel_packages
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
logger.info "🎉 Migration to SWC complete!"
|
|
89
|
+
logger.info " Note: SWC is approximately 20x faster than Babel for transpilation."
|
|
90
|
+
logger.info " Please test your application thoroughly after migration."
|
|
91
|
+
logger.info "\n📝 Configuration Info:"
|
|
92
|
+
logger.info " - config/swc.config.js is merged with Shakapacker's default SWC configuration"
|
|
93
|
+
logger.info " - You can customize config/swc.config.js to add additional options"
|
|
94
|
+
logger.info " - Avoid using .swcrc as it overrides Shakapacker defaults completely"
|
|
95
|
+
|
|
96
|
+
# Show cleanup recommendations if babel packages found
|
|
97
|
+
if results[:babel_packages_found].any?
|
|
98
|
+
logger.info "\n🧹 Cleanup Recommendations:"
|
|
99
|
+
logger.info " Found the following Babel packages in your package.json:"
|
|
100
|
+
results[:babel_packages_found].each do |package|
|
|
101
|
+
logger.info " - #{package}"
|
|
102
|
+
end
|
|
103
|
+
logger.info "\n To remove them, run:"
|
|
104
|
+
logger.info " rails shakapacker:clean_babel_packages"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Suggest running doctor to verify configuration
|
|
108
|
+
logger.info "\n🩺 Run 'rails shakapacker:doctor' to verify your configuration"
|
|
109
|
+
|
|
110
|
+
# Run package manager install if packages were added
|
|
111
|
+
if run_installer && results[:packages_installed].any?
|
|
112
|
+
run_package_manager_install
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
results
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def clean_babel_packages(run_installer: true)
|
|
119
|
+
logger.info "🧹 Removing Babel packages..."
|
|
120
|
+
|
|
121
|
+
package_json_path = root_path.join("package.json")
|
|
122
|
+
unless package_json_path.exist?
|
|
123
|
+
logger.error "❌ No package.json found"
|
|
124
|
+
return { removed_packages: [], config_files_deleted: [], preserved_packages: [] }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Check if ESLint uses Babel parser
|
|
128
|
+
preserved_for_eslint = []
|
|
129
|
+
if eslint_uses_babel?
|
|
130
|
+
logger.info "\n⚠️ ESLint configuration detected that uses Babel parser"
|
|
131
|
+
logger.info " Preserving @babel/core and @babel/eslint-parser for ESLint compatibility"
|
|
132
|
+
logger.info " To switch ESLint parser:"
|
|
133
|
+
logger.info " 1. For TypeScript: use @typescript-eslint/parser"
|
|
134
|
+
logger.info " 2. For JavaScript: use espree (ESLint's default parser)"
|
|
135
|
+
preserved_for_eslint = ESLINT_BABEL_PACKAGES
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
removed_packages = remove_babel_from_package_json(package_json_path, preserve: preserved_for_eslint)
|
|
139
|
+
deleted_files = delete_babel_config_files
|
|
140
|
+
|
|
141
|
+
if removed_packages.any?
|
|
142
|
+
logger.info "✅ Babel packages removed successfully!"
|
|
143
|
+
run_package_manager_install if run_installer
|
|
144
|
+
else
|
|
145
|
+
logger.info "ℹ️ No Babel packages found to remove"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
{ removed_packages: removed_packages, config_files_deleted: deleted_files, preserved_packages: preserved_for_eslint }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def find_babel_packages
|
|
152
|
+
package_json_path = root_path.join("package.json")
|
|
153
|
+
return [] unless package_json_path.exist?
|
|
154
|
+
|
|
155
|
+
begin
|
|
156
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
157
|
+
dependencies = package_json["dependencies"] || {}
|
|
158
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
159
|
+
all_deps = dependencies.merge(dev_dependencies)
|
|
160
|
+
|
|
161
|
+
# Find all babel packages (including ESLint-related ones for display)
|
|
162
|
+
all_babel_packages = BABEL_PACKAGES + ESLINT_BABEL_PACKAGES
|
|
163
|
+
found_packages = all_babel_packages.select { |pkg| all_deps.key?(pkg) }
|
|
164
|
+
found_packages
|
|
165
|
+
rescue JSON::ParserError => e
|
|
166
|
+
logger.error "Failed to parse package.json: #{e.message}"
|
|
167
|
+
[]
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
private
|
|
172
|
+
|
|
173
|
+
def eslint_uses_babel?
|
|
174
|
+
# Check for ESLint config files
|
|
175
|
+
# Note: This is a heuristic check that may have false positives (e.g., in comments),
|
|
176
|
+
# but false positives only result in an extra warning, which is safer than silently
|
|
177
|
+
# breaking ESLint configurations.
|
|
178
|
+
ESLINT_CONFIG_FILES.each do |config_file|
|
|
179
|
+
config_path = root_path.join(config_file)
|
|
180
|
+
next unless config_path.exist?
|
|
181
|
+
|
|
182
|
+
content = File.read(config_path)
|
|
183
|
+
# Check for Babel parser references
|
|
184
|
+
return true if content.match?(/@babel\/eslint-parser|babel-eslint/)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Check package.json for eslintConfig
|
|
188
|
+
package_json_path = root_path.join("package.json")
|
|
189
|
+
if package_json_path.exist?
|
|
190
|
+
begin
|
|
191
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
192
|
+
if package_json["eslintConfig"]
|
|
193
|
+
# Check parser field explicitly
|
|
194
|
+
parser = package_json["eslintConfig"]["parser"]
|
|
195
|
+
return true if parser && parser.match?(/@babel\/eslint-parser|babel-eslint/)
|
|
196
|
+
|
|
197
|
+
# Also check entire config for babel parser references (catches nested configs)
|
|
198
|
+
return true if package_json["eslintConfig"].to_json.match?(/@babel\/eslint-parser|babel-eslint/)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Check if Babel ESLint packages are installed
|
|
202
|
+
dependencies = package_json["dependencies"] || {}
|
|
203
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
204
|
+
all_deps = dependencies.merge(dev_dependencies)
|
|
205
|
+
return true if all_deps.key?("@babel/eslint-parser") || all_deps.key?("babel-eslint")
|
|
206
|
+
rescue JSON::ParserError => e
|
|
207
|
+
logger.debug "Could not parse package.json for ESLint detection: #{e.message}"
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
false
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def update_shakapacker_config
|
|
215
|
+
config_path = root_path.join("config/shakapacker.yml")
|
|
216
|
+
return false unless config_path.exist?
|
|
217
|
+
|
|
218
|
+
logger.info "📝 Updating shakapacker.yml..."
|
|
219
|
+
config = begin
|
|
220
|
+
YAML.load_file(config_path, aliases: true)
|
|
221
|
+
rescue ArgumentError
|
|
222
|
+
YAML.load_file(config_path)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
config.each do |env, settings|
|
|
226
|
+
next unless settings.is_a?(Hash)
|
|
227
|
+
|
|
228
|
+
if settings["babel"]
|
|
229
|
+
logger.info " - Removing babel config from #{env} environment"
|
|
230
|
+
settings.delete("babel")
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
settings["javascript_transpiler"] = "swc"
|
|
234
|
+
logger.info " - Set javascript_transpiler to 'swc' for #{env} environment"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
File.write(config_path, config.to_yaml)
|
|
238
|
+
logger.info "✅ shakapacker.yml updated"
|
|
239
|
+
true
|
|
240
|
+
rescue StandardError => e
|
|
241
|
+
logger.error "Failed to update config: #{e.message}"
|
|
242
|
+
false
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def install_swc_packages
|
|
246
|
+
package_json_path = root_path.join("package.json")
|
|
247
|
+
return {} unless package_json_path.exist?
|
|
248
|
+
|
|
249
|
+
logger.info "📦 Installing SWC dependencies..."
|
|
250
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
251
|
+
|
|
252
|
+
dependencies = package_json["dependencies"] || {}
|
|
253
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
254
|
+
installed = {}
|
|
255
|
+
|
|
256
|
+
SWC_PACKAGES.each do |package, version|
|
|
257
|
+
unless dependencies[package] || dev_dependencies[package]
|
|
258
|
+
logger.info " - Adding #{package}@#{version}"
|
|
259
|
+
dev_dependencies[package] = version
|
|
260
|
+
installed[package] = version
|
|
261
|
+
else
|
|
262
|
+
logger.info " - #{package} already installed"
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
if installed.any?
|
|
267
|
+
package_json["devDependencies"] = dev_dependencies
|
|
268
|
+
File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
|
|
269
|
+
logger.info "✅ package.json updated with SWC dependencies"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
installed
|
|
273
|
+
rescue StandardError => e
|
|
274
|
+
logger.error "Failed to install packages: #{e.message}"
|
|
275
|
+
{}
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def create_swc_config
|
|
279
|
+
config_dir = root_path.join("config")
|
|
280
|
+
swc_config_path = config_dir.join("swc.config.js")
|
|
281
|
+
|
|
282
|
+
if swc_config_path.exist?
|
|
283
|
+
logger.info "ℹ️ config/swc.config.js already exists"
|
|
284
|
+
return false
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
FileUtils.mkdir_p(config_dir) unless config_dir.exist?
|
|
288
|
+
|
|
289
|
+
logger.info "📄 Creating config/swc.config.js..."
|
|
290
|
+
File.write(swc_config_path, DEFAULT_SWC_CONFIG)
|
|
291
|
+
logger.info "✅ config/swc.config.js created"
|
|
292
|
+
true
|
|
293
|
+
rescue StandardError => e
|
|
294
|
+
logger.error "Failed to create config/swc.config.js: #{e.message}"
|
|
295
|
+
false
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def remove_babel_from_package_json(package_json_path, preserve: [])
|
|
299
|
+
package_json = JSON.parse(File.read(package_json_path))
|
|
300
|
+
dependencies = package_json["dependencies"] || {}
|
|
301
|
+
dev_dependencies = package_json["devDependencies"] || {}
|
|
302
|
+
removed_packages = []
|
|
303
|
+
|
|
304
|
+
BABEL_PACKAGES.each do |package|
|
|
305
|
+
next if preserve.include?(package)
|
|
306
|
+
|
|
307
|
+
if dependencies.delete(package)
|
|
308
|
+
removed_packages << package
|
|
309
|
+
logger.info " - Removed #{package} from dependencies"
|
|
310
|
+
end
|
|
311
|
+
if dev_dependencies.delete(package)
|
|
312
|
+
removed_packages << package
|
|
313
|
+
logger.info " - Removed #{package} from devDependencies"
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
# Log preserved packages
|
|
318
|
+
preserve.each do |package|
|
|
319
|
+
if dependencies[package] || dev_dependencies[package]
|
|
320
|
+
logger.info " - Preserved #{package} (needed for ESLint)"
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
if removed_packages.any?
|
|
325
|
+
package_json["dependencies"] = dependencies
|
|
326
|
+
package_json["devDependencies"] = dev_dependencies
|
|
327
|
+
File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
|
|
328
|
+
logger.info "✅ package.json updated"
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
removed_packages.uniq
|
|
332
|
+
rescue StandardError => e
|
|
333
|
+
logger.error "Failed to remove packages: #{e.message}"
|
|
334
|
+
[]
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def delete_babel_config_files
|
|
338
|
+
deleted_files = []
|
|
339
|
+
babel_config_files = [".babelrc", "babel.config.js", ".babelrc.js", "babel.config.json"]
|
|
340
|
+
|
|
341
|
+
babel_config_files.each do |file|
|
|
342
|
+
file_path = root_path.join(file)
|
|
343
|
+
if file_path.exist?
|
|
344
|
+
logger.info " - Removing #{file}"
|
|
345
|
+
File.delete(file_path)
|
|
346
|
+
deleted_files << file
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
deleted_files
|
|
351
|
+
rescue StandardError => e
|
|
352
|
+
logger.error "Failed to delete config files: #{e.message}"
|
|
353
|
+
[]
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def run_package_manager_install
|
|
357
|
+
logger.info "\n🔧 Running npm/yarn install..."
|
|
358
|
+
|
|
359
|
+
yarn_lock = root_path.join("yarn.lock")
|
|
360
|
+
pnpm_lock = root_path.join("pnpm-lock.yaml")
|
|
361
|
+
|
|
362
|
+
if yarn_lock.exist?
|
|
363
|
+
system("yarn install")
|
|
364
|
+
elsif pnpm_lock.exist?
|
|
365
|
+
system("pnpm install")
|
|
366
|
+
else
|
|
367
|
+
system("npm install")
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def package_manager
|
|
372
|
+
yarn_lock = root_path.join("yarn.lock")
|
|
373
|
+
pnpm_lock = root_path.join("pnpm-lock.yaml")
|
|
374
|
+
|
|
375
|
+
if yarn_lock.exist?
|
|
376
|
+
"yarn"
|
|
377
|
+
elsif pnpm_lock.exist?
|
|
378
|
+
"pnpm"
|
|
379
|
+
else
|
|
380
|
+
"npm"
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
@@ -16,7 +16,7 @@ module Shakapacker
|
|
|
16
16
|
|
|
17
17
|
# Emits a warning if it's not obvious what package manager to use
|
|
18
18
|
def self.error_unless_package_manager_is_obvious!
|
|
19
|
-
return unless PackageJson.read.fetch("packageManager", nil).nil?
|
|
19
|
+
return unless PackageJson.read(rails_root).fetch("packageManager", nil).nil?
|
|
20
20
|
|
|
21
21
|
guessed_binary = guess_binary
|
|
22
22
|
|
|
@@ -35,7 +35,7 @@ module Shakapacker
|
|
|
35
35
|
#
|
|
36
36
|
# @return [String]
|
|
37
37
|
def self.guess_binary
|
|
38
|
-
MANAGER_LOCKS.find { |_, lock| File.exist?(lock) }&.first || "npm"
|
|
38
|
+
MANAGER_LOCKS.find { |_, lock| File.exist?(rails_root.join(lock)) }&.first || "npm"
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Guesses the version of the package manager to use by calling `<manager> --version`
|
|
@@ -53,6 +53,19 @@ module Shakapacker
|
|
|
53
53
|
|
|
54
54
|
stdout.chomp
|
|
55
55
|
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
def self.rails_root
|
|
59
|
+
if defined?(APP_ROOT)
|
|
60
|
+
Pathname.new(APP_ROOT)
|
|
61
|
+
elsif ENV["APP_ROOT"]
|
|
62
|
+
Pathname.new(ENV["APP_ROOT"])
|
|
63
|
+
elsif defined?(Rails)
|
|
64
|
+
Rails.root
|
|
65
|
+
else
|
|
66
|
+
raise "can only be called from a rails environment or with APP_ROOT defined"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
56
69
|
end
|
|
57
70
|
end
|
|
58
71
|
end
|
data/lib/shakapacker/version.rb
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
2
|
+
require_relative "version"
|
|
3
3
|
|
|
4
4
|
module Shakapacker
|
|
5
5
|
class VersionChecker
|
|
@@ -126,7 +126,7 @@ module Shakapacker
|
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def relative_path?
|
|
129
|
-
raw.match(%r{(\.\.|\Afile
|
|
129
|
+
raw.match(%r{(\.\.|\Afile:)}).present?
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
def git_url?
|
|
@@ -1,50 +1,13 @@
|
|
|
1
1
|
require "shellwords"
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
require_relative "runner"
|
|
3
4
|
|
|
4
5
|
module Shakapacker
|
|
5
6
|
class WebpackRunner < Shakapacker::Runner
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"-h",
|
|
11
|
-
"version",
|
|
12
|
-
"v",
|
|
13
|
-
"--version",
|
|
14
|
-
"-v",
|
|
15
|
-
"info",
|
|
16
|
-
"i"
|
|
17
|
-
].freeze
|
|
18
|
-
|
|
19
|
-
def run
|
|
20
|
-
env = Shakapacker::Compiler.env
|
|
21
|
-
env["SHAKAPACKER_CONFIG"] = @shakapacker_config
|
|
22
|
-
env["NODE_OPTIONS"] = ENV["NODE_OPTIONS"] || ""
|
|
23
|
-
|
|
24
|
-
cmd = build_cmd
|
|
25
|
-
|
|
26
|
-
if @argv.delete("--debug-shakapacker")
|
|
27
|
-
env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --inspect-brk"
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
if @argv.delete "--trace-deprecation"
|
|
31
|
-
env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --trace-deprecation"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
if @argv.delete "--no-deprecation"
|
|
35
|
-
env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --no-deprecation"
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Webpack commands are not compatible with --config option.
|
|
39
|
-
if (@argv & WEBPACK_COMMANDS).empty?
|
|
40
|
-
cmd += ["--config", @webpack_config]
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
cmd += @argv
|
|
44
|
-
|
|
45
|
-
Dir.chdir(@app_path) do
|
|
46
|
-
Kernel.exec env, *cmd
|
|
47
|
-
end
|
|
7
|
+
def self.run(argv)
|
|
8
|
+
$stdout.sync = true
|
|
9
|
+
Shakapacker.ensure_node_env!
|
|
10
|
+
new(argv).run
|
|
48
11
|
end
|
|
49
12
|
|
|
50
13
|
private
|
data/lib/shakapacker.rb
CHANGED
|
@@ -6,7 +6,10 @@ require "active_support/tagged_logging"
|
|
|
6
6
|
module Shakapacker
|
|
7
7
|
extend self
|
|
8
8
|
|
|
9
|
-
DEFAULT_ENV = "
|
|
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)
|
|
@@ -37,13 +47,14 @@ module Shakapacker
|
|
|
37
47
|
delegate :bootstrap, :clean, :clobber, :compile, to: :commands
|
|
38
48
|
end
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
require_relative "shakapacker/instance"
|
|
51
|
+
require_relative "shakapacker/env"
|
|
52
|
+
require_relative "shakapacker/configuration"
|
|
53
|
+
require_relative "shakapacker/manifest"
|
|
54
|
+
require_relative "shakapacker/compiler"
|
|
55
|
+
require_relative "shakapacker/commands"
|
|
56
|
+
require_relative "shakapacker/dev_server"
|
|
57
|
+
require_relative "shakapacker/doctor"
|
|
58
|
+
require_relative "shakapacker/deprecation_helper"
|
|
59
|
+
|
|
60
|
+
require_relative "shakapacker/railtie" if defined?(Rails)
|
|
@@ -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
|
|
@@ -2,10 +2,20 @@ install_template_path = File.expand_path("../../install/template.rb", __dir__).f
|
|
|
2
2
|
bin_path = ENV["BUNDLE_BIN"] || Rails.root.join("bin")
|
|
3
3
|
|
|
4
4
|
namespace :shakapacker do
|
|
5
|
-
desc "Install Shakapacker in this application"
|
|
6
|
-
task install: [:check_node] do |task|
|
|
5
|
+
desc "Install Shakapacker in this application (use SHAKAPACKER_ASSETS_BUNDLER=rspack for Rspack, --typescript for TypeScript)"
|
|
6
|
+
task :install, [:bundler, :typescript] => [:check_node] do |task, args|
|
|
7
7
|
Shakapacker::Configuration.installing = true
|
|
8
8
|
|
|
9
|
+
if args[:bundler] == "rspack" || ENV["SHAKAPACKER_ASSETS_BUNDLER"] == "rspack"
|
|
10
|
+
ENV["SHAKAPACKER_ASSETS_BUNDLER"] = "rspack"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Set typescript flag if passed as argument
|
|
14
|
+
# Accepts: typescript, true, or any truthy value
|
|
15
|
+
if args[:typescript] && args[:typescript] != "false"
|
|
16
|
+
ENV["SHAKAPACKER_USE_TYPESCRIPT"] = "true"
|
|
17
|
+
end
|
|
18
|
+
|
|
9
19
|
prefix = task.name.split(/#|shakapacker:install/).first
|
|
10
20
|
|
|
11
21
|
if Rails::VERSION::MAJOR >= 5
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require "shakapacker/swc_migrator"
|
|
2
|
+
|
|
3
|
+
namespace :shakapacker do
|
|
4
|
+
desc "Migrate from Babel to SWC transpiler"
|
|
5
|
+
task :migrate_to_swc do
|
|
6
|
+
Shakapacker::SwcMigrator.new(Rails.root).migrate_to_swc
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc "Remove Babel packages after migrating to SWC"
|
|
10
|
+
task :clean_babel_packages do
|
|
11
|
+
Shakapacker::SwcMigrator.new(Rails.root).clean_babel_packages
|
|
12
|
+
end
|
|
13
|
+
end
|