shakapacker 9.0.0.beta.7 → 9.0.0.beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.fast.js +40 -0
  3. data/.eslintrc.js +48 -0
  4. data/.gitignore +1 -3
  5. data/.npmignore +1 -0
  6. data/CHANGELOG.md +64 -1
  7. data/CONTRIBUTING.md +75 -21
  8. data/Gemfile.lock +1 -1
  9. data/TODO.md +15 -16
  10. data/lib/shakapacker/version.rb +1 -1
  11. data/package/babel/preset.ts +56 -0
  12. data/package/esbuild/index.ts +42 -0
  13. data/package/optimization/rspack.ts +36 -0
  14. data/package/optimization/{webpack.js → webpack.ts} +12 -4
  15. data/package/plugins/{rspack.js → rspack.ts} +20 -5
  16. data/package/plugins/{webpack.js → webpack.ts} +2 -2
  17. data/package/rspack/{index.js → index.ts} +17 -10
  18. data/package/rules/{babel.js → babel.ts} +1 -1
  19. data/package/rules/{coffee.js → coffee.ts} +1 -1
  20. data/package/rules/{css.js → css.ts} +1 -1
  21. data/package/rules/{erb.js → erb.ts} +1 -1
  22. data/package/rules/{esbuild.js → esbuild.ts} +2 -2
  23. data/package/rules/{file.js → file.ts} +11 -6
  24. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  25. data/package/rules/{less.js → less.ts} +3 -3
  26. data/package/rules/raw.ts +25 -0
  27. data/package/rules/{rspack.js → rspack.ts} +21 -11
  28. data/package/rules/{sass.js → sass.ts} +1 -1
  29. data/package/rules/{stylus.js → stylus.ts} +3 -7
  30. data/package/rules/{swc.js → swc.ts} +2 -2
  31. data/package/rules/{webpack.js → webpack.ts} +1 -1
  32. data/package/swc/index.ts +54 -0
  33. data/package.json +22 -2
  34. data/scripts/type-check-no-emit.js +27 -0
  35. data/test/package/rules/raw.test.js +40 -7
  36. data/test/package/rules/webpack.test.js +21 -2
  37. data/tsconfig.eslint.json +16 -0
  38. data/tsconfig.json +9 -10
  39. data/yarn.lock +415 -6
  40. metadata +27 -24
  41. data/package/babel/preset.js +0 -48
  42. data/package/esbuild/index.js +0 -40
  43. data/package/optimization/rspack.js +0 -29
  44. data/package/rules/raw.js +0 -15
  45. data/package/swc/index.js +0 -50
@@ -6,7 +6,22 @@ const config = require("../config")
6
6
  const { isProduction } = require("../env")
7
7
  const { moduleExists } = require("../utils/helpers")
8
8
 
