shakapacker 9.0.0.beta.6 → 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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc.fast.js +40 -0
  3. data/.eslintrc.js +48 -0
  4. data/.github/workflows/generator.yml +6 -0
  5. data/.gitignore +1 -4
  6. data/.npmignore +56 -0
  7. data/CHANGELOG.md +64 -1
  8. data/CONTRIBUTING.md +75 -21
  9. data/Gemfile.lock +1 -1
  10. data/README.md +4 -0
  11. data/TODO.md +15 -16
  12. data/docs/transpiler-migration.md +191 -0
  13. data/docs/typescript-migration.md +378 -0
  14. data/lib/install/template.rb +54 -7
  15. data/lib/shakapacker/version.rb +1 -1
  16. data/package/.npmignore +4 -0
  17. data/package/babel/preset.ts +56 -0
  18. data/package/config.ts +23 -10
  19. data/package/env.ts +15 -2
  20. data/package/environments/{development.js → development.ts} +30 -8
  21. data/package/environments/{production.js → production.ts} +18 -4
  22. data/package/environments/test.ts +53 -0
  23. data/package/environments/types.ts +90 -0
  24. data/package/esbuild/index.ts +42 -0
  25. data/package/optimization/rspack.ts +36 -0
  26. data/package/optimization/{webpack.js → webpack.ts} +12 -4
  27. data/package/plugins/{rspack.js → rspack.ts} +20 -5
  28. data/package/plugins/{webpack.js → webpack.ts} +2 -2
  29. data/package/rspack/{index.js → index.ts} +17 -10
  30. data/package/rules/{babel.js → babel.ts} +1 -1
  31. data/package/rules/{coffee.js → coffee.ts} +1 -1
  32. data/package/rules/{css.js → css.ts} +1 -1
  33. data/package/rules/{erb.js → erb.ts} +1 -1
  34. data/package/rules/{esbuild.js → esbuild.ts} +2 -2
  35. data/package/rules/{file.js → file.ts} +11 -6
  36. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  37. data/package/rules/{less.js → less.ts} +3 -3
  38. data/package/rules/raw.ts +25 -0
  39. data/package/rules/{rspack.js → rspack.ts} +21 -11
  40. data/package/rules/{sass.js → sass.ts} +1 -1
  41. data/package/rules/{stylus.js → stylus.ts} +3 -7
  42. data/package/rules/{swc.js → swc.ts} +2 -2
  43. data/package/rules/{webpack.js → webpack.ts} +1 -1
  44. data/package/swc/index.ts +54 -0
  45. data/package/types/README.md +87 -0
  46. data/package/types/index.ts +60 -0
  47. data/package/utils/errorCodes.ts +219 -0
  48. data/package/utils/errorHelpers.ts +68 -2
  49. data/package/utils/pathValidation.ts +139 -0
  50. data/package/utils/typeGuards.ts +161 -47
  51. data/package.json +26 -4
  52. data/scripts/remove-use-strict.js +45 -0
  53. data/scripts/type-check-no-emit.js +27 -0
  54. data/test/package/rules/raw.test.js +40 -7
  55. data/test/package/rules/webpack.test.js +21 -2
  56. data/test/package/transpiler-defaults.test.js +127 -0
  57. data/test/scripts/remove-use-strict.test.js +125 -0
  58. data/test/typescript/build.test.js +3 -2
  59. data/test/typescript/environments.test.js +107 -0
  60. data/test/typescript/pathValidation.test.js +142 -0
  61. data/test/typescript/securityValidation.test.js +182 -0
  62. data/tsconfig.eslint.json +16 -0
  63. data/tsconfig.json +9 -10
  64. data/yarn.lock +415 -6
  65. metadata +50 -28
  66. data/package/babel/preset.js +0 -48
  67. data/package/environments/base.js +0 -103
  68. data/package/environments/test.js +0 -19
  69. data/package/esbuild/index.js +0 -40
  70. data/package/optimization/rspack.js +0 -29
  71. data/package/rules/raw.js +0 -15
  72. data/package/swc/index.js +0 -50
data/package/config.ts CHANGED
@@ -125,33 +125,46 @@ if (config.integrity?.hash_functions) {
125
125
  config.integrity.hash_functions = [...new Set(config.integrity.hash_functions)]
126
126
  }
127
127
 
128
- // Allow ENV variable to override assets_bundler
128
+ // Ensure assets_bundler has a default value
129
+ if (!config.assets_bundler) {
130
+ config.assets_bundler = "webpack"
131
+ }
132
+
133
+ // Allow ENV variable to override assets_bundler
129
134
  if (process.env.SHAKAPACKER_ASSETS_BUNDLER) {
130
135
  config.assets_bundler = process.env.SHAKAPACKER_ASSETS_BUNDLER
131
136
  }
