shakapacker 9.6.0.rc.0 → 9.6.0.rc.1
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/.github/workflows/claude-code-review.yml +1 -0
- data/CHANGELOG.md +8 -3
- data/docs/releasing.md +100 -21
- data/lib/install/template.rb +3 -3
- data/lib/shakapacker/version.rb +1 -1
- data/package.json +1 -1
- data/test/package/rspack/index.test.js +251 -0
- data/test/package/rspack/optimization.test.js +86 -0
- data/test/package/rspack/plugins.test.js +185 -0
- data/test/package/rspack/rules.test.js +229 -0
- data/test/resolver.js +34 -3
- metadata +10 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68ba8fdde7f793bee53354a639cf21695963ff564e3478de1e19711f9e71704d
|
|
4
|
+
data.tar.gz: f5baa408e07f17e89cb0ba19abe652a4eeba151400b5014b4381156221e25811
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3845c0fd8e02339a077dd2a385bc2ed32905e62f56f786177c5761ddca3fab1b521bb92aeb1469458657cb139273137079a33e1ebbffba9574cf1e192e56fac2
|
|
7
|
+
data.tar.gz: 81d6113206160b9184bc1a0016cbe52d0c0ee565cd951da3b613ae99dd58ea83279d6ee60bfa6be8f14a2d524900941ed7568c8169ba2d855a9734c856260cc3
|
data/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
|
|
12
12
|
Changes since the last non-beta release.
|
|
13
13
|
|
|
14
|
+
## [v9.6.0-rc.0] - March 5, 2026
|
|
15
|
+
|
|
14
16
|
### Security
|
|
15
17
|
|
|
16
18
|
- Removed default `Access-Control-Allow-Origin: *` header from dev server configuration. This header allowed any website to access dev server resources. **If your setup runs webpack-dev-server on a different port from your Rails server, uncomment the `headers` section in `config/shakapacker.yml` to restore cross-origin asset loading.** [PR #936](https://github.com/shakacode/shakapacker/pull/936) by [justin808](https://github.com/justin808). Fixes [#935](https://github.com/shakacode/shakapacker/issues/935).
|
|
@@ -35,6 +37,7 @@ Changes since the last non-beta release.
|
|
|
35
37
|
|
|
36
38
|
### Changed
|
|
37
39
|
|
|
40
|
+
- **Changed default file rule type from `asset/resource` to `asset`**. [PR #901](https://github.com/shakacode/shakapacker/pull/901) by [justin808](https://github.com/justin808). Static assets (images, fonts, SVGs) now use webpack/rspack's `asset` type instead of `asset/resource`, allowing the bundler to automatically inline small files as data URIs for better performance.
|
|
38
41
|
- Allow `compression-webpack-plugin` v12. [PR #937](https://github.com/shakacode/shakapacker/pull/937) by [G-Rath](https://github.com/G-Rath).
|
|
39
42
|
- **BREAKING: sass-loader now defaults to modern Sass API**. [PR #879](https://github.com/shakacode/shakapacker/pull/879) by [justin808](https://github.com/justin808). The sass-loader configuration now uses `api: "modern"` instead of the deprecated legacy API. This improves compatibility with plugins like sass-resources-loader that require the modern API. If you experience issues after upgrading, you can revert to the legacy API by customizing your webpack config:
|
|
40
43
|
|
|
@@ -63,6 +66,7 @@ Changes since the last non-beta release.
|
|
|
63
66
|
- **Fixed orphaned webpack/rspack processes when foreman receives SIGTERM**. [PR #888](https://github.com/shakacode/shakapacker/pull/888) by [jordan-brough](https://github.com/jordan-brough). When running under foreman, sending SIGTERM to foreman (e.g. `kill <pid>`) would kill the Ruby shakapacker process but leave the webpack/rspack child process running as an orphan. DevServerRunner now uses `exec` to replace the Ruby process entirely, and Runner uses `spawn` with SIGTERM forwarding to ensure the child process is properly terminated.
|
|
64
67
|
- **Fixed missing-environment fallback to use production instead of development**. [PR #894](https://github.com/shakacode/shakapacker/pull/894) by [justin808](https://github.com/justin808). When a Rails environment (e.g., staging) is not defined in `shakapacker.yml`, Shakapacker now falls back to the `production` configuration instead of `development`. This ensures unknown environments get production-optimized webpack/rspack builds by default.
|
|
65
68
|
- **Fixed installer writing wrong shakapacker version in package.json**. [PR #899](https://github.com/shakacode/shakapacker/pull/899) by [justin808](https://github.com/justin808). The `shakapacker:install` generator now keeps the `package.json` dependency value in sync with the exact version or path that was requested, instead of relying on the post-install value which could differ.
|
|
69
|
+
- **Fixed `privateOutputPath` not being computed in JavaScript config**. [PR #891](https://github.com/shakacode/shakapacker/pull/891) by [ihabadham](https://github.com/ihabadham). The `private_output_path` setting from `shakapacker.yml` is now properly resolved to an absolute path and exposed as `privateOutputPath` in the JavaScript configuration, matching the behavior already present in the Ruby configuration.
|
|
66
70
|
- **Fixed installer not updating `shakapacker.yml` when selecting a non-default transpiler**. [PR #895](https://github.com/shakacode/shakapacker/pull/895) by [codex-rs](https://github.com/apps/codex-rs). Installing with `JAVASCRIPT_TRANSPILER=babel` (or `esbuild`) now correctly updates `config/shakapacker.yml` to match the selected transpiler instead of leaving it set to `swc`. Previously, a quote mismatch in the `gsub_file` call meant the config was never actually updated, and the condition also excluded `JAVASCRIPT_TRANSPILER=babel` from the update entirely. Additionally, `JAVASCRIPT_TRANSPILER=babel` no longer installs SWC packages.
|
|
67
71
|
- **Fixed ENOENT crash on clean builds when using `webpack-assets-manifest` v6 with `merge: true`**. [PR #931](https://github.com/shakacode/shakapacker/pull/931) by [justin808](https://github.com/justin808). Seeds an empty `{}` manifest file before instantiating the plugin, so the merge read succeeds on first build rather than throwing an unhandled ENOENT.
|
|
68
72
|
- **Improved error message when manifest is empty or missing**. [PR #872](https://github.com/shakacode/shakapacker/pull/872) by [justin808](https://github.com/justin808). When the bundler is still compiling (empty manifest) or hasn't run yet (missing manifest file), users now see clear, actionable error messages instead of the generic 7-point checklist.
|
|
@@ -116,7 +120,7 @@ Changes since the last non-beta release.
|
|
|
116
120
|
|
|
117
121
|
- **Added `SHAKAPACKER_SKIP_PRECOMPILE_HOOK` environment variable to skip precompile hook**. [PR #850](https://github.com/shakacode/shakapacker/pull/850) by [justin808](https://github.com/justin808). Set `SHAKAPACKER_SKIP_PRECOMPILE_HOOK=true` to skip the precompile hook during compilation. This is useful when using process managers like Foreman or Overmind to run the hook once before starting multiple webpack processes, preventing duplicate hook execution. **Migration tip:** If you have a custom `bin/dev` script that starts multiple webpack processes, you can now run the precompile hook once in the script and set this environment variable to prevent each webpack process from running the hook again. See the [precompile hook documentation](./docs/precompile_hook.md#skipping-the-hook) for implementation examples.
|
|
118
122
|
|
|
119
|
-
## [v9.3.4
|
|
123
|
+
## [v9.3.4] - November 17, 2025
|
|
120
124
|
|
|
121
125
|
### Fixed
|
|
122
126
|
|
|
@@ -864,10 +868,11 @@ Note: [Rubygem is 6.3.0.pre.rc.1](https://rubygems.org/gems/shakapacker/versions
|
|
|
864
868
|
|
|
865
869
|
See [CHANGELOG.md in rails/webpacker (up to v5.4.3)](https://github.com/rails/webpacker/blob/master/CHANGELOG.md)
|
|
866
870
|
|
|
867
|
-
[Unreleased]: https://github.com/shakacode/shakapacker/compare/v9.
|
|
871
|
+
[Unreleased]: https://github.com/shakacode/shakapacker/compare/v9.6.0-rc.0...main
|
|
872
|
+
[v9.6.0-rc.0]: https://github.com/shakacode/shakapacker/compare/v9.5.0...v9.6.0-rc.0
|
|
868
873
|
[v9.5.0]: https://github.com/shakacode/shakapacker/compare/v9.4.0...v9.5.0
|
|
869
874
|
[v9.4.0]: https://github.com/shakacode/shakapacker/compare/v9.3.4...v9.4.0
|
|
870
|
-
[v9.3.4
|
|
875
|
+
[v9.3.4]: https://github.com/shakacode/shakapacker/compare/v9.3.3...v9.3.4
|
|
871
876
|
[v9.3.3]: https://github.com/shakacode/shakapacker/compare/v9.3.2...v9.3.3
|
|
872
877
|
[v9.3.2]: https://github.com/shakacode/shakapacker/compare/v9.3.1...v9.3.2
|
|
873
878
|
[v9.3.1]: https://github.com/shakacode/shakapacker/compare/v9.3.0...v9.3.1
|
data/docs/releasing.md
CHANGED
|
@@ -9,6 +9,7 @@ This guide is for Shakapacker maintainers who need to publish a new release.
|
|
|
9
9
|
```bash
|
|
10
10
|
bundle install # Installs gem-release
|
|
11
11
|
yarn global add release-it # Installs release-it for npm publishing
|
|
12
|
+
gh --version # Required only if you plan to run sync_github_release
|
|
12
13
|
```
|
|
13
14
|
|
|
14
15
|
2. **Ensure you have publishing access:**
|
|
@@ -18,6 +19,8 @@ This guide is for Shakapacker maintainers who need to publish a new release.
|
|
|
18
19
|
3. **Enable 2FA on both platforms:**
|
|
19
20
|
- npm: 2FA is required for publishing
|
|
20
21
|
- RubyGems: 2FA is required for publishing
|
|
22
|
+
4. **If you plan to run `sync_github_release`, authenticate GitHub CLI:**
|
|
23
|
+
- Run `gh auth login` and ensure your account/token has write access to this repository
|
|
21
24
|
|
|
22
25
|
## Release Process
|
|
23
26
|
|
|
@@ -26,9 +29,7 @@ This guide is for Shakapacker maintainers who need to publish a new release.
|
|
|
26
29
|
Before running the release task:
|
|
27
30
|
|
|
28
31
|
1. Ensure all desired changes are merged to `main` branch
|
|
29
|
-
2.
|
|
30
|
-
3. Commit the CHANGELOG changes
|
|
31
|
-
4. Ensure your working directory is clean (`git status` shows no uncommitted changes)
|
|
32
|
+
2. Ensure your working directory is clean (`git status` shows no uncommitted changes)
|
|
32
33
|
|
|
33
34
|
### 2. Run the Release Task
|
|
34
35
|
|
|
@@ -36,38 +37,89 @@ The automated release task handles the entire release process:
|
|
|
36
37
|
|
|
37
38
|
```bash
|
|
38
39
|
# For a specific version (e.g., 9.1.0)
|
|
39
|
-
bundle exec rake create_release[9.1.0]
|
|
40
|
+
bundle exec rake "create_release[9.1.0]"
|
|
40
41
|
|
|
41
42
|
# For a beta release (note: use period, not dash)
|
|
42
|
-
bundle exec rake create_release[9.2.0.beta.1] # Creates npm package 9.2.0-beta.1
|
|
43
|
+
bundle exec rake "create_release[9.2.0.beta.1]" # Creates npm package 9.2.0-beta.1
|
|
44
|
+
|
|
45
|
+
# For a release candidate
|
|
46
|
+
bundle exec rake "create_release[9.6.0.rc.0]"
|
|
47
|
+
|
|
48
|
+
# Auto-calculate next prerelease and confirm before publishing
|
|
49
|
+
bundle exec rake "create_prerelease[9.6.0]" # defaults to rc -> 9.6.0.rc.0 or 9.6.0.rc.1, etc.
|
|
50
|
+
bundle exec rake "create_prerelease[9.6.0,rc]" # -> 9.6.0.rc.0 or 9.6.0.rc.1, etc.
|
|
51
|
+
bundle exec rake "create_prerelease[9.6.0,beta]" # -> 9.6.0.beta.0 or 9.6.0.beta.1, etc.
|
|
43
52
|
|
|
44
53
|
# For a patch version bump (auto-increments)
|
|
45
|
-
bundle exec rake create_release
|
|
54
|
+
bundle exec rake create_release # prompts to confirm computed patch version
|
|
46
55
|
|
|
47
56
|
# Dry run to test without publishing
|
|
48
|
-
bundle exec rake create_release[9.1.0,true]
|
|
57
|
+
bundle exec rake "create_release[9.1.0,true]"
|
|
58
|
+
|
|
59
|
+
# Override version policy checks (monotonic + changelog/bump consistency)
|
|
60
|
+
RELEASE_VERSION_POLICY_OVERRIDE=true bundle exec rake "create_release[9.1.0]"
|
|
61
|
+
bundle exec rake "create_release[9.1.0,false,true]"
|
|
49
62
|
```
|
|
50
63
|
|
|
64
|
+
Dry runs use a temporary git worktree so version bumps and installs do not modify your current checkout.
|
|
65
|
+
|
|
66
|
+
`create_release` and `create_prerelease` validate release-version policy before publishing:
|
|
67
|
+
- Target version must be greater than the latest tagged release.
|
|
68
|
+
- If the versioned target changelog section exists (`## [vX.Y.Z...]`; not `UNRELEASED`), it maps to expected bump type:
|
|
69
|
+
- Breaking changes => major bump
|
|
70
|
+
- Added/New Features/Features/Enhancements => minor bump
|
|
71
|
+
- Fixed/Fixes/Bug Fixes/Security/Improved/Deprecated => patch bump
|
|
72
|
+
- Other headings => no inferred bump level (consistency check is skipped)
|
|
73
|
+
|
|
74
|
+
Use override only when needed:
|
|
75
|
+
- `RELEASE_VERSION_POLICY_OVERRIDE=true`
|
|
76
|
+
- Or task arg override (`create_release[..., ..., true]`, `create_prerelease[..., ..., ..., true]`)
|
|
77
|
+
|
|
51
78
|
### 3. What the Release Task Does
|
|
52
79
|
|
|
53
80
|
The `create_release` task automatically:
|
|
54
81
|
|
|
55
|
-
1. **
|
|
56
|
-
|
|
82
|
+
1. **Validates release prerequisites**:
|
|
83
|
+
- Verifies npm authentication
|
|
84
|
+
2. **Pulls latest changes** from the repository
|
|
85
|
+
3. **Bumps version numbers** in:
|
|
57
86
|
- `lib/shakapacker/version.rb` (Ruby gem version)
|
|
58
87
|
- `package.json` (npm package version - converted from Ruby format)
|
|
59
|
-
|
|
88
|
+
4. **Publishes to npm:**
|
|
60
89
|
- Prompts for npm OTP (2FA code)
|
|
61
90
|
- Creates git tag
|
|
62
91
|
- Pushes to GitHub
|
|
63
|
-
|
|
92
|
+
5. **Publishes to RubyGems:**
|
|
64
93
|
- Prompts for RubyGems OTP (2FA code)
|
|
65
|
-
|
|
94
|
+
6. **Updates spec/dummy lockfiles:**
|
|
66
95
|
- Runs `bundle install` to update `Gemfile.lock`
|
|
67
96
|
- Runs `npm install` to update `package-lock.json` (yarn.lock may also be updated for multi-package-manager compatibility testing)
|
|
68
|
-
|
|
97
|
+
7. **Commits and pushes lockfile changes** automatically
|
|
98
|
+
|
|
99
|
+
### 4. Sync GitHub Release (Optional, After Publish)
|
|
100
|
+
|
|
101
|
+
If you want GitHub Releases, do that as a separate step after publishing.
|
|
102
|
+
Requires GitHub CLI (`gh`) with write access to this repository:
|
|
103
|
+
|
|
104
|
+
Legacy note: `SKIP_GITHUB_RELEASE=true` is no longer used by release tasks. GitHub release creation is now an explicit, separate step via `sync_github_release`.
|
|
105
|
+
|
|
106
|
+
1. Run `bundle exec rake update_changelog`
|
|
107
|
+
2. Update `CHANGELOG.md` with the published version section
|
|
108
|
+
- For prerelease entries, use npm semver header format with dashes, for example `## [v9.6.0-rc.1]`
|
|
109
|
+
3. Commit `CHANGELOG.md`
|
|
110
|
+
4. Run:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Stable
|
|
114
|
+
bundle exec rake "sync_github_release[9.6.0]"
|
|
115
|
+
|
|
116
|
+
# Prerelease
|
|
117
|
+
bundle exec rake "sync_github_release[9.6.0.rc.1]"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`sync_github_release` reads release notes from the matching `CHANGELOG.md` section and creates/updates the GitHub release for the corresponding tag.
|
|
69
121
|
|
|
70
|
-
###
|
|
122
|
+
### 5. Version Format
|
|
71
123
|
|
|
72
124
|
**Important:** Use Ruby gem version format (no dashes):
|
|
73
125
|
|
|
@@ -83,28 +135,35 @@ The task automatically converts Ruby gem format to npm semver format:
|
|
|
83
135
|
|
|
84
136
|
```bash
|
|
85
137
|
# Regular release
|
|
86
|
-
bundle exec rake create_release[9.1.0] # Gem: 9.1.0, npm: 9.1.0
|
|
138
|
+
bundle exec rake "create_release[9.1.0]" # Gem: 9.1.0, npm: 9.1.0
|
|
87
139
|
|
|
88
140
|
# Beta release
|
|
89
|
-
bundle exec rake create_release[9.2.0.beta.1] # Gem: 9.2.0.beta.1, npm: 9.2.0-beta.1
|
|
141
|
+
bundle exec rake "create_release[9.2.0.beta.1]" # Gem: 9.2.0.beta.1, npm: 9.2.0-beta.1
|
|
90
142
|
|
|
91
143
|
# Release candidate
|
|
92
|
-
bundle exec rake create_release[10.0.0.rc.1] # Gem: 10.0.0.rc.1, npm: 10.0.0-rc.1
|
|
144
|
+
bundle exec rake "create_release[10.0.0.rc.1]" # Gem: 10.0.0.rc.1, npm: 10.0.0-rc.1
|
|
145
|
+
|
|
146
|
+
# Auto-next prerelease (recommended)
|
|
147
|
+
bundle exec rake "create_prerelease[10.0.0,rc]" # picks rc.0 then rc.1, etc., with confirmation
|
|
93
148
|
```
|
|
94
149
|
|
|
95
|
-
|
|
150
|
+
The `create_prerelease` task defaults to `rc` if prerelease type is omitted. Use `beta` explicitly when needed.
|
|
151
|
+
|
|
152
|
+
### 6. During the Release
|
|
96
153
|
|
|
97
154
|
1. When prompted for **npm OTP**, enter your 2FA code from your authenticator app
|
|
98
155
|
2. Accept defaults for release-it options
|
|
99
156
|
3. When prompted for **RubyGems OTP**, enter your 2FA code
|
|
100
|
-
4.
|
|
157
|
+
4. If using patch auto-bump (`create_release` with no version), confirm the computed patch version when prompted
|
|
158
|
+
5. If using `create_prerelease`, confirm the computed next prerelease version when prompted
|
|
159
|
+
6. The script will automatically commit and push lockfile updates
|
|
101
160
|
|
|
102
|
-
###
|
|
161
|
+
### 7. After Release
|
|
103
162
|
|
|
104
163
|
1. Verify the release on:
|
|
105
164
|
- [npm](https://www.npmjs.com/package/shakapacker)
|
|
106
165
|
- [RubyGems](https://rubygems.org/gems/shakapacker)
|
|
107
|
-
- [GitHub releases](https://github.com/shakacode/shakapacker/releases)
|
|
166
|
+
- [GitHub releases](https://github.com/shakacode/shakapacker/releases) if you ran `sync_github_release`
|
|
108
167
|
|
|
109
168
|
2. Check that the lockfile commit was pushed:
|
|
110
169
|
|
|
@@ -141,6 +200,26 @@ If publishing fails partway through:
|
|
|
141
200
|
3. If RubyGems failed: Fix the issue and manually run `gem release`
|
|
142
201
|
4. Then manually update and commit spec/dummy lockfiles
|
|
143
202
|
|
|
203
|
+
### GitHub Release Sync Fails
|
|
204
|
+
|
|
205
|
+
If package publishing succeeds but `sync_github_release` fails:
|
|
206
|
+
|
|
207
|
+
1. Fix GitHub auth (`gh auth login`) or permissions
|
|
208
|
+
2. Ensure `CHANGELOG.md` has matching header `## [vX.Y.Z...]` (npm format for prereleases)
|
|
209
|
+
3. Rerun only:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
bundle exec rake "sync_github_release[<gem_version>]"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Legacy `SKIP_GITHUB_RELEASE` Usage
|
|
216
|
+
|
|
217
|
+
If your CI or local runbook still sets `SKIP_GITHUB_RELEASE=true`:
|
|
218
|
+
|
|
219
|
+
1. `create_release` and `create_prerelease` now always skip GitHub release creation by design
|
|
220
|
+
2. Remove `SKIP_GITHUB_RELEASE` from scripts to avoid confusion
|
|
221
|
+
3. Run `sync_github_release` explicitly when you want to create or update a GitHub release
|
|
222
|
+
|
|
144
223
|
### Wrong Version Format
|
|
145
224
|
|
|
146
225
|
If you accidentally use npm format (with dashes):
|
data/lib/install/template.rb
CHANGED
|
@@ -18,7 +18,7 @@ install_dir = File.expand_path(File.dirname(__FILE__))
|
|
|
18
18
|
# Installation strategy:
|
|
19
19
|
# - USE_BABEL_PACKAGES installs both babel AND swc for compatibility
|
|
20
20
|
# - Otherwise install only the specified transpiler
|
|
21
|
-
if
|
|
21
|
+
if Shakapacker::Install::Env.truthy_env?("USE_BABEL_PACKAGES")
|
|
22
22
|
@transpiler_to_install = "babel"
|
|
23
23
|
@install_swc_compat_packages = true
|
|
24
24
|
say "📦 Installing Babel packages (USE_BABEL_PACKAGES is set)", :yellow
|
|
@@ -52,7 +52,7 @@ end
|
|
|
52
52
|
# Detect TypeScript usage
|
|
53
53
|
# Auto-detect from tsconfig.json or explicit via SHAKAPACKER_USE_TYPESCRIPT env var
|
|
54
54
|
@use_typescript = File.exist?(Rails.root.join("tsconfig.json")) ||
|
|
55
|
-
|
|
55
|
+
Shakapacker::Install::Env.truthy_env?("SHAKAPACKER_USE_TYPESCRIPT")
|
|
56
56
|
assets_bundler = ENV["SHAKAPACKER_ASSETS_BUNDLER"] || "webpack"
|
|
57
57
|
config_extension = @use_typescript ? "ts" : "js"
|
|
58
58
|
|
|
@@ -225,7 +225,7 @@ Dir.chdir(Rails.root) do
|
|
|
225
225
|
|
|
226
226
|
# Inline fetch_peer_dependencies and fetch_common_dependencies
|
|
227
227
|
peers = PackageJson.read(install_dir).fetch(ENV["SHAKAPACKER_ASSETS_BUNDLER"] || "webpack")
|
|
228
|
-
common_deps =
|
|
228
|
+
common_deps = Shakapacker::Install::Env.truthy_env?("SKIP_COMMON_LOADERS") ? {} : PackageJson.read(install_dir).fetch("common")
|
|
229
229
|
peers = peers.merge(common_deps)
|
|
230
230
|
|
|
231
231
|
# Add transpiler-specific dependencies based on detected/configured transpiler
|
data/lib/shakapacker/version.rb
CHANGED
data/package.json
CHANGED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/* eslint-disable jest/no-conditional-in-test */
|
|
2
|
+
|
|
3
|
+
const { chdirTestApp } = require("../../helpers")
|
|
4
|
+
|
|
5
|
+
const rootPath = process.cwd()
|
|
6
|
+
chdirTestApp()
|
|
7
|
+
|
|
8
|
+
// Mock config to ensure assets_bundler is set to rspack
|
|
9
|
+
jest.mock("../../../package/config", () => {
|
|
10
|
+
const actual = jest.requireActual("../../../package/config")
|
|
11
|
+
return {
|
|
12
|
+
...actual,
|
|
13
|
+
assets_bundler: "rspack"
|
|
14
|
+
}
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
// Mock helpers before requiring the rspack module
|
|
18
|
+
jest.mock("../../../package/utils/helpers", () => {
|
|
19
|
+
const original = jest.requireActual("../../../package/utils/helpers")
|
|
20
|
+
const moduleExists = jest.fn(() => true)
|
|
21
|
+
return {
|
|
22
|
+
...original,
|
|
23
|
+
moduleExists
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Mock validateDependencies to prevent actual validation
|
|
28
|
+
jest.mock("../../../package/utils/validateDependencies", () => ({
|
|
29
|
+
validateRspackDependencies: jest.fn()
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
describe("rspack/index", () => {
|
|
33
|
+
let rspackIndex
|
|
34
|
+
let validateRspackDependencies
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
jest.resetModules()
|
|
38
|
+
rspackIndex = require("../../../package/rspack/index")
|
|
39
|
+
;({
|
|
40
|
+
validateRspackDependencies
|
|
41
|
+
} = require("../../../package/utils/validateDependencies"))
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
afterAll(() => process.chdir(rootPath))
|
|
45
|
+
|
|
46
|
+
describe("exports", () => {
|
|
47
|
+
test("exports webpack-merge v5 functions", () => {
|
|
48
|
+
expect(rspackIndex.merge).toBeInstanceOf(Function)
|
|
49
|
+
expect(rspackIndex.mergeWithRules).toBeInstanceOf(Function)
|
|
50
|
+
expect(rspackIndex.mergeWithCustomize).toBeInstanceOf(Function)
|
|
51
|
+
expect(rspackIndex.unique).toBeInstanceOf(Function)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("exports config object", () => {
|
|
55
|
+
expect(rspackIndex.config).toHaveProperty("source_path")
|
|
56
|
+
expect(rspackIndex.config).toHaveProperty("public_output_path")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("exports devServer object", () => {
|
|
60
|
+
expect(rspackIndex.devServer).toBeDefined()
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("exports generateRspackConfig function", () => {
|
|
64
|
+
expect(rspackIndex.generateRspackConfig).toBeInstanceOf(Function)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("exports baseConfig object", () => {
|
|
68
|
+
expect(rspackIndex.baseConfig).toBeDefined()
|
|
69
|
+
expect(rspackIndex.baseConfig).toHaveProperty("mode")
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("exports env object", () => {
|
|
73
|
+
expect(rspackIndex.env).toHaveProperty("railsEnv")
|
|
74
|
+
expect(rspackIndex.env).toHaveProperty("nodeEnv")
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("exports rules array", () => {
|
|
78
|
+
expect(Array.isArray(rspackIndex.rules)).toBe(true)
|
|
79
|
+
expect(rspackIndex.rules.length).toBeGreaterThan(0)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("exports moduleExists function", () => {
|
|
83
|
+
expect(typeof rspackIndex.moduleExists).toBe("function")
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("exports canProcess function", () => {
|
|
87
|
+
expect(rspackIndex.canProcess).toBeInstanceOf(Function)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("exports inliningCss value", () => {
|
|
91
|
+
expect(typeof rspackIndex.inliningCss).toBe("boolean")
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
describe("generateRspackConfig", () => {
|
|
96
|
+
test("returns a valid rspack config object", () => {
|
|
97
|
+
const config = rspackIndex.generateRspackConfig()
|
|
98
|
+
|
|
99
|
+
expect(config).toBeDefined()
|
|
100
|
+
expect(config).toHaveProperty("mode")
|
|
101
|
+
expect(config).toHaveProperty("module")
|
|
102
|
+
expect(config).toHaveProperty("plugins")
|
|
103
|
+
expect(config).toHaveProperty("optimization")
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test("returns a new top-level config object on each call", () => {
|
|
107
|
+
const config1 = rspackIndex.generateRspackConfig()
|
|
108
|
+
const config2 = rspackIndex.generateRspackConfig()
|
|
109
|
+
|
|
110
|
+
expect(config1).not.toBe(config2)
|
|
111
|
+
config1.newKey = "new value"
|
|
112
|
+
|
|
113
|
+
expect(config2).not.toHaveProperty("newKey")
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("merges extra config", () => {
|
|
117
|
+
const config = rspackIndex.generateRspackConfig({
|
|
118
|
+
newKey: "new value",
|
|
119
|
+
output: {
|
|
120
|
+
path: "new path"
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
expect(config).toHaveProperty("newKey", "new value")
|
|
125
|
+
expect(config).toHaveProperty("output.path", "new path")
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test("includes module rules in config", () => {
|
|
129
|
+
const config = rspackIndex.generateRspackConfig()
|
|
130
|
+
|
|
131
|
+
expect(config.module).toBeDefined()
|
|
132
|
+
expect(config.module.rules).toBeDefined()
|
|
133
|
+
expect(Array.isArray(config.module.rules)).toBe(true)
|
|
134
|
+
// The exact number of rules depends on which optional loaders are installed,
|
|
135
|
+
// so we only verify that at least some rules exist
|
|
136
|
+
expect(config.module.rules.length).toBeGreaterThan(0)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
test("includes plugins in config", () => {
|
|
140
|
+
const config = rspackIndex.generateRspackConfig()
|
|
141
|
+
|
|
142
|
+
expect(config.plugins).toBeDefined()
|
|
143
|
+
expect(Array.isArray(config.plugins)).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("includes optimization in config", () => {
|
|
147
|
+
const config = rspackIndex.generateRspackConfig()
|
|
148
|
+
|
|
149
|
+
expect(config.optimization).toBeDefined()
|
|
150
|
+
expect(config.optimization).toHaveProperty("minimize")
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test("errors if multiple configs are provided", () => {
|
|
154
|
+
expect(() => rspackIndex.generateRspackConfig({}, {})).toThrow(
|
|
155
|
+
"use webpack-merge to merge configs before passing them to Shakapacker"
|
|
156
|
+
)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
test("validates rspack dependencies on generation", () => {
|
|
160
|
+
rspackIndex.generateRspackConfig()
|
|
161
|
+
expect(validateRspackDependencies).toHaveBeenCalledTimes(1)
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
describe("rules", () => {
|
|
166
|
+
test("includes JavaScript/JSX rule with builtin:swc-loader", () => {
|
|
167
|
+
const jsRule = rspackIndex.rules.find(
|
|
168
|
+
(rule) => rule.test && rule.test.toString().includes("js|jsx|mjs")
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
expect(jsRule).toBeDefined()
|
|
172
|
+
expect(jsRule.type).toBe("javascript/auto")
|
|
173
|
+
expect(jsRule.use).toBeDefined()
|
|
174
|
+
expect(Array.isArray(jsRule.use)).toBe(true)
|
|
175
|
+
expect(jsRule.use[0].loader).toBe("builtin:swc-loader")
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
test("includes TypeScript rule with builtin:swc-loader", () => {
|
|
179
|
+
const tsRule = rspackIndex.rules.find(
|
|
180
|
+
(rule) => rule.test && rule.test.toString().includes("ts|tsx")
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
expect(tsRule).toBeDefined()
|
|
184
|
+
expect(tsRule.type).toBe("javascript/auto")
|
|
185
|
+
expect(tsRule.use).toBeDefined()
|
|
186
|
+
expect(Array.isArray(tsRule.use)).toBe(true)
|
|
187
|
+
expect(tsRule.use[0].loader).toBe("builtin:swc-loader")
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
test("includes file/asset handling rule", () => {
|
|
191
|
+
const fileRule = rspackIndex.rules.find(
|
|
192
|
+
(rule) =>
|
|
193
|
+
rule.test &&
|
|
194
|
+
(rule.test.toString().includes("png") ||
|
|
195
|
+
rule.test.toString().includes("jpg") ||
|
|
196
|
+
rule.test.toString().includes("svg"))
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
expect(fileRule).toBeDefined()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test("includes raw file loading rule", () => {
|
|
203
|
+
const rawRule = rspackIndex.rules.find(
|
|
204
|
+
(rule) => rule.type === "asset/source"
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
expect(rawRule).toBeDefined()
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
describe("helper functions", () => {
|
|
212
|
+
test("moduleExists returns boolean", () => {
|
|
213
|
+
const result = rspackIndex.moduleExists("some-module")
|
|
214
|
+
expect(typeof result).toBe("boolean")
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
test("canProcess invokes callback when module resolves", () => {
|
|
218
|
+
const callback = jest.fn((modulePath) => ({
|
|
219
|
+
processed: true,
|
|
220
|
+
path: modulePath
|
|
221
|
+
}))
|
|
222
|
+
const result = rspackIndex.canProcess("path", callback)
|
|
223
|
+
|
|
224
|
+
expect(callback).toHaveBeenCalledWith(expect.any(String))
|
|
225
|
+
expect(result).toHaveProperty("processed", true)
|
|
226
|
+
expect(result).toHaveProperty("path")
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
test("canProcess returns null and does not invoke callback when module is missing", () => {
|
|
230
|
+
const callback = jest.fn()
|
|
231
|
+
const result = rspackIndex.canProcess(
|
|
232
|
+
"__definitely_not_a_real_package_name__",
|
|
233
|
+
callback
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
expect(result).toBeNull()
|
|
237
|
+
expect(callback).not.toHaveBeenCalled()
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
describe("environment integration", () => {
|
|
242
|
+
test("uses correct environment config based on NODE_ENV", () => {
|
|
243
|
+
const config = rspackIndex.generateRspackConfig()
|
|
244
|
+
const { nodeEnv } = rspackIndex.env
|
|
245
|
+
|
|
246
|
+
const expectedMode =
|
|
247
|
+
nodeEnv === "production" ? "production" : "development"
|
|
248
|
+
expect(config.mode).toBe(expectedMode)
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* eslint-disable func-names */
|
|
2
|
+
|
|
3
|
+
// Mock requireOrError to prevent actual module loading
|
|
4
|
+
jest.mock("../../../package/utils/requireOrError", () => ({
|
|
5
|
+
requireOrError: (moduleName) => {
|
|
6
|
+
if (moduleName === "@rspack/core") {
|
|
7
|
+
return {
|
|
8
|
+
SwcJsMinimizerRspackPlugin: jest.fn(function () {
|
|
9
|
+
this.name = "SwcJsMinimizerRspackPlugin"
|
|
10
|
+
}),
|
|
11
|
+
LightningCssMinimizerRspackPlugin: jest.fn(function () {
|
|
12
|
+
this.name = "LightningCssMinimizerRspackPlugin"
|
|
13
|
+
})
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Module ${moduleName} not found`)
|
|
17
|
+
}
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
// Mock debug logger
|
|
21
|
+
jest.mock("../../../package/utils/debug", () => ({
|
|
22
|
+
error: jest.fn(),
|
|
23
|
+
warn: jest.fn(),
|
|
24
|
+
info: jest.fn(),
|
|
25
|
+
debug: jest.fn()
|
|
26
|
+
}))
|
|
27
|
+
|
|
28
|
+
describe("rspack/optimization", () => {
|
|
29
|
+
let getOptimization
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.resetModules()
|
|
33
|
+
const optimizationModule = require("../../../package/optimization/rspack")
|
|
34
|
+
getOptimization = optimizationModule.getOptimization
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
jest.clearAllMocks()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
describe("getOptimization", () => {
|
|
42
|
+
test("returns an optimization config object", () => {
|
|
43
|
+
const optimization = getOptimization()
|
|
44
|
+
|
|
45
|
+
expect(optimization).toBeDefined()
|
|
46
|
+
expect(optimization).toHaveProperty("minimize")
|
|
47
|
+
expect(optimization).toHaveProperty("minimizer")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("sets minimize to true", () => {
|
|
51
|
+
const optimization = getOptimization()
|
|
52
|
+
|
|
53
|
+
expect(optimization.minimize).toBe(true)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("includes SwcJsMinimizerRspackPlugin", () => {
|
|
57
|
+
const optimization = getOptimization()
|
|
58
|
+
|
|
59
|
+
expect(Array.isArray(optimization.minimizer)).toBe(true)
|
|
60
|
+
const jsMinimizer = optimization.minimizer.find(
|
|
61
|
+
(m) => m.name === "SwcJsMinimizerRspackPlugin"
|
|
62
|
+
)
|
|
63
|
+
expect(jsMinimizer).toBeDefined()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("includes LightningCssMinimizerRspackPlugin", () => {
|
|
67
|
+
const optimization = getOptimization()
|
|
68
|
+
|
|
69
|
+
expect(Array.isArray(optimization.minimizer)).toBe(true)
|
|
70
|
+
const cssMinimizer = optimization.minimizer.find(
|
|
71
|
+
(m) => m.name === "LightningCssMinimizerRspackPlugin"
|
|
72
|
+
)
|
|
73
|
+
expect(cssMinimizer).toBeDefined()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("includes both minimizers in correct order", () => {
|
|
77
|
+
const optimization = getOptimization()
|
|
78
|
+
|
|
79
|
+
expect(optimization.minimizer).toHaveLength(2)
|
|
80
|
+
expect(optimization.minimizer[0].name).toBe("SwcJsMinimizerRspackPlugin")
|
|
81
|
+
expect(optimization.minimizer[1].name).toBe(
|
|
82
|
+
"LightningCssMinimizerRspackPlugin"
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
})
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/* eslint-disable func-names, jest/prefer-strict-equal */
|
|
2
|
+
|
|
3
|
+
// Mock helpers before requiring the plugins module
|
|
4
|
+
jest.mock("../../../package/utils/helpers", () => {
|
|
5
|
+
const original = jest.requireActual("../../../package/utils/helpers")
|
|
6
|
+
return {
|
|
7
|
+
...original,
|
|
8
|
+
moduleExists: jest.fn(() => true)
|
|
9
|
+
}
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
// Mock requireOrError to prevent actual module loading
|
|
13
|
+
jest.mock("../../../package/utils/requireOrError", () => ({
|
|
14
|
+
requireOrError: (moduleName) => {
|
|
15
|
+
if (moduleName === "rspack-manifest-plugin") {
|
|
16
|
+
return {
|
|
17
|
+
RspackManifestPlugin: jest.fn(function (options) {
|
|
18
|
+
this.options = options
|
|
19
|
+
this.name = "RspackManifestPlugin"
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (moduleName === "@rspack/core") {
|
|
24
|
+
return {
|
|
25
|
+
EnvironmentPlugin: jest.fn(function (env) {
|
|
26
|
+
this.env = env
|
|
27
|
+
this.name = "EnvironmentPlugin"
|
|
28
|
+
}),
|
|
29
|
+
CssExtractRspackPlugin: jest.fn(function (options) {
|
|
30
|
+
this.options = options
|
|
31
|
+
this.name = "CssExtractRspackPlugin"
|
|
32
|
+
}),
|
|
33
|
+
SubresourceIntegrityPlugin: jest.fn(function (options) {
|
|
34
|
+
this.options = options
|
|
35
|
+
this.name = "SubresourceIntegrityPlugin"
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`Module ${moduleName} not found`)
|
|
40
|
+
}
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
describe("rspack/plugins", () => {
|
|
44
|
+
let getPlugins
|
|
45
|
+
let moduleExists
|
|
46
|
+
let config
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
jest.resetModules()
|
|
50
|
+
config = require("../../../package/config")
|
|
51
|
+
moduleExists = require("../../../package/utils/helpers").moduleExists
|
|
52
|
+
const pluginsModule = require("../../../package/plugins/rspack")
|
|
53
|
+
getPlugins = pluginsModule.getPlugins
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
jest.clearAllMocks()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe("getPlugins", () => {
|
|
61
|
+
test("returns an array", () => {
|
|
62
|
+
const plugins = getPlugins()
|
|
63
|
+
expect(Array.isArray(plugins)).toBe(true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test("includes EnvironmentPlugin with filtered env", () => {
|
|
67
|
+
const plugins = getPlugins()
|
|
68
|
+
const envPlugin = plugins.find((p) => p.name === "EnvironmentPlugin")
|
|
69
|
+
expect(envPlugin).toBeDefined()
|
|
70
|
+
// EnvironmentPlugin receives getFilteredEnv() - a security-filtered version of process.env
|
|
71
|
+
// that only includes allowlisted environment variables
|
|
72
|
+
expect(envPlugin.env).toBeDefined()
|
|
73
|
+
expect(typeof envPlugin.env).toBe("object")
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("includes RspackManifestPlugin", () => {
|
|
77
|
+
const plugins = getPlugins()
|
|
78
|
+
const manifestPlugin = plugins.find(
|
|
79
|
+
(p) => p.name === "RspackManifestPlugin"
|
|
80
|
+
)
|
|
81
|
+
expect(manifestPlugin).toBeDefined()
|
|
82
|
+
expect(manifestPlugin.options).toBeDefined()
|
|
83
|
+
expect(manifestPlugin.options.writeToFileEmit).toBe(true)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("rspackManifestPlugin has generate function", () => {
|
|
87
|
+
const plugins = getPlugins()
|
|
88
|
+
const manifestPlugin = plugins.find(
|
|
89
|
+
(p) => p.name === "RspackManifestPlugin"
|
|
90
|
+
)
|
|
91
|
+
expect(manifestPlugin.options.generate).toBeInstanceOf(Function)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test("rspackManifestPlugin generate creates proper manifest structure", () => {
|
|
95
|
+
const plugins = getPlugins()
|
|
96
|
+
const manifestPlugin = plugins.find(
|
|
97
|
+
(p) => p.name === "RspackManifestPlugin"
|
|
98
|
+
)
|
|
99
|
+
const { publicPath } = manifestPlugin.options
|
|
100
|
+
|
|
101
|
+
const files = [
|
|
102
|
+
{ name: "app.js", path: `${publicPath}app-123.js` },
|
|
103
|
+
{ name: "app.css", path: `${publicPath}app-456.css` }
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
const entrypoints = {
|
|
107
|
+
app: ["app-123.js", "app-456.css"]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const manifest = manifestPlugin.options.generate(null, files, entrypoints)
|
|
111
|
+
|
|
112
|
+
expect(manifest["app.js"]).toBe(`${publicPath}app-123.js`)
|
|
113
|
+
expect(manifest["app.css"]).toBe(`${publicPath}app-456.css`)
|
|
114
|
+
expect(manifest).toHaveProperty("entrypoints")
|
|
115
|
+
expect(manifest.entrypoints).toHaveProperty("app")
|
|
116
|
+
expect(manifest.entrypoints.app).toHaveProperty("assets")
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("rspackManifestPlugin filters hot-update files", () => {
|
|
120
|
+
const plugins = getPlugins()
|
|
121
|
+
const manifestPlugin = plugins.find(
|
|
122
|
+
(p) => p.name === "RspackManifestPlugin"
|
|
123
|
+
)
|
|
124
|
+
const { publicPath } = manifestPlugin.options
|
|
125
|
+
|
|
126
|
+
const files = []
|
|
127
|
+
const entrypoints = {
|
|
128
|
+
app: [
|
|
129
|
+
"app-123.js",
|
|
130
|
+
"app.hot-update.js",
|
|
131
|
+
"app-456.css",
|
|
132
|
+
"app.hot-update.css"
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const manifest = manifestPlugin.options.generate(null, files, entrypoints)
|
|
137
|
+
|
|
138
|
+
expect(manifest.entrypoints.app.assets.js).toEqual([
|
|
139
|
+
`${publicPath}app-123.js`
|
|
140
|
+
])
|
|
141
|
+
expect(manifest.entrypoints.app.assets.css).toEqual([
|
|
142
|
+
`${publicPath}app-456.css`
|
|
143
|
+
])
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("includes CssExtractRspackPlugin when css-loader exists", () => {
|
|
147
|
+
moduleExists.mockReturnValue(true)
|
|
148
|
+
|
|
149
|
+
const plugins = getPlugins()
|
|
150
|
+
const cssPlugin = plugins.find((p) => p.name === "CssExtractRspackPlugin")
|
|
151
|
+
expect(cssPlugin).toBeDefined()
|
|
152
|
+
expect(cssPlugin.options.filename).toMatch(/^css\//)
|
|
153
|
+
expect(cssPlugin.options.emit).toBe(true)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test("does not include CssExtractRspackPlugin when css-loader is missing", () => {
|
|
157
|
+
moduleExists.mockReturnValue(false)
|
|
158
|
+
|
|
159
|
+
const plugins = getPlugins()
|
|
160
|
+
const cssPlugin = plugins.find((p) => p.name === "CssExtractRspackPlugin")
|
|
161
|
+
expect(cssPlugin).toBeUndefined()
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test("includes SubresourceIntegrityPlugin when integrity is enabled", () => {
|
|
165
|
+
const originalIntegrity = config.integrity
|
|
166
|
+
config.integrity = {
|
|
167
|
+
...originalIntegrity,
|
|
168
|
+
enabled: true,
|
|
169
|
+
hash_functions: ["sha256"]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const plugins = getPlugins()
|
|
174
|
+
const sriPlugin = plugins.find(
|
|
175
|
+
(p) => p.name === "SubresourceIntegrityPlugin"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
expect(sriPlugin).toBeDefined()
|
|
179
|
+
expect(sriPlugin.options.hashFuncNames).toEqual(["sha256"])
|
|
180
|
+
} finally {
|
|
181
|
+
config.integrity = originalIntegrity
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
})
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/* eslint-disable jest/no-conditional-in-test */
|
|
2
|
+
|
|
3
|
+
// Mock helpers and debug utilities
|
|
4
|
+
jest.mock("../../../package/utils/helpers", () => {
|
|
5
|
+
const original = jest.requireActual("../../../package/utils/helpers")
|
|
6
|
+
return {
|
|
7
|
+
...original,
|
|
8
|
+
moduleExists: jest.fn(() => true),
|
|
9
|
+
canProcess: jest.fn((_rule, callback) => callback("/mocked-loader"))
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
jest.mock("../../../package/utils/debug", () => ({
|
|
14
|
+
debug: jest.fn(),
|
|
15
|
+
info: jest.fn(),
|
|
16
|
+
warn: jest.fn(),
|
|
17
|
+
error: jest.fn()
|
|
18
|
+
}))
|
|
19
|
+
|
|
20
|
+
describe("rspack/rules", () => {
|
|
21
|
+
let rules
|
|
22
|
+
|
|
23
|
+
beforeAll(() => {
|
|
24
|
+
rules = require("../../../package/rules/rspack")
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe("rules array", () => {
|
|
28
|
+
test("exports an array", () => {
|
|
29
|
+
expect(Array.isArray(rules)).toBe(true)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test("contains multiple rules", () => {
|
|
33
|
+
expect(rules.length).toBeGreaterThan(0)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe("javaScript rule", () => {
|
|
38
|
+
test("includes rule for JS/JSX files", () => {
|
|
39
|
+
const jsRule = rules.find(
|
|
40
|
+
(rule) => rule.test && rule.test.toString().includes("js|jsx|mjs")
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
expect(jsRule).toBeDefined()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("uses builtin:swc-loader for JavaScript", () => {
|
|
47
|
+
const jsRule = rules.find(
|
|
48
|
+
(rule) => rule.test && rule.test.toString().includes("js|jsx|mjs")
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
expect(jsRule.use).toBeDefined()
|
|
52
|
+
expect(Array.isArray(jsRule.use)).toBe(true)
|
|
53
|
+
expect(jsRule.use[0].loader).toBe("builtin:swc-loader")
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test("excludes node_modules for JavaScript", () => {
|
|
57
|
+
const jsRule = rules.find(
|
|
58
|
+
(rule) => rule.test && rule.test.toString().includes("js|jsx|mjs")
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
expect(jsRule.exclude).toBeDefined()
|
|
62
|
+
expect(jsRule.exclude.toString()).toContain("node_modules")
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("sets type to javascript/auto for JavaScript", () => {
|
|
66
|
+
const jsRule = rules.find(
|
|
67
|
+
(rule) => rule.test && rule.test.toString().includes("js|jsx|mjs")
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
expect(jsRule.type).toBe("javascript/auto")
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("configures SWC with JSX runtime automatic", () => {
|
|
74
|
+
const jsRule = rules.find(
|
|
75
|
+
(rule) => rule.test && rule.test.toString().includes("js|jsx|mjs")
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
expect(jsRule.use[0].options).toBeDefined()
|
|
79
|
+
expect(jsRule.use[0].options.jsc.parser.syntax).toBe("ecmascript")
|
|
80
|
+
expect(jsRule.use[0].options.jsc.parser.jsx).toBe(true)
|
|
81
|
+
expect(jsRule.use[0].options.jsc.transform.react.runtime).toBe(
|
|
82
|
+
"automatic"
|
|
83
|
+
)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe("typeScript rule", () => {
|
|
88
|
+
test("includes rule for TS/TSX files", () => {
|
|
89
|
+
const tsRule = rules.find(
|
|
90
|
+
(rule) => rule.test && rule.test.toString().includes("ts|tsx")
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
expect(tsRule).toBeDefined()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test("uses builtin:swc-loader for TypeScript", () => {
|
|
97
|
+
const tsRule = rules.find(
|
|
98
|
+
(rule) => rule.test && rule.test.toString().includes("ts|tsx")
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
expect(tsRule.use).toBeDefined()
|
|
102
|
+
expect(Array.isArray(tsRule.use)).toBe(true)
|
|
103
|
+
expect(tsRule.use[0].loader).toBe("builtin:swc-loader")
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test("excludes node_modules for TypeScript", () => {
|
|
107
|
+
const tsRule = rules.find(
|
|
108
|
+
(rule) => rule.test && rule.test.toString().includes("ts|tsx")
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
expect(tsRule.exclude).toBeDefined()
|
|
112
|
+
expect(tsRule.exclude.toString()).toContain("node_modules")
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
test("sets type to javascript/auto for TypeScript", () => {
|
|
116
|
+
const tsRule = rules.find(
|
|
117
|
+
(rule) => rule.test && rule.test.toString().includes("ts|tsx")
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
expect(tsRule.type).toBe("javascript/auto")
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("configures SWC with TypeScript parser", () => {
|
|
124
|
+
const tsRule = rules.find(
|
|
125
|
+
(rule) => rule.test && rule.test.toString().includes("ts|tsx")
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
expect(tsRule.use[0].options).toBeDefined()
|
|
129
|
+
expect(tsRule.use[0].options.jsc.parser.syntax).toBe("typescript")
|
|
130
|
+
expect(tsRule.use[0].options.jsc.parser.tsx).toBe(true)
|
|
131
|
+
expect(tsRule.use[0].options.jsc.transform.react.runtime).toBe(
|
|
132
|
+
"automatic"
|
|
133
|
+
)
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe("css rules", () => {
|
|
138
|
+
test("includes CSS rule when css-loader is available", () => {
|
|
139
|
+
const cssRule = rules.find(
|
|
140
|
+
(rule) => rule.test && rule.test.toString().includes("css")
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
// CSS rule should be present since moduleExists is mocked to return true
|
|
144
|
+
expect(cssRule).toBeDefined()
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe("sass rules", () => {
|
|
149
|
+
test("includes Sass rule when dependencies are available", () => {
|
|
150
|
+
const sassRule = rules.find(
|
|
151
|
+
(rule) => rule.test && rule.test.toString().includes("scss|sass")
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
// Sass rule should be present since moduleExists is mocked to return true
|
|
155
|
+
expect(sassRule).toBeDefined()
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
describe("less rules", () => {
|
|
160
|
+
test("includes Less rule when less-loader is available", () => {
|
|
161
|
+
const lessRule = rules.find(
|
|
162
|
+
(rule) => rule.test && rule.test.toString().includes("less")
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
expect(lessRule).toBeDefined()
|
|
166
|
+
expect(lessRule.test.toString()).toContain("less")
|
|
167
|
+
})
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
describe("stylus rules", () => {
|
|
171
|
+
test("includes Stylus rule when stylus-loader is available", () => {
|
|
172
|
+
const stylusRule = rules.find(
|
|
173
|
+
(rule) => rule.test && rule.test.toString().includes("styl")
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
expect(stylusRule).toBeDefined()
|
|
177
|
+
expect(stylusRule.test.toString()).toContain("styl")
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
describe("erb rule", () => {
|
|
182
|
+
test("includes ERB rule", () => {
|
|
183
|
+
const erbRule = rules.find(
|
|
184
|
+
(rule) => rule.test && rule.test.toString().includes("erb")
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
expect(erbRule).toBeDefined()
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
describe("file/asset rule", () => {
|
|
192
|
+
test("includes file/asset handling rule", () => {
|
|
193
|
+
const fileRule = rules.find(
|
|
194
|
+
(rule) =>
|
|
195
|
+
rule.test &&
|
|
196
|
+
(rule.test.toString().includes("png") ||
|
|
197
|
+
rule.test.toString().includes("jpg") ||
|
|
198
|
+
rule.test.toString().includes("svg"))
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
expect(fileRule).toBeDefined()
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
describe("raw file rule", () => {
|
|
206
|
+
test("includes raw file loading rule", () => {
|
|
207
|
+
// Raw rule may be a direct rule or nested in oneOf
|
|
208
|
+
const rawRule =
|
|
209
|
+
rules.find(
|
|
210
|
+
(rule) =>
|
|
211
|
+
rule.type === "asset/source" &&
|
|
212
|
+
rule.resourceQuery &&
|
|
213
|
+
rule.resourceQuery.toString().includes("raw")
|
|
214
|
+
) ||
|
|
215
|
+
rules
|
|
216
|
+
.filter((rule) => rule.oneOf)
|
|
217
|
+
.flatMap((rule) => rule.oneOf)
|
|
218
|
+
.find(
|
|
219
|
+
(subRule) =>
|
|
220
|
+
subRule.type === "asset/source" &&
|
|
221
|
+
subRule.resourceQuery &&
|
|
222
|
+
subRule.resourceQuery.toString().includes("raw")
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
expect(rawRule).toBeDefined()
|
|
226
|
+
expect(rawRule.type).toBe("asset/source")
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
})
|
data/test/resolver.js
CHANGED
|
@@ -1,13 +1,44 @@
|
|
|
1
|
+
const { resolve } = require("path")
|
|
2
|
+
|
|
1
3
|
const mapping = {
|
|
2
4
|
"css-loader": "this path was mocked",
|
|
3
5
|
"sass-loader/package.json": "../../__mocks__/sass-loader/package.json",
|
|
4
6
|
"nonexistent/package.json": "../../__mocks__/nonexistent/package.json"
|
|
5
7
|
}
|
|
6
8
|
|
|
9
|
+
const repoRoot = resolve(__dirname, "..")
|
|
10
|
+
// Keep this map explicit to avoid accidentally rewriting third-party imports.
|
|
11
|
+
// If a new local rspack TS module is required via its compiled .js path in tests,
|
|
12
|
+
// add the corresponding mapping here.
|
|
13
|
+
const rspackModuleAliasMap = {
|
|
14
|
+
[resolve(repoRoot, "package/plugins/rspack.js")]: resolve(
|
|
15
|
+
repoRoot,
|
|
16
|
+
"package/plugins/rspack.ts"
|
|
17
|
+
),
|
|
18
|
+
[resolve(repoRoot, "package/rules/rspack.js")]: resolve(
|
|
19
|
+
repoRoot,
|
|
20
|
+
"package/rules/rspack.ts"
|
|
21
|
+
),
|
|
22
|
+
[resolve(repoRoot, "package/optimization/rspack.js")]: resolve(
|
|
23
|
+
repoRoot,
|
|
24
|
+
"package/optimization/rspack.ts"
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
7
28
|
function resolver(module, options) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
29
|
+
if (mapping[module]) {
|
|
30
|
+
return mapping[module]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Remap only this repository's known rspack JS targets to TS sources.
|
|
34
|
+
if (options.basedir) {
|
|
35
|
+
const requestedPath = resolve(options.basedir, module)
|
|
36
|
+
if (rspackModuleAliasMap[requestedPath]) {
|
|
37
|
+
return rspackModuleAliasMap[requestedPath]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return options.defaultResolver(module, options)
|
|
11
42
|
}
|
|
12
43
|
|
|
13
44
|
module.exports = resolver
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: shakapacker
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 9.6.0.rc.
|
|
4
|
+
version: 9.6.0.rc.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
@@ -390,6 +390,10 @@ files:
|
|
|
390
390
|
- test/package/index.test.js
|
|
391
391
|
- test/package/plugins/envFiltering.test.js
|
|
392
392
|
- test/package/production.test.js
|
|
393
|
+
- test/package/rspack/index.test.js
|
|
394
|
+
- test/package/rspack/optimization.test.js
|
|
395
|
+
- test/package/rspack/plugins.test.js
|
|
396
|
+
- test/package/rspack/rules.test.js
|
|
393
397
|
- test/package/rules/babel.test.js
|
|
394
398
|
- test/package/rules/esbuild.test.js
|
|
395
399
|
- test/package/rules/file.test.js
|
|
@@ -422,7 +426,7 @@ homepage: https://github.com/shakacode/shakapacker
|
|
|
422
426
|
licenses:
|
|
423
427
|
- MIT
|
|
424
428
|
metadata:
|
|
425
|
-
source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.6.0-rc.
|
|
429
|
+
source_code_uri: https://github.com/shakacode/shakapacker/tree/v9.6.0-rc.1
|
|
426
430
|
rdoc_options: []
|
|
427
431
|
require_paths:
|
|
428
432
|
- lib
|
|
@@ -461,6 +465,10 @@ test_files:
|
|
|
461
465
|
- test/package/index.test.js
|
|
462
466
|
- test/package/plugins/envFiltering.test.js
|
|
463
467
|
- test/package/production.test.js
|
|
468
|
+
- test/package/rspack/index.test.js
|
|
469
|
+
- test/package/rspack/optimization.test.js
|
|
470
|
+
- test/package/rspack/plugins.test.js
|
|
471
|
+
- test/package/rspack/rules.test.js
|
|
464
472
|
- test/package/rules/babel.test.js
|
|
465
473
|
- test/package/rules/esbuild.test.js
|
|
466
474
|
- test/package/rules/file.test.js
|