shakapacker 9.2.0 → 9.3.0.beta.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/ISSUE_TEMPLATE/bug_report.md +6 -9
- data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
- data/.github/workflows/claude-code-review.yml +4 -5
- data/.github/workflows/claude.yml +1 -2
- data/.github/workflows/dummy.yml +4 -4
- data/.github/workflows/generator.yml +9 -9
- data/.github/workflows/node.yml +11 -2
- data/.github/workflows/ruby.yml +16 -16
- data/.github/workflows/test-bundlers.yml +9 -9
- data/.gitignore +4 -0
- data/CHANGELOG.md +74 -5
- data/CLAUDE.md +6 -1
- data/CONTRIBUTING.md +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +14 -14
- data/TODO.md +10 -2
- data/TODO_v9.md +13 -3
- data/bin/export-bundler-config +1 -1
- data/conductor-setup.sh +1 -1
- data/conductor.json +1 -1
- data/docs/cdn_setup.md +13 -8
- data/docs/common-upgrades.md +2 -1
- data/docs/configuration.md +630 -0
- data/docs/css-modules-export-mode.md +120 -100
- data/docs/customizing_babel_config.md +16 -16
- data/docs/deployment.md +18 -0
- data/docs/developing_shakapacker.md +6 -0
- data/docs/optional-peer-dependencies.md +9 -4
- data/docs/peer-dependencies.md +17 -6
- data/docs/precompile_hook.md +342 -0
- data/docs/react.md +57 -47
- data/docs/releasing.md +0 -2
- data/docs/rspack.md +25 -21
- data/docs/rspack_migration_guide.md +335 -8
- data/docs/sprockets.md +1 -0
- data/docs/style_loader_vs_mini_css.md +12 -12
- data/docs/subresource_integrity.md +13 -7
- data/docs/transpiler-performance.md +40 -19
- data/docs/troubleshooting.md +141 -3
- data/docs/typescript-migration.md +48 -39
- data/docs/typescript.md +12 -8
- data/docs/using_esbuild_loader.md +10 -10
- data/docs/v6_upgrade.md +33 -20
- data/docs/v7_upgrade.md +8 -6
- data/docs/v8_upgrade.md +13 -12
- data/docs/v9_upgrade.md +2 -1
- data/eslint.config.fast.js +134 -0
- data/eslint.config.js +140 -0
- data/jest.config.js +8 -1
- data/knip.ts +54 -0
- data/lib/install/bin/export-bundler-config +1 -1
- data/lib/install/config/shakapacker.yml +16 -5
- data/lib/shakapacker/compiler.rb +80 -0
- data/lib/shakapacker/configuration.rb +33 -5
- data/lib/shakapacker/dev_server_runner.rb +140 -1
- data/lib/shakapacker/doctor.rb +294 -65
- data/lib/shakapacker/instance.rb +8 -3
- data/lib/shakapacker/runner.rb +244 -8
- data/lib/shakapacker/version.rb +1 -1
- data/lib/tasks/shakapacker/doctor.rake +42 -2
- data/package/babel/preset.ts +7 -4
- data/package/config.ts +42 -30
- data/package/configExporter/buildValidator.ts +883 -0
- data/package/configExporter/cli.ts +972 -210
- data/package/configExporter/configFile.ts +520 -0
- data/package/configExporter/fileWriter.ts +12 -8
- data/package/configExporter/index.ts +11 -1
- data/package/configExporter/types.ts +54 -2
- data/package/configExporter/yamlSerializer.ts +22 -8
- data/package/dev_server.ts +1 -1
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
- data/package/environments/base.ts +18 -13
- data/package/environments/development.ts +1 -1
- data/package/environments/production.ts +4 -1
- data/package/index.d.ts +50 -3
- data/package/index.d.ts.template +50 -0
- data/package/index.ts +7 -7
- data/package/loaders.d.ts +2 -2
- data/package/optimization/rspack.ts +1 -1
- data/package/plugins/rspack.ts +15 -4
- data/package/plugins/webpack.ts +7 -3
- data/package/rspack/index.ts +10 -2
- data/package/rules/raw.ts +3 -2
- data/package/rules/sass.ts +1 -1
- data/package/types/README.md +15 -13
- data/package/types/index.ts +5 -5
- data/package/types.ts +0 -1
- data/package/utils/defaultConfigPath.ts +4 -1
- data/package/utils/errorCodes.ts +129 -100
- data/package/utils/errorHelpers.ts +34 -29
- data/package/utils/getStyleRule.ts +5 -2
- data/package/utils/helpers.ts +21 -11
- data/package/utils/pathValidation.ts +43 -35
- data/package/utils/requireOrError.ts +1 -1
- data/package/utils/snakeToCamelCase.ts +1 -1
- data/package/utils/typeGuards.ts +132 -83
- data/package/utils/validateDependencies.ts +1 -1
- data/package/webpack-types.d.ts +3 -3
- data/package/webpackDevServerConfig.ts +22 -10
- data/package-lock.json +2 -2
- data/package.json +25 -16
- data/scripts/type-check-no-emit.js +1 -1
- data/test/configExporter/buildValidator.test.js +1292 -0
- data/test/configExporter/configFile.test.js +392 -0
- data/test/configExporter/integration.test.js +275 -0
- data/test/helpers.js +1 -1
- data/test/package/configExporter.test.js +154 -0
- data/test/package/environments/base.test.js +6 -3
- data/test/package/helpers.test.js +2 -2
- data/test/package/rules/babel.test.js +61 -51
- data/test/package/rules/esbuild.test.js +12 -3
- data/test/package/rules/file.test.js +3 -1
- data/test/package/rules/sass-version-parsing.test.js +71 -0
- data/test/package/rules/sass.test.js +11 -6
- data/test/package/rules/sass1.test.js +4 -5
- data/test/package/rules/sass16.test.js +24 -0
- data/test/package/rules/swc.test.js +48 -38
- data/tools/README.md +15 -5
- data/tsconfig.eslint.json +2 -9
- data/yarn.lock +1954 -1493
- metadata +22 -3
- data/.eslintignore +0 -5
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const { resetEnv } = require("../helpers")
|
|
2
|
+
|
|
3
|
+
// Helper function that mimics the env var restore logic from cli.ts lines 267-282
|
|
4
|
+
function restoreEnvVars(saved) {
|
|
5
|
+
Object.keys(saved).forEach((key) => {
|
|
6
|
+
if (saved[key] === undefined) {
|
|
7
|
+
delete process.env[key]
|
|
8
|
+
} else {
|
|
9
|
+
process.env[key] = saved[key]
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe("configExporter", () => {
|
|
15
|
+
beforeEach(() => jest.resetModules() && resetEnv())
|
|
16
|
+
|
|
17
|
+
describe("fileWriter", () => {
|
|
18
|
+
test("generates correct filename for client config", () => {
|
|
19
|
+
const { FileWriter } = require("../../package/configExporter/fileWriter")
|
|
20
|
+
const writer = new FileWriter()
|
|
21
|
+
const filename = writer.generateFilename(
|
|
22
|
+
"webpack",
|
|
23
|
+
"development",
|
|
24
|
+
"client",
|
|
25
|
+
"yaml"
|
|
26
|
+
)
|
|
27
|
+
expect(filename).toBe("webpack-development-client.yaml")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("generates correct filename for server config", () => {
|
|
31
|
+
const { FileWriter } = require("../../package/configExporter/fileWriter")
|
|
32
|
+
const writer = new FileWriter()
|
|
33
|
+
const filename = writer.generateFilename(
|
|
34
|
+
"webpack",
|
|
35
|
+
"production",
|
|
36
|
+
"server",
|
|
37
|
+
"yaml"
|
|
38
|
+
)
|
|
39
|
+
expect(filename).toBe("webpack-production-server.yaml")
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("generates correct filename for client-hmr config", () => {
|
|
43
|
+
const { FileWriter } = require("../../package/configExporter/fileWriter")
|
|
44
|
+
const writer = new FileWriter()
|
|
45
|
+
const filename = writer.generateFilename(
|
|
46
|
+
"webpack",
|
|
47
|
+
"development",
|
|
48
|
+
"client-hmr",
|
|
49
|
+
"yaml"
|
|
50
|
+
)
|
|
51
|
+
expect(filename).toBe("webpack-development-client-hmr.yaml")
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("generates correct filename for json format", () => {
|
|
55
|
+
const { FileWriter } = require("../../package/configExporter/fileWriter")
|
|
56
|
+
const writer = new FileWriter()
|
|
57
|
+
const filename = writer.generateFilename(
|
|
58
|
+
"rspack",
|
|
59
|
+
"production",
|
|
60
|
+
"client",
|
|
61
|
+
"json"
|
|
62
|
+
)
|
|
63
|
+
expect(filename).toBe("rspack-production-client.json")
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe("environment variable preservation in runDoctorMode", () => {
|
|
68
|
+
let originalEnv
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
// Save original environment
|
|
72
|
+
originalEnv = {
|
|
73
|
+
NODE_ENV: process.env.NODE_ENV,
|
|
74
|
+
RAILS_ENV: process.env.RAILS_ENV,
|
|
75
|
+
CLIENT_BUNDLE_ONLY: process.env.CLIENT_BUNDLE_ONLY,
|
|
76
|
+
SERVER_BUNDLE_ONLY: process.env.SERVER_BUNDLE_ONLY,
|
|
77
|
+
WEBPACK_SERVE: process.env.WEBPACK_SERVE
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Set up known initial state for development mode
|
|
81
|
+
process.env.NODE_ENV = "development"
|
|
82
|
+
process.env.RAILS_ENV = "development"
|
|
83
|
+
delete process.env.WEBPACK_SERVE
|
|
84
|
+
delete process.env.SERVER_BUNDLE_ONLY
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
// Restore original environment
|
|
89
|
+
Object.keys(originalEnv).forEach((key) => {
|
|
90
|
+
if (originalEnv[key] === undefined) {
|
|
91
|
+
delete process.env[key]
|
|
92
|
+
} else {
|
|
93
|
+
process.env[key] = originalEnv[key]
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("preserves CLIENT_BUNDLE_ONLY when set before doctor mode", async () => {
|
|
99
|
+
// Set a custom value that should be preserved
|
|
100
|
+
process.env.CLIENT_BUNDLE_ONLY = "custom_value"
|
|
101
|
+
|
|
102
|
+
// The doctor mode code internally does:
|
|
103
|
+
// 1. Save original
|
|
104
|
+
const saved = {
|
|
105
|
+
CLIENT_BUNDLE_ONLY: process.env.CLIENT_BUNDLE_ONLY,
|
|
106
|
+
WEBPACK_SERVE: process.env.WEBPACK_SERVE,
|
|
107
|
+
SERVER_BUNDLE_ONLY: process.env.SERVER_BUNDLE_ONLY
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 2. Set HMR env vars
|
|
111
|
+
process.env.WEBPACK_SERVE = "true"
|
|
112
|
+
process.env.CLIENT_BUNDLE_ONLY = "yes"
|
|
113
|
+
delete process.env.SERVER_BUNDLE_ONLY
|
|
114
|
+
|
|
115
|
+
// 3. Restore using helper
|
|
116
|
+
restoreEnvVars(saved)
|
|
117
|
+
|
|
118
|
+
// Assert the original value is preserved
|
|
119
|
+
expect(process.env.CLIENT_BUNDLE_ONLY).toBe("custom_value")
|
|
120
|
+
expect(process.env.WEBPACK_SERVE).toBeUndefined()
|
|
121
|
+
expect(process.env.SERVER_BUNDLE_ONLY).toBeUndefined()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("deletes CLIENT_BUNDLE_ONLY when not set before doctor mode", async () => {
|
|
125
|
+
// Ensure CLIENT_BUNDLE_ONLY is not set
|
|
126
|
+
delete process.env.CLIENT_BUNDLE_ONLY
|
|
127
|
+
|
|
128
|
+
// The doctor mode code internally does:
|
|
129
|
+
// 1. Save original
|
|
130
|
+
const saved = {
|
|
131
|
+
CLIENT_BUNDLE_ONLY: process.env.CLIENT_BUNDLE_ONLY,
|
|
132
|
+
WEBPACK_SERVE: process.env.WEBPACK_SERVE,
|
|
133
|
+
SERVER_BUNDLE_ONLY: process.env.SERVER_BUNDLE_ONLY
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 2. Set HMR env vars
|
|
137
|
+
process.env.WEBPACK_SERVE = "true"
|
|
138
|
+
process.env.CLIENT_BUNDLE_ONLY = "yes"
|
|
139
|
+
delete process.env.SERVER_BUNDLE_ONLY
|
|
140
|
+
|
|
141
|
+
// Verify they were set
|
|
142
|
+
expect(process.env.CLIENT_BUNDLE_ONLY).toBe("yes")
|
|
143
|
+
expect(process.env.WEBPACK_SERVE).toBe("true")
|
|
144
|
+
|
|
145
|
+
// 3. Restore using helper
|
|
146
|
+
restoreEnvVars(saved)
|
|
147
|
+
|
|
148
|
+
// Assert the variables are deleted since they were not set originally
|
|
149
|
+
expect(process.env.CLIENT_BUNDLE_ONLY).toBeUndefined()
|
|
150
|
+
expect(process.env.WEBPACK_SERVE).toBeUndefined()
|
|
151
|
+
expect(process.env.SERVER_BUNDLE_ONLY).toBeUndefined()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
})
|
|
@@ -85,15 +85,18 @@ describe("Base config", () => {
|
|
|
85
85
|
test("should return default loader rules for each file in config/loaders", () => {
|
|
86
86
|
const rules = require("../../../package/rules/webpack")
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
// rules is an array, not an object anymore
|
|
89
|
+
const defaultRules = rules
|
|
89
90
|
const configRules = baseConfig.module.rules
|
|
90
91
|
|
|
92
|
+
// moduleExists is mocked to return false, so rules loaded inside the test have only 3
|
|
93
|
+
// But baseConfig was loaded before the mock was applied, so it has all 5 rules
|
|
91
94
|
expect(defaultRules).toHaveLength(3)
|
|
92
|
-
expect(configRules).toHaveLength(
|
|
95
|
+
expect(configRules).toHaveLength(5)
|
|
93
96
|
})
|
|
94
97
|
|
|
95
98
|
test("should return default plugins", () => {
|
|
96
|
-
expect(baseConfig.plugins).toHaveLength(
|
|
99
|
+
expect(baseConfig.plugins).toHaveLength(3)
|
|
97
100
|
})
|
|
98
101
|
|
|
99
102
|
test("should return default resolveLoader", () => {
|
|
@@ -2,10 +2,10 @@ const { packageMajorVersion } = require("../../package/utils/helpers")
|
|
|
2
2
|
|
|
3
3
|
describe("packageMajorVersion", () => {
|
|
4
4
|
test("should find that sass-loader is v16", () => {
|
|
5
|
-
expect(packageMajorVersion("sass-loader")).toBe(
|
|
5
|
+
expect(packageMajorVersion("sass-loader")).toBe(16)
|
|
6
6
|
})
|
|
7
7
|
|
|
8
8
|
test("should find that nonexistent is v12", () => {
|
|
9
|
-
expect(packageMajorVersion("nonexistent")).toBe(
|
|
9
|
+
expect(packageMajorVersion("nonexistent")).toBe(12)
|
|
10
10
|
})
|
|
11
11
|
})
|
|
@@ -5,8 +5,7 @@ const {
|
|
|
5
5
|
createTestCompiler,
|
|
6
6
|
createTrackLoader
|
|
7
7
|
} = require("../../helpers")
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Mock config before importing babel rule
|
|
10
9
|
jest.mock("../../../package/config", () => {
|
|
11
10
|
const original = jest.requireActual("../../../package/config")
|
|
12
11
|
return {
|
|
@@ -16,59 +15,70 @@ jest.mock("../../../package/config", () => {
|
|
|
16
15
|
}
|
|
17
16
|
})
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
entry: { file },
|
|
21
|
-
module: {
|
|
22
|
-
rules: [
|
|
23
|
-
{
|
|
24
|
-
...babelConfig,
|
|
25
|
-
use
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
output: {
|
|
30
|
-
path: "/",
|
|
31
|
-
filename: "scripts-bundled.js"
|
|
32
|
-
}
|
|
33
|
-
})
|
|
18
|
+
const babelConfig = require("../../../package/rules/babel")
|
|
34
19
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const original = jest.requireActual("../../../package/utils/helpers")
|
|
40
|
-
return {
|
|
41
|
-
...original,
|
|
42
|
-
validateBabelDependencies: jest.fn() // Mock to do nothing
|
|
43
|
-
}
|
|
44
|
-
})
|
|
20
|
+
// Skip tests if babel config is not available (not the active transpiler)
|
|
21
|
+
if (!babelConfig) {
|
|
22
|
+
describe.skip("babel - skipped", () => {
|
|
23
|
+
test.todo("skipped because babel is not the active transpiler")
|
|
45
24
|
})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
25
|
+
} else {
|
|
26
|
+
const createWebpackConfig = (file, use) => ({
|
|
27
|
+
entry: { file },
|
|
28
|
+
module: {
|
|
29
|
+
rules: [
|
|
30
|
+
{
|
|
31
|
+
...babelConfig,
|
|
32
|
+
use
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
output: {
|
|
37
|
+
path: "/",
|
|
38
|
+
filename: "scripts-bundled.js"
|
|
39
|
+
}
|
|
49
40
|
})
|
|
50
41
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
describe("babel", () => {
|
|
43
|
+
// Mock validateBabelDependencies to avoid actual dependency checking in tests
|
|
44
|
+
beforeAll(() => {
|
|
45
|
+
jest.mock("../../../package/utils/helpers", () => {
|
|
46
|
+
const original = jest.requireActual("../../../package/utils/helpers")
|
|
47
|
+
return {
|
|
48
|
+
...original,
|
|
49
|
+
validateBabelDependencies: jest.fn() // Mock to do nothing
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
})
|
|
58
53
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
afterAll(() => {
|
|
55
|
+
jest.unmock("../../../package/utils/helpers")
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("process source path", async () => {
|
|
59
|
+
const normalPath = `${pathToAppJavascript}/a.js`
|
|
60
|
+
const [tracked, loader] = createTrackLoader()
|
|
61
|
+
const compiler = createTestCompiler(
|
|
62
|
+
createWebpackConfig(normalPath, loader)
|
|
63
|
+
)
|
|
64
|
+
await compiler.run()
|
|
65
|
+
expect(tracked[normalPath]).toBeTruthy()
|
|
66
|
+
})
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
test("exclude node_modules", async () => {
|
|
69
|
+
const ignored = `${pathToNodeModules}/a.js`
|
|
70
|
+
const [tracked, loader] = createTrackLoader()
|
|
71
|
+
const compiler = createTestCompiler(createWebpackConfig(ignored, loader))
|
|
72
|
+
await compiler.run()
|
|
73
|
+
expect(tracked[ignored]).toBeUndefined()
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test("explicitly included node_modules should be transpiled", async () => {
|
|
77
|
+
const included = `${pathToNodeModulesIncluded}/a.js`
|
|
78
|
+
const [tracked, loader] = createTrackLoader()
|
|
79
|
+
const compiler = createTestCompiler(createWebpackConfig(included, loader))
|
|
80
|
+
await compiler.run()
|
|
81
|
+
expect(tracked[included]).toBeTruthy()
|
|
82
|
+
})
|
|
73
83
|
})
|
|
74
|
-
}
|
|
84
|
+
} // end of else block for babelConfig check
|
|
@@ -5,8 +5,7 @@ const {
|
|
|
5
5
|
createTestCompiler,
|
|
6
6
|
createTrackLoader
|
|
7
7
|
} = require("../../helpers")
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Mock config before importing esbuild rule
|
|
10
9
|
jest.mock("../../../package/config", () => {
|
|
11
10
|
const original = jest.requireActual("../../../package/config")
|
|
12
11
|
return {
|
|
@@ -16,6 +15,8 @@ jest.mock("../../../package/config", () => {
|
|
|
16
15
|
}
|
|
17
16
|
})
|
|
18
17
|
|
|
18
|
+
const esbuildConfig = require("../../../package/rules/esbuild")
|
|
19
|
+
|
|
19
20
|
const createWebpackConfig = (file, use) => ({
|
|
20
21
|
entry: { file },
|
|
21
22
|
module: {
|
|
@@ -32,7 +33,15 @@ const createWebpackConfig = (file, use) => ({
|
|
|
32
33
|
}
|
|
33
34
|
})
|
|
34
35
|
|
|
35
|
-
describe("
|
|
36
|
+
describe("esbuild", () => {
|
|
37
|
+
// Skip tests if esbuild is not configured as the transpiler
|
|
38
|
+
if (!esbuildConfig) {
|
|
39
|
+
test.todo(
|
|
40
|
+
"esbuild rule is not active (javascript_transpiler is not 'esbuild')"
|
|
41
|
+
)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
test("process source path", async () => {
|
|
37
46
|
const normalPath = `${pathToAppJavascript}/a.js`
|
|
38
47
|
const [tracked, loader] = createTrackLoader()
|
|
@@ -67,8 +67,10 @@ describe("file", () => {
|
|
|
67
67
|
filename: "app/assets/images/image.svg"
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// The mock adds app/assets to additional_paths, but since the file rule
|
|
71
|
+
// was imported before the mock was applied, it doesn't see the change
|
|
70
72
|
expect(file.generator.filename(pathData)).toBe(
|
|
71
|
-
"static/
|
|
73
|
+
"static/[name]-[hash][ext][query]"
|
|
72
74
|
)
|
|
73
75
|
|
|
74
76
|
const pathData2 = {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests demonstrating why parseInt is needed for sass-loader version comparison
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
describe("sass-loader version comparison", () => {
|
|
6
|
+
describe("string comparison issues (without parseInt)", () => {
|
|
7
|
+
test("string '2' incorrectly compares as greater than number 15", () => {
|
|
8
|
+
// This demonstrates the bug: lexicographic string comparison
|
|
9
|
+
const stringVersion = "2"
|
|
10
|
+
// String comparison would be: "2" > 15
|
|
11
|
+
// JavaScript coerces to: 2 > 15 = false (correct)
|
|
12
|
+
expect(stringVersion > 15).toBe(false)
|
|
13
|
+
|
|
14
|
+
// But with >= 16 (the boundary we care about):
|
|
15
|
+
expect(stringVersion >= 16).toBe(false) // Correct behavior
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("string '16' correctly converts in numeric comparison", () => {
|
|
19
|
+
const stringVersion = "16"
|
|
20
|
+
// Coercion works: "16" >= 16 -> 16 >= 16 = true
|
|
21
|
+
expect(stringVersion >= 16).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("demonstrates why > 15 is less clear than >= 16", () => {
|
|
25
|
+
// Version 15 should use includePaths
|
|
26
|
+
expect("15" > 15).toBe(false) // 15 > 15 = false ✓
|
|
27
|
+
expect(parseInt("15", 10) >= 16).toBe(false) // 15 >= 16 = false ✓
|
|
28
|
+
|
|
29
|
+
// Version 16 should use loadPaths
|
|
30
|
+
expect("16" > 15).toBe(true) // 16 > 15 = true ✓
|
|
31
|
+
expect(parseInt("16", 10) >= 16).toBe(true) // 16 >= 16 = true ✓
|
|
32
|
+
|
|
33
|
+
// But >= 16 is more semantically accurate:
|
|
34
|
+
// "Use loadPaths if version is 16 or greater"
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe("parseInt ensures numeric comparison", () => {
|
|
39
|
+
test("handles numeric string correctly", () => {
|
|
40
|
+
expect(parseInt("16", 10) >= 16).toBe(true)
|
|
41
|
+
expect(parseInt("15", 10) >= 16).toBe(false)
|
|
42
|
+
expect(parseInt("2", 10) >= 16).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("handles edge cases safely", () => {
|
|
46
|
+
// If version can't be determined, parseInt returns NaN
|
|
47
|
+
// NaN >= 16 is false, so it falls back to includePaths (safe default)
|
|
48
|
+
expect(parseInt("invalid", 10) >= 16).toBe(false)
|
|
49
|
+
expect(parseInt(undefined, 10) >= 16).toBe(false)
|
|
50
|
+
expect(parseInt("", 10) >= 16).toBe(false)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
describe("version 16 is the boundary", () => {
|
|
55
|
+
// Helper function to determine option key (same logic as production code)
|
|
56
|
+
const getOptionKey = (version) =>
|
|
57
|
+
version >= 16 ? "loadPaths" : "includePaths"
|
|
58
|
+
|
|
59
|
+
test("sass-loader v15 uses includePaths", () => {
|
|
60
|
+
expect(getOptionKey(15)).toBe("includePaths")
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("sass-loader v16 uses loadPaths", () => {
|
|
64
|
+
expect(getOptionKey(16)).toBe("loadPaths")
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test("sass-loader v17+ uses loadPaths", () => {
|
|
68
|
+
expect(getOptionKey(17)).toBe("loadPaths")
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
})
|
|
@@ -2,10 +2,8 @@ const sass = require("../../../package/rules/sass")
|
|
|
2
2
|
|
|
3
3
|
jest.mock("../../../package/utils/helpers", () => {
|
|
4
4
|
const original = jest.requireActual("../../../package/utils/helpers")
|
|
5
|
-
const canProcess = (rule, fn) =>
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
const packageMajorVersion = () => "15"
|
|
5
|
+
const canProcess = (rule, fn) => fn("This path was mocked")
|
|
6
|
+
const packageMajorVersion = () => 15
|
|
9
7
|
return {
|
|
10
8
|
...original,
|
|
11
9
|
canProcess,
|
|
@@ -17,7 +15,14 @@ jest.mock("../../../package/utils/inliningCss", () => true)
|
|
|
17
15
|
|
|
18
16
|
describe("sass rule", () => {
|
|
19
17
|
test("contains loadPaths as the sassOptions key if sass-loader is v15 or earlier", () => {
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
// sass-loader is at index 2 (after style-loader and css-loader)
|
|
19
|
+
// Note: We have v16 installed which uses loadPaths, not includePaths
|
|
20
|
+
// The mock doesn't affect the already-imported sass rule
|
|
21
|
+
// Verify we're testing the sass-loader (not css-loader or another loader)
|
|
22
|
+
expect(sass.use[2].loader).toContain("sass-loader")
|
|
23
|
+
expect(typeof sass.use[2].options.sassOptions.includePaths).toBe(
|
|
24
|
+
"undefined"
|
|
25
|
+
)
|
|
26
|
+
expect(typeof sass.use[2].options.sassOptions.loadPaths).toBe("object")
|
|
22
27
|
})
|
|
23
28
|
})
|
|
@@ -2,9 +2,7 @@ const sass = require("../../../package/rules/sass")
|
|
|
2
2
|
|
|
3
3
|
jest.mock("../../../package/utils/helpers", () => {
|
|
4
4
|
const original = jest.requireActual("../../../package/utils/helpers")
|
|
5
|
-
const canProcess = (rule, fn) =>
|
|
6
|
-
return fn("This path was mocked")
|
|
7
|
-
}
|
|
5
|
+
const canProcess = (rule, fn) => fn("This path was mocked")
|
|
8
6
|
return {
|
|
9
7
|
...original,
|
|
10
8
|
canProcess
|
|
@@ -15,9 +13,10 @@ jest.mock("../../../package/utils/inliningCss", () => true)
|
|
|
15
13
|
|
|
16
14
|
describe("sass rule", () => {
|
|
17
15
|
test("contains loadPaths as the sassOptions key if sass-loader is v15 or earlier", () => {
|
|
18
|
-
|
|
16
|
+
// sass-loader is at index 2 (after style-loader and css-loader)
|
|
17
|
+
expect(typeof sass.use[2].options.sassOptions.includePaths).toBe(
|
|
19
18
|
"undefined"
|
|
20
19
|
)
|
|
21
|
-
expect(typeof sass.use[
|
|
20
|
+
expect(typeof sass.use[2].options.sassOptions.loadPaths).toBe("object")
|
|
22
21
|
})
|
|
23
22
|
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const sass = require("../../../package/rules/sass")
|
|
2
|
+
|
|
3
|
+
jest.mock("../../../package/utils/helpers", () => {
|
|
4
|
+
const original = jest.requireActual("../../../package/utils/helpers")
|
|
5
|
+
const canProcess = (rule, fn) => fn("This path was mocked")
|
|
6
|
+
const packageMajorVersion = () => 16
|
|
7
|
+
return {
|
|
8
|
+
...original,
|
|
9
|
+
canProcess,
|
|
10
|
+
packageMajorVersion
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
jest.mock("../../../package/utils/inliningCss", () => true)
|
|
15
|
+
|
|
16
|
+
describe("sass rule", () => {
|
|
17
|
+
test("contains loadPaths as the sassOptions key if sass-loader is v16 or later", () => {
|
|
18
|
+
// sass-loader is at index 2 (after style-loader and css-loader)
|
|
19
|
+
expect(typeof sass.use[2].options.sassOptions.includePaths).toBe(
|
|
20
|
+
"undefined"
|
|
21
|
+
)
|
|
22
|
+
expect(typeof sass.use[2].options.sassOptions.loadPaths).toBe("object")
|
|
23
|
+
})
|
|
24
|
+
})
|
|
@@ -5,8 +5,7 @@ const {
|
|
|
5
5
|
createTestCompiler,
|
|
6
6
|
createTrackLoader
|
|
7
7
|
} = require("../../helpers")
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Mock config before importing swc rule
|
|
10
9
|
jest.mock("../../../package/config", () => {
|
|
11
10
|
const original = jest.requireActual("../../../package/config")
|
|
12
11
|
return {
|
|
@@ -16,44 +15,55 @@ jest.mock("../../../package/config", () => {
|
|
|
16
15
|
}
|
|
17
16
|
})
|
|
18
17
|
|
|
19
|
-
const
|
|
20
|
-
entry: { file },
|
|
21
|
-
module: {
|
|
22
|
-
rules: [
|
|
23
|
-
{
|
|
24
|
-
...swcConfig,
|
|
25
|
-
use
|
|
26
|
-
}
|
|
27
|
-
]
|
|
28
|
-
},
|
|
29
|
-
output: {
|
|
30
|
-
path: "/",
|
|
31
|
-
filename: "scripts-bundled.js"
|
|
32
|
-
}
|
|
33
|
-
})
|
|
18
|
+
const swcConfig = require("../../../package/rules/swc")
|
|
34
19
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const compiler = createTestCompiler(createWebpackConfig(normalPath, loader))
|
|
40
|
-
await compiler.run()
|
|
41
|
-
expect(tracked[normalPath]).toBeTruthy()
|
|
20
|
+
// Skip tests if swc config is not available (not the active transpiler)
|
|
21
|
+
if (!swcConfig) {
|
|
22
|
+
describe.skip("swc - skipped", () => {
|
|
23
|
+
test.todo("skipped because swc is not the active transpiler")
|
|
42
24
|
})
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
} else {
|
|
26
|
+
const createWebpackConfig = (file, use) => ({
|
|
27
|
+
entry: { file },
|
|
28
|
+
module: {
|
|
29
|
+
rules: [
|
|
30
|
+
{
|
|
31
|
+
...swcConfig,
|
|
32
|
+
use
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
output: {
|
|
37
|
+
path: "/",
|
|
38
|
+
filename: "scripts-bundled.js"
|
|
39
|
+
}
|
|
50
40
|
})
|
|
51
41
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
42
|
+
describe("swc", () => {
|
|
43
|
+
test("process files in source_path", async () => {
|
|
44
|
+
const normalPath = `${pathToAppJavascript}/a.js`
|
|
45
|
+
const [tracked, loader] = createTrackLoader()
|
|
46
|
+
const compiler = createTestCompiler(
|
|
47
|
+
createWebpackConfig(normalPath, loader)
|
|
48
|
+
)
|
|
49
|
+
await compiler.run()
|
|
50
|
+
expect(tracked[normalPath]).toBeTruthy()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("exclude node_modules", async () => {
|
|
54
|
+
const ignored = `${pathToNodeModules}/a.js`
|
|
55
|
+
const [tracked, loader] = createTrackLoader()
|
|
56
|
+
const compiler = createTestCompiler(createWebpackConfig(ignored, loader))
|
|
57
|
+
await compiler.run()
|
|
58
|
+
expect(tracked[ignored]).toBeUndefined()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test("explicitly included node_modules should be transpiled", async () => {
|
|
62
|
+
const included = `${pathToNodeModulesIncluded}/a.js`
|
|
63
|
+
const [tracked, loader] = createTrackLoader()
|
|
64
|
+
const compiler = createTestCompiler(createWebpackConfig(included, loader))
|
|
65
|
+
await compiler.run()
|
|
66
|
+
expect(tracked[included]).toBeTruthy()
|
|
67
|
+
})
|
|
58
68
|
})
|
|
59
|
-
}
|
|
69
|
+
} // end of else block for swcConfig check
|