132
137
 
133
138
  // Define clear defaults
134
- const DEFAULT_JAVASCRIPT_TRANSPILER =
139
+ // Keep Babel as default for webpack to maintain backward compatibility
140
+ // Use SWC for rspack as it's a newer bundler where we can set modern defaults
141
+ const DEFAULT_JAVASCRIPT_TRANSPILER =
135
142
  config.assets_bundler === "rspack" ? "swc" : "babel"
136
143
 
137
- // Backward compatibility: Add webpack_loader property that maps to javascript_transpiler
138
- // Show deprecation warning if webpack_loader is used
139
- // Use type-safe property check
140
- const configWithLegacy = config as Config & { webpack_loader?: string }
141
- const webpackLoader = configWithLegacy.webpack_loader
144
+ // Backward compatibility: Check for webpack_loader using proper type guard
145
+ function hasWebpackLoader(obj: unknown): obj is Config & { webpack_loader: string } {
146
+ return (
147
+ typeof obj === 'object' &&
148
+ obj !== null &&
149
+ 'webpack_loader' in obj &&
150
+ typeof (obj as Record<string, unknown>).webpack_loader === 'string'
151
+ )
152
+ }
142
153
 
143
- if (webpackLoader && !config.javascript_transpiler) {
154
+ // Allow environment variable to override javascript_transpiler
155
+ if (process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER) {
156
+ config.javascript_transpiler = process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER
157
+ } else if (hasWebpackLoader(config) && !config.javascript_transpiler) {
144
158
  console.warn(
145
159
  "[SHAKAPACKER DEPRECATION] The 'webpack_loader' configuration option is deprecated.\n" +
146
160
  "Please use 'javascript_transpiler' instead as it better reflects its purpose of configuring JavaScript transpilation regardless of the bundler used."
147
161
  )
148
- config.javascript_transpiler = webpackLoader
162
+ config.javascript_transpiler = config.webpack_loader
149
163
  } else if (!config.javascript_transpiler) {
150
164
  config.javascript_transpiler = DEFAULT_JAVASCRIPT_TRANSPILER
151
165
  }
152
166
 
153
167
  // Ensure webpack_loader is always available for backward compatibility
154
- // Use property assignment instead of type assertion
155
168
  Object.defineProperty(config, 'webpack_loader', {
156
169
  value: config.javascript_transpiler,
157
170
  writable: true,
data/package/env.ts CHANGED
@@ -3,14 +3,27 @@ import { readFileSync } from "fs"
3
3
  const defaultConfigPath = require("./utils/defaultConfigPath")
4
4
  const configPath = require("./utils/configPath")
5
5
  const { isFileNotFoundError } = require("./utils/errorHelpers")
6
+ const { sanitizeEnvValue } = require("./utils/pathValidation")
6
7
 
7
8
  const NODE_ENVIRONMENTS = ["development", "production", "test"] as const
8
9
  const DEFAULT = "production"
9
10
 
10
- const initialRailsEnv = process.env.RAILS_ENV
11
- const rawNodeEnv = process.env.NODE_ENV
11
+ // Sanitize environment variables to prevent injection
12
+ const initialRailsEnv = sanitizeEnvValue(process.env.RAILS_ENV)
13
+ const rawNodeEnv = sanitizeEnvValue(process.env.NODE_ENV)
14
+
15
+ // Validate NODE_ENV strictly
12
16
  const nodeEnv =
13
17
  rawNodeEnv && NODE_ENVIRONMENTS.includes(rawNodeEnv as typeof NODE_ENVIRONMENTS[number]) ? rawNodeEnv : DEFAULT
18
+
19
+ // Log warning if NODE_ENV was invalid
20
+ if (rawNodeEnv && !NODE_ENVIRONMENTS.includes(rawNodeEnv as typeof NODE_ENVIRONMENTS[number])) {
21
+ console.warn(
22
+ `[SHAKAPACKER WARNING] Invalid NODE_ENV value: ${rawNodeEnv}. ` +
23
+ `Valid values are: ${NODE_ENVIRONMENTS.join(', ')}. Using default: ${DEFAULT}`
24
+ )
25
+ }
26
+
14
27
  const isProduction = nodeEnv === "production"
15
28
  const isDevelopment = nodeEnv === "development"
16
29
 
@@ -1,17 +1,35 @@
1
+ /**
2
+ * Development environment configuration for webpack and rspack bundlers
3
+ * @module environments/development
4
+ */
5
+
1
6
  const { merge } = require("webpack-merge")
2
7
  const config = require("../config")
3
8
  const baseConfig = require("./base")
4
9
  const webpackDevServerConfig = require("../webpackDevServerConfig")
5
10
  const { runningWebpackDevServer } = require("../env")
6
11
  const { moduleExists } = require("../utils/helpers")
12
+ import type {
13
+ WebpackConfigWithDevServer,
14
+ RspackConfigWithDevServer,
15
+ ReactRefreshWebpackPlugin,
16
+ ReactRefreshRspackPlugin
17
+ } from "./types"
7
18
 
19
+ /**
20
+ * Base development configuration shared between webpack and rspack
21
+ */
8
22
  const baseDevConfig = {
9
- mode: "development",
10
- devtool: "cheap-module-source-map"
23
+ mode: "development" as const,
24
+ devtool: "cheap-module-source-map" as const
11
25
  }
12
26
 
13
- const webpackDevConfig = () => {
14
- const webpackConfig = {
27
+ /**
28
+ * Generate webpack-specific development configuration
29
+ * @returns Webpack configuration with dev server settings
30
+ */
31
+ const webpackDevConfig = (): WebpackConfigWithDevServer => {
32
+ const webpackConfig: WebpackConfigWithDevServer = {
15
33
  ...baseDevConfig,
16
34
  ...(runningWebpackDevServer && { devServer: webpackDevServerConfig() })
17
35
  }
@@ -33,15 +51,19 @@ const webpackDevConfig = () => {
33
51
  return webpackConfig
34
52
  }
35
53
 
36
- const rspackDevConfig = () => {
54
+ /**
55
+ * Generate rspack-specific development configuration
56
+ * @returns Rspack configuration with dev server settings
57
+ */
58
+ const rspackDevConfig = (): RspackConfigWithDevServer => {
37
59
  const devServerConfig = webpackDevServerConfig()
38
- const rspackConfig = {
60
+ const rspackConfig: RspackConfigWithDevServer = {
39
61
  ...baseDevConfig,
40
62
  devServer: {
41
63
  ...devServerConfig,
42
64
  devMiddleware: {
43
- ...devServerConfig.devMiddleware,
44
- writeToDisk: (filePath) => !filePath.includes(".hot-update.")
65
+ ...(devServerConfig.devMiddleware || {}),
66
+ writeToDisk: (filePath: string) => !filePath.includes(".hot-update.")
45
67
  }
46
68
  }
47
69
  }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * Production environment configuration for webpack and rspack bundlers
3
+ * @module environments/production
4
+ */
5
+
1
6
  /* eslint global-require: 0 */
2
7
  /* eslint import/no-dynamic-require: 0 */
3
8
 
@@ -6,6 +11,8 @@ const { merge } = require("webpack-merge")
6
11
  const baseConfig = require("./base")
7
12
  const { moduleExists } = require("../utils/helpers")
8
13
  const config = require("../config")
14
+ import type { Configuration as WebpackConfiguration, WebpackPluginInstance } from "webpack"
15
+ import type { CompressionPluginConstructor } from "./types"
9
16
 
10
17
  const optimizationPath = resolve(
11
18
  __dirname,
@@ -15,14 +22,18 @@ const optimizationPath = resolve(
15
22
  )
16
23
  const { getOptimization } = require(optimizationPath)
17
24
 
18
- let CompressionPlugin = null
25
+ let CompressionPlugin: CompressionPluginConstructor | null = null
19
26
  if (moduleExists("compression-webpack-plugin")) {
20
27
  // eslint-disable-next-line global-require
21
28
  CompressionPlugin = require("compression-webpack-plugin")
22
29
  }
23
30
 
24
- const getPlugins = () => {
25
- const plugins = []
31
+ /**
32
+ * Generate production plugins including compression
33
+ * @returns Array of webpack plugins for production
34
+ */
35
+ const getPlugins = (): WebpackPluginInstance[] => {
36
+ const plugins: WebpackPluginInstance[] = []
26
37
 
27
38
  if (CompressionPlugin) {
28
39
  plugins.push(
@@ -47,7 +58,10 @@ const getPlugins = () => {
47
58
  return plugins
48
59
  }
49
60
 
50
- const productionConfig = {
61
+ /**
62
+ * Production configuration with optimizations and compression
63
+ */
64
+ const productionConfig: Partial<WebpackConfiguration> = {
51
65
  devtool: "source-map",
52
66
  stats: "normal",
53
67
  bail: true,
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Test environment configuration for webpack and rspack bundlers
3
+ * @module environments/test
4
+ */
5
+
6
+ const { merge } = require("webpack-merge")
7
+ const config = require("../config")
8
+ const baseConfig = require("./base")
9
+ import type { Configuration as WebpackConfiguration } from "webpack"
10
+
11
+ interface TestConfig {
12
+ mode: "development" | "production" | "none"
13
+ devtool: string | false
14
+ watchOptions?: {
15
+ ignored: RegExp
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Shared test configuration for both webpack and rspack
21
+ * Ensures consistent test behavior across bundlers
22
+ */
23
+ const sharedTestConfig: TestConfig = {
24
+ mode: "development",
25
+ devtool: "cheap-module-source-map",
26
+ // Disable file watching in test mode
27
+ watchOptions: {
28
+ ignored: /node_modules/
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Generate rspack-specific test configuration
34
+ * @returns Rspack configuration optimized for testing
35
+ */
36
+ const rspackTestConfig = (): TestConfig => ({
37
+ ...sharedTestConfig
38
+ // Add any rspack-specific overrides here if needed
39
+ })
40
+
41
+ /**
42
+ * Generate webpack-specific test configuration
43
+ * @returns Webpack configuration for testing with same settings as rspack
44
+ */
45
+ const webpackTestConfig = (): Partial<WebpackConfiguration> => ({
46
+ ...sharedTestConfig
47
+ // Add any webpack-specific overrides here if needed
48
+ })
49
+
50
+ const bundlerConfig =
51
+ config.assets_bundler === "rspack" ? rspackTestConfig() : webpackTestConfig()
52
+
53
+ module.exports = merge(baseConfig, bundlerConfig)
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Type definitions for environment configurations
3
+ * These types are exported for consumer use
4
+ */
5
+
6
+ import type { Configuration as WebpackConfiguration, WebpackPluginInstance } from "webpack"
7
+ import type { Configuration as DevServerConfiguration } from "webpack-dev-server"
8
+
9
+ /**
10
+ * Webpack configuration extended with dev server support
11
+ */
12
+ export interface WebpackConfigWithDevServer extends WebpackConfiguration {
13
+ devServer?: DevServerConfiguration
14
+ plugins?: WebpackPluginInstance[]
15
+ }
16
+
17
+ /**
18
+ * Rspack plugin interface
19
+ * Rspack plugins follow a similar pattern to webpack but may have different internals
20
+ */
21
+ export interface RspackPlugin {
22
+ new(...args: any[]): {
23
+ apply(compiler: any): void
24
+ [key: string]: any
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Rspack dev server configuration
30
+ * Similar to webpack-dev-server but with some rspack-specific options
31
+ */
32
+ export interface RspackDevServerConfig {
33
+ port?: number | string | "auto"
34
+ host?: string
35
+ hot?: boolean | "only"
36
+ historyApiFallback?: boolean | Record<string, unknown>
37
+ headers?: Record<string, string | string[]>
38
+ proxy?: unknown
39
+ static?: boolean | string | Array<string | Record<string, unknown>>
40
+ devMiddleware?: {
41
+ writeToDisk?: boolean | ((filePath: string) => boolean)
42
+ publicPath?: string
43
+ [key: string]: unknown
44
+ }
45
+ [key: string]: unknown
46
+ }
47
+
48
+ /**
49
+ * Rspack configuration with dev server support
50
+ */
51
+ export interface RspackConfigWithDevServer {
52
+ mode?: "development" | "production" | "none"
53
+ devtool?: string | false
54
+ devServer?: RspackDevServerConfig
55
+ plugins?: RspackPlugin[]
56
+ module?: WebpackConfiguration["module"]
57
+ resolve?: WebpackConfiguration["resolve"]
58
+ entry?: WebpackConfiguration["entry"]
59
+ output?: WebpackConfiguration["output"]
60
+ optimization?: WebpackConfiguration["optimization"]
61
+ [key: string]: unknown
62
+ }
63
+
64
+ /**
65
+ * Compression plugin options interface
66
+ */
67
+ export interface CompressionPluginOptions {
68
+ filename: string
69
+ algorithm: string | "gzip" | "brotliCompress"
70
+ test: RegExp
71
+ threshold?: number
72
+ minRatio?: number
73
+ deleteOriginalAssets?: boolean
74
+ }
75
+
76
+ /**
77
+ * Compression plugin constructor type
78
+ */
79
+ export type CompressionPluginConstructor = new (options: CompressionPluginOptions) => WebpackPluginInstance
80
+
81
+ /**
82
+ * React Refresh plugin types
83
+ */
84
+ export interface ReactRefreshWebpackPlugin {
85
+ new(options?: Record<string, unknown>): WebpackPluginInstance
86
+ }
87
+
88
+ export interface ReactRefreshRspackPlugin {
89
+ new(options?: Record<string, unknown>): RspackPlugin
90
+ }
@@ -0,0 +1,42 @@
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 LOADER_EXT_REGEX = /\.([jt]sx?)(\.erb)?$/
10
+
11
+ const getLoaderExtension = (filename: string): string => {
12
+ const matchData = filename.match(LOADER_EXT_REGEX)
13
+
14
+ if (!matchData) {
15
+ return "js"
16
+ }
17
+
18
+ return matchData[1] ?? "js"
19
+ }
20
+
21
+ const getCustomConfig = (): Partial<RuleSetRule> => {
22
+ const path = resolve("config", "esbuild.config.js")
23
+ if (existsSync(path)) {
24
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
25
+ return require(path)
26
+ }
27
+ return {}
28
+ }
29
+
30
+ const getEsbuildLoaderConfig = (filenameToProcess: string): RuleSetRule => {
31
+ const customConfig = getCustomConfig()
32
+ const defaultConfig: RuleSetRule = {
33
+ loader: require.resolve("esbuild-loader"),
34
+ options: {
35
+ loader: getLoaderExtension(filenameToProcess)
36
+ }
37
+ }
38
+
39
+ return merge(defaultConfig, customConfig)
40
+ }
41
+
42
+ export { getEsbuildLoaderConfig }
@@ -0,0 +1,36 @@
1
+ const { requireOrError } = require("../utils/requireOrError")
2
+ const { error: logError } = require("../utils/debug")
3
+
4
+ const rspack = requireOrError("@rspack/core")
5
+
6
+ interface OptimizationConfig {
7
+ minimize: boolean
8
+ minimizer?: unknown[]
9
+ }
10
+
11
+ const getOptimization = (): OptimizationConfig => {
12
+ // Use Rspack's built-in minification instead of terser-webpack-plugin
13
+ const result: OptimizationConfig = { minimize: true }
14
+ try {
15
+ result.minimizer = [
16
+ new rspack.SwcJsMinimizerRspackPlugin(),
17
+ new rspack.LightningCssMinimizerRspackPlugin()
18
+ ]
19
+ } catch (error: unknown) {
20
+ const errorMessage = error instanceof Error ? error.message : String(error)
21
+ const errorStack = error instanceof Error ? error.stack : ''
22
+ // Log full error with stack trace
23
+ logError(
24
+ `Failed to configure Rspack minimizers: ${errorMessage}\n${errorStack}`
25
+ )
26
+ // Re-throw the error to properly propagate it
27
+ throw new Error(
28
+ `Could not configure Rspack minimizers: ${errorMessage}. Please check that @rspack/core is properly installed.`
29
+ )
30
+ }
31
+ return result
32
+ }
33
+
34
+ export = {
35
+ getOptimization
36
+ }
@@ -3,7 +3,7 @@ const { requireOrError } = require("../utils/requireOrError")
3
3
  const TerserPlugin = requireOrError("terser-webpack-plugin")
4
4
  const { moduleExists } = require("../utils/helpers")
5
5
 
6
- const tryCssMinimizer = () => {
6
+ const tryCssMinimizer = (): unknown | null => {
7
7
  if (
8
8
  moduleExists("css-loader") &&
9
9
  moduleExists("css-minimizer-webpack-plugin")
@@ -15,12 +15,20 @@ const tryCssMinimizer = () => {
15
15
  return null
16
16
  }
17
17
 
18
- const getOptimization = () => {
18
+ interface OptimizationConfig {
19
+ minimizer: unknown[]
20
+ }
21
+
22
+ const getOptimization = (): OptimizationConfig => {
19
23
  return {
20
24
  minimizer: [
21
25
  tryCssMinimizer(),
22
26
  new TerserPlugin({
23
- parallel: Number.parseInt(process.env.SHAKAPACKER_PARALLEL, 10) || true,
27
+ // SHAKAPACKER_PARALLEL env var: number of parallel workers, or true for auto (os.cpus().length - 1)
28
+ // If not set or invalid, defaults to true (automatic parallelization)
29
+ parallel: process.env.SHAKAPACKER_PARALLEL
30
+ ? Number.parseInt(process.env.SHAKAPACKER_PARALLEL, 10) || true
31
+ : true,
24
32
  terserOptions: {
25
33
  parse: {
26
34
  // Let terser parse ecma 8 code but always output
@@ -44,6 +52,6 @@ const getOptimization = () => {
44
52
  }
45
53
  }
46
54
 
47
- module.exports = {
55
+ export = {
48
56
  getOptimization
49
57
  }
@@ -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
  }))