9
- const getPlugins = () => {
9
+ interface ManifestFile {
10
+ name: string
11
+ path: string
12
+ }
13
+
14
+ interface EntrypointAssets {
15
+ js: string[]
16
+ css: string[]
17
+ }
18
+
19
+ interface Manifest {
20
+ entrypoints?: Record<string, { assets: EntrypointAssets }>
21
+ [key: string]: string | { assets: EntrypointAssets } | Record<string, { assets: EntrypointAssets }> | undefined
22
+ }
23
+
24
+ const getPlugins = (): unknown[] => {
10
25
  const plugins = [
11
26
  new rspack.EnvironmentPlugin(process.env),
12
27
  new RspackManifestPlugin({
@@ -14,8 +29,8 @@ const getPlugins = () => {
14
29
  publicPath: config.publicPathWithoutCDN,
15
30
  writeToFileEmit: true,
16
31
  // rspack-manifest-plugin uses different option names than webpack-assets-manifest
17
- generate: (seed, files, entrypoints) => {
18
- const manifest = seed || {}
32
+ generate: (seed: Manifest | null, files: ManifestFile[], entrypoints: Record<string, string[]>) => {
33
+ const manifest: Manifest = seed || {}
19
34
 
20
35
  // Add files mapping first
21
36
  files.forEach((file) => {
@@ -23,7 +38,7 @@ const getPlugins = () => {
23
38
  })
24
39
 
25
40
  // Add entrypoints information compatible with Shakapacker expectations
26
- const entrypointsManifest = {}
41
+ const entrypointsManifest: Record<string, { assets: EntrypointAssets }> = {}
27
42
  Object.entries(entrypoints).forEach(
28
43
  ([entrypointName, entrypointFiles]) => {
29
44
  const jsFiles = entrypointFiles
@@ -83,6 +98,6 @@ const getPlugins = () => {
83
98
  return plugins
84
99
  }
85
100
 
86
- module.exports = {
101
+ export = {
87
102
  getPlugins
88
103
  }
@@ -6,7 +6,7 @@ const config = require("../config")
6
6
  const { isProduction } = require("../env")
7
7
  const { moduleExists } = require("../utils/helpers")
8
8
 
9
- const getPlugins = () => {
9
+ const getPlugins = (): unknown[] => {
10
10
  // TODO: Remove WebpackAssetsManifestConstructor workaround when dropping 'webpack-assets-manifest < 6.0.0' (Node >=20.10.0) support
11
11
  const WebpackAssetsManifestConstructor =
12
12
  "WebpackAssetsManifest" in WebpackAssetsManifest
@@ -57,6 +57,6 @@ const getPlugins = () => {
57
57
  return plugins
58
58
  }
59
59
 
60
- module.exports = {
60
+ export = {
61
61
  getPlugins
62
62
  }
@@ -1,14 +1,15 @@
1
1
  /* eslint global-require: 0 */
2
2
  /* eslint import/no-dynamic-require: 0 */
3
3
 
4
+ // Mixed require/import syntax:
5
+ // - Using require() for compiled JS modules that may not have proper ES module exports
6
+ // - Using import for type-only imports and Node.js built-in modules
4
7
  const webpackMerge = require("webpack-merge")
5
- const { resolve } = require("path")
6
- const { existsSync } = require("fs")
8
+ import { resolve } from "path"
9
+ import { existsSync } from "fs"
10
+ import type { RspackConfigWithDevServer } from "../environments/types"
7
11
  const config = require("../config")
8
12
  const baseConfig = require("../environments/base")
9
-
10
- const rulesPath = resolve(__dirname, "../rules", "rspack.js")
11
- const rules = require(rulesPath)
12
13
  const devServer = require("../dev_server")
13
14
  const env = require("../env")
14
15
  const { moduleExists, canProcess } = require("../utils/helpers")
@@ -17,7 +18,10 @@ const { getPlugins } = require("../plugins/rspack")
17
18
  const { getOptimization } = require("../optimization/rspack")
18
19
  const { validateRspackDependencies } = require("../utils/validateDependencies")
19
20
 
20
- const generateRspackConfig = (extraConfig = {}, ...extraArgs) => {
21
+ const rulesPath = resolve(__dirname, "../rules", "rspack.js")
22
+ const rules = require(rulesPath)
23
+
24
+ const generateRspackConfig = (extraConfig: RspackConfigWithDevServer = {}, ...extraArgs: unknown[]): RspackConfigWithDevServer => {
21
25
  // Validate required dependencies first
22
26
  validateRspackDependencies()
23
27
  if (extraArgs.length > 0) {
@@ -28,10 +32,11 @@ const generateRspackConfig = (extraConfig = {}, ...extraArgs) => {
28
32
 
29
33
  const { nodeEnv } = env
30
34
  const path = resolve(__dirname, "../environments", `${nodeEnv}.js`)
35
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
31
36
  const environmentConfig = existsSync(path) ? require(path) : baseConfig
32
37
 
33
38
  // Create base rspack config
34
- const rspackConfig = {
39
+ const rspackConfig: RspackConfigWithDevServer = {
35
40
  ...environmentConfig,
36
41
  module: {
37
42
  rules
@@ -43,7 +48,10 @@ const generateRspackConfig = (extraConfig = {}, ...extraArgs) => {
43
48
  return webpackMerge.merge({}, rspackConfig, extraConfig)
44
49
  }
45
50
 
46
- module.exports = {
51
+ // Re-export webpack-merge utilities for backward compatibility
52
+ export { merge, mergeWithCustomize, mergeWithRules, unique } from "webpack-merge"
53
+
54
+ export {
47
55
  config, // shakapacker.yml
48
56
  devServer,
49
57
  generateRspackConfig,
@@ -52,6 +60,5 @@ module.exports = {
52
60
  rules,
53
61
  moduleExists,
54
62
  canProcess,
55
- inliningCss,
56
- ...webpackMerge
63
+ inliningCss
57
64
  }
@@ -3,7 +3,7 @@ const { javascript_transpiler: javascriptTranspiler } = require("../config")
3
3
  const { isProduction } = require("../env")
4
4
  const jscommon = require("./jscommon")
5
5
 
6
- module.exports = loaderMatches(javascriptTranspiler, "babel", () => ({
6
+ export = loaderMatches(javascriptTranspiler, "babel", () => ({
7
7
  test: /\.(js|jsx|mjs|ts|tsx|coffee)?(\.erb)?$/,
8
8
  ...jscommon,
9
9
  use: [
@@ -1,6 +1,6 @@
1
1
  const { canProcess } = require("../utils/helpers")
2
2
 
3
- module.exports = canProcess("coffee-loader", (resolvedPath) => ({
3
+ export = canProcess("coffee-loader", (resolvedPath: string) => ({
4
4
  test: /\.coffee(\.erb)?$/,
5
5
  use: [{ loader: resolvedPath }]
6
6
  }))
@@ -1,3 +1,3 @@
1
1
  const { getStyleRule } = require("../utils/getStyleRule")
2
2
 
3
- module.exports = getStyleRule(/\.(css)$/i)
3
+ export = getStyleRule(/\.(css)$/i)
@@ -2,7 +2,7 @@ const { canProcess } = require("../utils/helpers")
2
2
 
3
3
  const runner = /^win/.test(process.platform) ? "ruby " : ""
4
4
 
5
- module.exports = canProcess("rails-erb-loader", (resolvedPath) => ({
5
+ export = canProcess("rails-erb-loader", (resolvedPath: string) => ({
6
6
  test: /\.erb$/,
7
7
  enforce: "pre",
8
8
  exclude: /node_modules/,
@@ -3,8 +3,8 @@ const { getEsbuildLoaderConfig } = require("../esbuild")
3
3
  const { javascript_transpiler: javascriptTranspiler } = require("../config")
4
4
  const jscommon = require("./jscommon")
5
5
 
6
- module.exports = loaderMatches(javascriptTranspiler, "esbuild", () => ({
6
+ export = loaderMatches(javascriptTranspiler, "esbuild", () => ({
7
7
  test: /\.(ts|tsx|js|jsx|mjs|coffee)?(\.erb)?$/,
8
8
  ...jscommon,
9
- use: ({ resource }) => getEsbuildLoaderConfig(resource)
9
+ use: ({ resource }: { resource: string }) => getEsbuildLoaderConfig(resource)
10
10
  }))
@@ -1,21 +1,25 @@
1
- const { dirname, sep, normalize } = require("path")
1
+ import { dirname, sep, normalize } from "path"
2
2
  const {
3
3
  additional_paths: additionalPaths,
4
4
  source_path: sourcePath
5
5
  } = require("../config")
6
6
 
7
- module.exports = {
7
+ export = {
8
8
  test: /\.(bmp|gif|jpe?g|png|tiff|ico|avif|webp|eot|otf|ttf|woff|woff2|svg)$/,
9
9
  exclude: /\.(js|mjs|jsx|ts|tsx)$/,
10
10
  type: "asset/resource",
11
11
  generator: {
12
- filename: (pathData) => {
12
+ filename: (pathData: { filename?: string }) => {
13
+ // Guard against null/undefined pathData or filename
14
+ if (!pathData || !pathData.filename) {
15
+ return `static/[name]-[hash][ext][query]`
16
+ }
13
17
  const path = normalize(dirname(pathData.filename))
14
- const stripPaths = [...additionalPaths, sourcePath].map((p) =>
18
+ const stripPaths = [...additionalPaths, sourcePath].map((p: string) =>
15
19
  normalize(p)
16
20
  )
17
21
 
18
- const selectedStripPath = stripPaths.find((includePath) =>
22
+ const selectedStripPath = stripPaths.find((includePath: string) =>
19
23
  path.startsWith(includePath)
20
24
  )
21
25
 
@@ -23,9 +27,10 @@ module.exports = {
23
27
  return `static/[name]-[hash][ext][query]`
24
28
  }
25
29
 
30
+ // Split on both forward and backward slashes for cross-platform compatibility
26
31
  const folders = path
27
32
  .replace(selectedStripPath, "")
28
- .split(sep)
33
+ .split(/[\\/]/)
29
34
  .filter(Boolean)
30
35
 
31
36
  const foldersWithStatic = ["static", ...folders].join("/")
@@ -1,11 +1,11 @@
1
- const { resolve } = require("path")
2
- const { realpathSync } = require("fs")
1
+ import { resolve } from "path"
2
+ import { realpathSync } from "fs"
3
3
  const {
4
4
  source_path: sourcePath,
5
5
  additional_paths: additionalPaths
6
6
  } = require("../config")
7
7
 
8
- const inclusions = [sourcePath, ...additionalPaths].map((p) => {
8
+ const inclusions = [sourcePath, ...additionalPaths].map((p: string) => {
9
9
  try {
10
10
  return realpathSync(p)
11
11
  } catch (e) {
@@ -13,7 +13,7 @@ const inclusions = [sourcePath, ...additionalPaths].map((p) => {
13
13
  }
14
14
  })
15
15
 
16
- module.exports = {
16
+ export = {
17
17
  include: inclusions,
18
18
  exclude: [
19
19
  {
@@ -1,4 +1,3 @@
1
- const path = require("path")
2
1
  const { canProcess } = require("../utils/helpers")
3
2
  const { getStyleRule } = require("../utils/getStyleRule")
4
3
 
@@ -7,13 +6,14 @@ const {
7
6
  source_path: sourcePath
8
7
  } = require("../config")
9
8
 
10
- module.exports = canProcess("less-loader", (resolvedPath) =>
9
+ export = canProcess("less-loader", (resolvedPath: string) =>
11
10
  getStyleRule(/\.(less)(\.erb)?$/i, [
12
11
  {
13
12
  loader: resolvedPath,
14
13
  options: {
15
14
  lessOptions: {
16
- paths: [path.resolve(__dirname, "node_modules"), sourcePath, ...paths]
15
+ // Additional paths for Less imports (node_modules is resolved automatically)
16
+ paths: [sourcePath, ...paths]
17
17
  },
18
18
  sourceMap: true
19
19
  }
@@ -0,0 +1,25 @@
1
+ const config = require("../config")
2
+
3
+ const rspackRawConfig = () => ({
4
+ resourceQuery: /raw/,
5
+ type: "asset/source"
6
+ })
7
+
8
+ const webpackRawConfig = () => ({
9
+ oneOf: [
10
+ {
11
+ // Match any file with ?raw query parameter
12
+ resourceQuery: /raw/,
13
+ type: "asset/source"
14
+ },
15
+ {
16
+ // Fallback: match .html files without query
17
+ test: /\.html$/,
18
+ exclude: /\.(js|mjs|jsx|ts|tsx)$/,
19
+ type: "asset/source"
20
+ }
21
+ ]
22
+ })
23
+
24
+ export =
25
+ config.assets_bundler === "rspack" ? rspackRawConfig() : webpackRawConfig()
@@ -141,26 +141,36 @@ if (erb) {
141
141
  }
142
142
 
143
143
  // File/asset handling using Rspack's built-in asset modules
144
+ // This is a critical rule required for proper asset handling
144
145
  debug("Adding file/asset handling rule...")
145
146
  const file = require("./file")
146
147
 
147
- if (file) {
148
- debug("Successfully added file/asset rule")
149
- rules.push(file)
150
- } else {
151
- warn("file rule configuration returned null")
148
+ if (!file) {
149
+ throw new Error(
150
+ "CRITICAL: file rule configuration returned null. " +
151
+ "Asset handling is required for proper bundling. " +
152
+ "Please ensure the file rule module exports a valid rule configuration."
153
+ )
152
154
  }
153
155
 
156
+ debug("Successfully added file/asset rule")
157
+ rules.push(file)
158
+
154
159
  // Raw file loading
160
+ // This is a critical rule required for raw file imports
155
161
  debug("Adding raw file loading rule...")
156
162
  const raw = require("./raw")
157
163
 
158
- if (raw) {
159
- debug("Successfully added raw file rule")
160
- rules.push(raw)
161
- } else {
162
- warn("raw rule configuration returned null")
164
+ if (!raw) {
165
+ throw new Error(
166
+ "CRITICAL: raw rule configuration returned null. " +
167
+ "Raw file loading is required for proper bundling. " +
168
+ "Please ensure the raw rule module exports a valid rule configuration."
169
+ )
163
170
  }
164
171
 
172
+ debug("Successfully added raw file rule")
173
+ rules.push(raw)
174
+
165
175
  debug(`Rspack rules configuration complete. Total rules: ${rules.length}`)
166
- module.exports = rules
176
+ export = rules
@@ -4,7 +4,7 @@ const { getStyleRule } = require("../utils/getStyleRule")
4
4
  const { canProcess, packageMajorVersion } = require("../utils/helpers")
5
5
  const { additional_paths: extraPaths } = require("../config")
6
6
 
7
- module.exports = canProcess("sass-loader", (resolvedPath) => {
7
+ export = canProcess("sass-loader", (resolvedPath: string) => {
8
8
  const optionKey =
9
9
  packageMajorVersion("sass-loader") > 15 ? "loadPaths" : "includePaths"
10
10
  return getStyleRule(/\.(scss|sass)(\.erb)?$/i, [
@@ -1,4 +1,3 @@
1
- const path = require("path")
2
1
  const { canProcess } = require("../utils/helpers")
3
2
  const { getStyleRule } = require("../utils/getStyleRule")
4
3
 
@@ -7,17 +6,14 @@ const {
7
6
  source_path: sourcePath
8
7
  } = require("../config")
9
8
 
10
- module.exports = canProcess("stylus-loader", (resolvedPath) =>
9
+ export = canProcess("stylus-loader", (resolvedPath: string) =>
11
10
  getStyleRule(/\.(styl(us)?)(\.erb)?$/i, [
12
11
  {
13
12
  loader: resolvedPath,
14
13
  options: {
15
14
  stylusOptions: {
16
- include: [
17
- path.resolve(__dirname, "node_modules"),
18
- sourcePath,
19
- ...paths
20
- ]
15
+ // Additional paths for Stylus imports (node_modules is resolved automatically)
16
+ include: [sourcePath, ...paths]
21
17
  },
22
18
  sourceMap: true
23
19
  }
@@ -3,8 +3,8 @@ const { getSwcLoaderConfig } = require("../swc")
3
3
  const { javascript_transpiler: javascriptTranspiler } = require("../config")
4
4
  const jscommon = require("./jscommon")
5
5
 
6
- module.exports = loaderMatches(javascriptTranspiler, "swc", () => ({
6
+ export = loaderMatches(javascriptTranspiler, "swc", () => ({
7
7
  test: /\.(ts|tsx|js|jsx|mjs|coffee)?(\.erb)?$/,
8
8
  ...jscommon,
9
- use: ({ resource }) => getSwcLoaderConfig(resource)
9
+ use: ({ resource }: { resource: string }) => getSwcLoaderConfig(resource)
10
10
  }))
@@ -1,7 +1,7 @@
1
1
  /* eslint global-require: 0 */
2
2
  /* eslint import/no-dynamic-require: 0 */
3
3
 
4
- module.exports = [
4
+ export = [
5
5
  require("./raw"),
6
6
  require("./file"),
7
7
  require("./css"),
@@ -0,0 +1,54 @@
1
+ /* eslint global-require: 0 */
2
+ /* eslint import/no-dynamic-require: 0 */
3
+
4
+ import { resolve } from "path"
5
+ import { existsSync } from "fs"
6
+ import { merge } from "webpack-merge"
7
+ import type { RuleSetRule } from "webpack"
8
+
9
+ const JSX_FILE_REGEX = /\.(jsx|tsx)(\.erb)?$/
10
+ const TYPESCRIPT_FILE_REGEX = /\.(ts|tsx)(\.erb)?$/
11
+
12
+ const isJsxFile = (filename: string): boolean => !!filename.match(JSX_FILE_REGEX)
13
+
14
+ const isTypescriptFile = (filename: string): boolean => !!filename.match(TYPESCRIPT_FILE_REGEX)
15
+
16
+ const getCustomConfig = (): Partial<RuleSetRule> => {
17
+ const path = resolve("config", "swc.config.js")
18
+ if (existsSync(path)) {
19
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
20
+ return require(path)
21
+ }
22
+ return {}
23
+ }
24
+
25
+ const getSwcLoaderConfig = (filenameToProcess: string): RuleSetRule => {
26
+ const customConfig = getCustomConfig()
27
+ const isTs = isTypescriptFile(filenameToProcess)
28
+ const isJsx = isJsxFile(filenameToProcess)
29
+ const jsxKey = isTs ? "tsx" : "jsx"
30
+
31
+ const defaultConfig: RuleSetRule = {
32
+ loader: require.resolve("swc-loader"),
33
+ options: {
34
+ jsc: {
35
+ parser: {
36
+ dynamicImport: true,
37
+ syntax: isTs ? "typescript" : "ecmascript",
38
+ [jsxKey]: isJsx
39
+ },
40
+ loose: true
41
+ },
42
+ sourceMaps: true,
43
+ env: {
44
+ coreJs: 3,
45
+ exclude: ["transform-typeof-symbol"],
46
+ mode: "entry"
47
+ }
48
+ }
49
+ }
50
+
51
+ return merge(defaultConfig, customConfig)
52
+ }
53
+
54
+ export { getSwcLoaderConfig }
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.0.0-beta.7",
3
+ "version": "9.0.0-beta.8",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -34,6 +34,7 @@
34
34
  "build": "tsc && node scripts/remove-use-strict.js && yarn prettier --write 'package/**/*.js'",
35
35
  "build:types": "tsc",
36
36
  "lint": "eslint .",
37
+ "lint:fast": "eslint . --ext .js,.jsx,.ts,.tsx --config .eslintrc.fast.js",
37
38
  "test": "jest",
38
39
  "type-check": "tsc --noEmit",
39
40
  "prepublishOnly": "yarn build && yarn type-check"
@@ -46,12 +47,15 @@
46
47
  "devDependencies": {
47
48
  "@rspack/cli": "^1.4.11",
48
49
  "@rspack/core": "^1.4.11",
50
+ "@types/babel__core": "^7.20.5",
49
51
  "@types/js-yaml": "^4.0.9",
50
52
  "@types/node": "^24.5.2",
51
53
  "@types/path-complete-extname": "^1.0.3",
52
54
  "@types/webpack": "^5.28.5",
53
55
  "@types/webpack-dev-server": "^4.7.2",
54
56
  "@types/webpack-merge": "^5.0.0",
57
+ "@typescript-eslint/eslint-plugin": "^8.45.0",
58
+ "@typescript-eslint/parser": "^8.45.0",
55
59
  "babel-loader": "^8.2.4",
56
60
  "compression-webpack-plugin": "^9.0.0",
57
61
  "css-loader": "^7.1.2",
@@ -65,7 +69,9 @@
65
69
  "eslint-plugin-prettier": "^5.2.6",
66
70
  "eslint-plugin-react": "^7.37.5",
67
71
  "eslint-plugin-react-hooks": "^4.6.0",
72
+ "husky": "^9.1.7",
68
73
  "jest": "^29.7.0",
74
+ "lint-staged": "^15.2.10",
69
75
  "memory-fs": "^0.5.0",
70
76
  "mini-css-extract-plugin": "^2.9.4",
71
77
  "prettier": "^3.2.5",
@@ -83,8 +89,8 @@
83
89
  "@babel/plugin-transform-runtime": "^7.17.0",
84
90
  "@babel/preset-env": "^7.16.11",
85
91
  "@babel/runtime": "^7.17.9",
86
- "@rspack/core": "^1.0.0",
87
92
  "@rspack/cli": "^1.0.0",
93
+ "@rspack/core": "^1.0.0",
88
94
  "@rspack/plugin-react-refresh": "^1.0.0",
89
95
  "@types/babel__core": "^7.0.0",
90
96
  "@types/webpack": "^5.0.0",
@@ -183,6 +189,20 @@
183
189
  }
184
190
  },
185
191
  "packageManager": "yarn@1.22.22",
192
+ "lint-staged": {
193
+ "*.{js,jsx}": [
194
+ "eslint --fix",
195
+ "prettier --write"
196
+ ],
197
+ "*.{ts,tsx}": [
198
+ "eslint --fix",
199
+ "prettier --write",
200
+ "node scripts/type-check-no-emit.js"
201
+ ],
202
+ "*.{json,yml,yaml,md}": [
203
+ "prettier --write"
204
+ ]
205
+ },
186
206
  "engines": {
187
207
  "node": ">= 14",
188
208
  "yarn": ">=1 <5"
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Type-check script for lint-staged
5
+ *
6
+ * This script runs TypeScript type checking without emitting files.
7
+ * It ignores any arguments passed by lint-staged to ensure tsc uses
8
+ * the project's tsconfig.json rather than trying to compile individual files.
9
+ *
10
+ * Without this wrapper, lint-staged would pass staged file paths as arguments
11
+ * to tsc, causing it to ignore tsconfig.json and fail type checking.
12
+ */
13
+
14
+ const { execSync } = require("child_process")
15
+
16
+ try {
17
+ // Run tsc with no arguments (ignoring any passed by lint-staged)
18
+ // This ensures it uses tsconfig.json properly
19
+ execSync("npx tsc --noEmit", {
20
+ stdio: "inherit",
21
+ cwd: process.cwd()
22
+ })
23
+ process.exit(0)
24
+ } catch (error) {
25
+ // Type checking failed
26
+ process.exit(1)
27
+ }
@@ -1,12 +1,45 @@
1
- const raw = require("../../../package/rules/raw")
2
-
3
1
  describe("raw", () => {
4
- test("expected file types", () => {
5
- expect(raw.test.test(".html")).toBe(true)
2
+ describe("rspack bundler", () => {
3
+ beforeEach(() => {
4
+ jest.resetModules()
5
+ jest.doMock("../../../package/config", () => ({
6
+ assets_bundler: "rspack"
7
+ }))
8
+ })
9
+
10
+ afterEach(() => {
11
+ jest.dontMock("../../../package/config")
12
+ })
13
+
14
+ test("uses resourceQuery for any file with ?raw", () => {
15
+ const raw = require("../../../package/rules/raw")
16
+ expect(raw.resourceQuery).toStrictEqual(/raw/)
17
+ expect(raw.type).toBe("asset/source")
18
+ })
6
19
  })
7
20
 
8
- test("exclude expected file types", () => {
9
- const types = [".js", ".mjs", ".jsx", ".ts", ".tsx"]
10
- types.forEach((type) => expect(raw.exclude.test(type)).toBe(true))
21
+ describe("webpack bundler", () => {
22
+ beforeEach(() => {
23
+ jest.resetModules()
24
+ jest.doMock("../../../package/config", () => ({
25
+ assets_bundler: "webpack"
26
+ }))
27
+ })
28
+
29
+ afterEach(() => {
30
+ jest.dontMock("../../../package/config")
31
+ })
32
+
33
+ test("supports ?raw query and .html fallback with oneOf", () => {
34
+ const raw = require("../../../package/rules/raw")
35
+ expect(raw.oneOf).toHaveLength(2)
36
+ // First rule: any file with ?raw
37
+ expect(raw.oneOf[0].resourceQuery).toStrictEqual(/raw/)
38
+ expect(raw.oneOf[0].type).toBe("asset/source")
39
+ // Second rule: .html files without query
40
+ expect(raw.oneOf[1].test.test(".html")).toBe(true)
41
+ expect(raw.oneOf[1].exclude.test(".js")).toBe(true)
42
+ expect(raw.oneOf[1].type).toBe("asset/source")
43
+ })
11
44
  })
12
45
  })
@@ -10,7 +10,26 @@ jest.mock("../../../package/utils/helpers", () => {
10
10
  })
11
11
 
12
12
  describe("index", () => {
13
- test("rule tests are regexes", () => {
14
- rules.forEach((rule) => expect(rule.test instanceof RegExp).toBe(true))
13
+ test("rule tests are regexes or oneOf arrays", () => {
14
+ const rulesWithTest = rules.filter((rule) => !rule.oneOf)
15
+ const rulesWithOneOf = rules.filter((rule) => rule.oneOf)
16
+
17
+ // Verify all non-oneOf rules have test property
18
+ rulesWithTest.forEach((rule) => {
19
+ expect(rule.test).toBeInstanceOf(RegExp)
20
+ })
21
+
22
+ // Verify all oneOf rules are properly structured
23
+ rulesWithOneOf.forEach((rule) => {
24
+ expect(Array.isArray(rule.oneOf)).toBe(true)
25
+ rule.oneOf.forEach((subRule) => {
26
+ // Each subRule must have either a test or resourceQuery property (RegExp)
27
+ const matchers = [
28
+ subRule.test instanceof RegExp,
29
+ subRule.resourceQuery instanceof RegExp
30
+ ]
31
+ expect(matchers.some(Boolean)).toBe(true)
32
+ })
33
+ })
15
34
  })
16
35
  })
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "rootDir": "."
6
+ },
7
+ "include": [
8
+ "package/**/*.ts",
9
+ "package/**/*.tsx",
10
+ "package/**/*.test.ts",
11
+ "package/**/*.spec.ts",
12
+ "test/**/*.ts",
13
+ "test/**/*.tsx"
14
+ ],
15
+ "exclude": ["node_modules"]
16
+ }