shakapacker 8.4.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintignore +1 -0
  3. data/.eslintrc.fast.js +40 -0
  4. data/.eslintrc.js +48 -0
  5. data/.github/STATUS.md +1 -0
  6. data/.github/workflows/claude-code-review.yml +54 -0
  7. data/.github/workflows/claude.yml +50 -0
  8. data/.github/workflows/dummy.yml +8 -4
  9. data/.github/workflows/generator.yml +17 -14
  10. data/.github/workflows/node.yml +23 -1
  11. data/.github/workflows/ruby.yml +11 -0
  12. data/.github/workflows/test-bundlers.yml +170 -0
  13. data/.gitignore +17 -0
  14. data/.husky/pre-commit +2 -0
  15. data/.npmignore +56 -0
  16. data/.prettierignore +3 -0
  17. data/.rubocop.yml +1 -0
  18. data/.yalcignore +26 -0
  19. data/CHANGELOG.md +156 -18
  20. data/CLAUDE.md +29 -0
  21. data/CONTRIBUTING.md +138 -20
  22. data/Gemfile.lock +3 -3
  23. data/README.md +130 -5
  24. data/Rakefile +39 -4
  25. data/TODO.md +50 -0
  26. data/TODO_v9.md +87 -0
  27. data/conductor-setup.sh +70 -0
  28. data/conductor.json +7 -0
  29. data/docs/cdn_setup.md +379 -0
  30. data/docs/css-modules-export-mode.md +512 -0
  31. data/docs/deployment.md +10 -1
  32. data/docs/optional-peer-dependencies.md +198 -0
  33. data/docs/peer-dependencies.md +60 -0
  34. data/docs/rspack.md +190 -0
  35. data/docs/rspack_migration_guide.md +202 -0
  36. data/docs/transpiler-migration.md +188 -0
  37. data/docs/transpiler-performance.md +179 -0
  38. data/docs/troubleshooting.md +5 -0
  39. data/docs/typescript-migration.md +378 -0
  40. data/docs/typescript.md +99 -0
  41. data/docs/using_esbuild_loader.md +3 -3
  42. data/docs/using_swc_loader.md +5 -3
  43. data/docs/v6_upgrade.md +10 -0
  44. data/docs/v9_upgrade.md +413 -0
  45. data/lib/install/bin/shakapacker +3 -5
  46. data/lib/install/config/rspack/rspack.config.js +6 -0
  47. data/lib/install/config/rspack/rspack.config.ts +7 -0
  48. data/lib/install/config/shakapacker.yml +12 -2
  49. data/lib/install/config/webpack/webpack.config.ts +7 -0
  50. data/lib/install/package.json +38 -0
  51. data/lib/install/template.rb +194 -44
  52. data/lib/shakapacker/configuration.rb +141 -0
  53. data/lib/shakapacker/dev_server_runner.rb +25 -5
  54. data/lib/shakapacker/doctor.rb +844 -0
  55. data/lib/shakapacker/manifest.rb +4 -2
  56. data/lib/shakapacker/rspack_runner.rb +19 -0
  57. data/lib/shakapacker/runner.rb +144 -4
  58. data/lib/shakapacker/swc_migrator.rb +376 -0
  59. data/lib/shakapacker/utils/manager.rb +2 -0
  60. data/lib/shakapacker/version.rb +1 -1
  61. data/lib/shakapacker/version_checker.rb +1 -1
  62. data/lib/shakapacker/webpack_runner.rb +4 -42
  63. data/lib/shakapacker.rb +2 -1
  64. data/lib/tasks/shakapacker/doctor.rake +8 -0
  65. data/lib/tasks/shakapacker/install.rake +12 -2
  66. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  67. data/lib/tasks/shakapacker.rake +1 -0
  68. data/package/.npmignore +4 -0
  69. data/package/babel/preset.ts +56 -0
  70. data/package/config.ts +175 -0
  71. data/package/{dev_server.js → dev_server.ts} +8 -5
  72. data/package/env.ts +92 -0
  73. data/package/environments/base.ts +138 -0
  74. data/package/environments/development.ts +90 -0
  75. data/package/environments/production.ts +80 -0
  76. data/package/environments/test.ts +53 -0
  77. data/package/environments/types.ts +90 -0
  78. data/package/esbuild/index.ts +42 -0
  79. data/package/index.d.ts +3 -97
  80. data/package/index.ts +52 -0
  81. data/package/loaders.d.ts +28 -0
  82. data/package/optimization/rspack.ts +36 -0
  83. data/package/optimization/webpack.ts +57 -0
  84. data/package/plugins/rspack.ts +103 -0
  85. data/package/plugins/webpack.ts +62 -0
  86. data/package/rspack/index.ts +64 -0
  87. data/package/rules/{babel.js → babel.ts} +2 -2
  88. data/package/rules/{coffee.js → coffee.ts} +1 -1
  89. data/package/rules/css.ts +3 -0
  90. data/package/rules/{erb.js → erb.ts} +1 -1
  91. data/package/rules/esbuild.ts +10 -0
  92. data/package/rules/file.ts +40 -0
  93. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  94. data/package/rules/{less.js → less.ts} +4 -4
  95. data/package/rules/raw.ts +25 -0
  96. data/package/rules/rspack.ts +176 -0
  97. data/package/rules/{sass.js → sass.ts} +7 -3
  98. data/package/rules/{stylus.js → stylus.ts} +4 -8
  99. data/package/rules/swc.ts +10 -0
  100. data/package/rules/{index.js → webpack.ts} +1 -1
  101. data/package/swc/index.ts +54 -0
  102. data/package/types/README.md +87 -0
  103. data/package/types/index.ts +60 -0
  104. data/package/types.ts +108 -0
  105. data/package/utils/configPath.ts +6 -0
  106. data/package/utils/debug.ts +49 -0
  107. data/package/utils/defaultConfigPath.ts +4 -0
  108. data/package/utils/errorCodes.ts +219 -0
  109. data/package/utils/errorHelpers.ts +143 -0
  110. data/package/utils/getStyleRule.ts +64 -0
  111. data/package/utils/helpers.ts +85 -0
  112. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  113. data/package/utils/pathValidation.ts +139 -0
  114. data/package/utils/requireOrError.ts +15 -0
  115. data/package/utils/snakeToCamelCase.ts +5 -0
  116. data/package/utils/typeGuards.ts +342 -0
  117. data/package/utils/validateDependencies.ts +61 -0
  118. data/package/webpack-types.d.ts +33 -0
  119. data/package/webpackDevServerConfig.ts +117 -0
  120. data/package.json +134 -9
  121. data/scripts/remove-use-strict.js +45 -0
  122. data/scripts/type-check-no-emit.js +27 -0
  123. data/test/package/config.test.js +3 -0
  124. data/test/package/env.test.js +42 -7
  125. data/test/package/environments/base.test.js +5 -1
  126. data/test/package/rules/babel.test.js +16 -0
  127. data/test/package/rules/esbuild.test.js +1 -1
  128. data/test/package/rules/raw.test.js +40 -7
  129. data/test/package/rules/swc.test.js +1 -1
  130. data/test/package/rules/webpack.test.js +35 -0
  131. data/test/package/staging.test.js +4 -3
  132. data/test/package/transpiler-defaults.test.js +127 -0
  133. data/test/peer-dependencies.sh +85 -0
  134. data/test/scripts/remove-use-strict.test.js +125 -0
  135. data/test/typescript/build.test.js +118 -0
  136. data/test/typescript/environments.test.js +107 -0
  137. data/test/typescript/pathValidation.test.js +142 -0
  138. data/test/typescript/securityValidation.test.js +182 -0
  139. data/tools/README.md +124 -0
  140. data/tools/css-modules-v9-codemod.js +179 -0
  141. data/tsconfig.eslint.json +16 -0
  142. data/tsconfig.json +38 -0
  143. data/yarn.lock +2704 -767
  144. metadata +111 -41
  145. data/package/babel/preset.js +0 -48
  146. data/package/config.js +0 -56
  147. data/package/env.js +0 -48
  148. data/package/environments/base.js +0 -171
  149. data/package/environments/development.js +0 -13
  150. data/package/environments/production.js +0 -88
  151. data/package/environments/test.js +0 -3
  152. data/package/esbuild/index.js +0 -40
  153. data/package/index.js +0 -40
  154. data/package/rules/css.js +0 -3
  155. data/package/rules/esbuild.js +0 -10
  156. data/package/rules/file.js +0 -29
  157. data/package/rules/raw.js +0 -5
  158. data/package/rules/swc.js +0 -10
  159. data/package/swc/index.js +0 -50
  160. data/package/utils/configPath.js +0 -4
  161. data/package/utils/defaultConfigPath.js +0 -2
  162. data/package/utils/getStyleRule.js +0 -40
  163. data/package/utils/helpers.js +0 -62
  164. data/package/utils/snakeToCamelCase.js +0 -5
  165. data/package/webpackDevServerConfig.js +0 -71
  166. data/test/package/rules/index.test.js +0 -16
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Production environment configuration for webpack and rspack bundlers
3
+ * @module environments/production
4
+ */
5
+
6
+ /* eslint global-require: 0 */
7
+ /* eslint import/no-dynamic-require: 0 */
8
+
9
+ const { resolve } = require("path")
10
+ const { merge } = require("webpack-merge")
11
+ const baseConfig = require("./base")
12
+ const { moduleExists } = require("../utils/helpers")
13
+ const config = require("../config")
14
+ import type { Configuration as WebpackConfiguration, WebpackPluginInstance } from "webpack"
15
+ import type { CompressionPluginConstructor } from "./types"
16
+
17
+ const optimizationPath = resolve(
18
+ __dirname,
19
+ "..",
20
+ "optimization",
21
+ `${config.assets_bundler}.js`
22
+ )
23
+ const { getOptimization } = require(optimizationPath)
24
+
25
+ let CompressionPlugin: CompressionPluginConstructor | null = null
26
+ if (moduleExists("compression-webpack-plugin")) {
27
+ // eslint-disable-next-line global-require
28
+ CompressionPlugin = require("compression-webpack-plugin")
29
+ }
30
+
31
+ /**
32
+ * Generate production plugins including compression
33
+ * @returns Array of webpack plugins for production
34
+ */
35
+ const getPlugins = (): WebpackPluginInstance[] => {
36
+ const plugins: WebpackPluginInstance[] = []
37
+
38
+ if (CompressionPlugin) {
39
+ plugins.push(
40
+ new CompressionPlugin({
41
+ filename: "[path][base].gz[query]",
42
+ algorithm: "gzip",
43
+ test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/
44
+ })
45
+ )
46
+
47
+ if ("brotli" in process.versions) {
48
+ plugins.push(
49
+ new CompressionPlugin({
50
+ filename: "[path][base].br[query]",
51
+ algorithm: "brotliCompress",
52
+ test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/
53
+ })
54
+ )
55
+ }
56
+ }
57
+
58
+ return plugins
59
+ }
60
+
61
+ /**
62
+ * Production configuration with optimizations and compression
63
+ */
64
+ const productionConfig: Partial<WebpackConfiguration> = {
65
+ devtool: "source-map",
66
+ stats: "normal",
67
+ bail: true,
68
+ plugins: getPlugins(),
69
+ optimization: getOptimization()
70
+ }
71
+
72
+ if (config.useContentHash === false) {
73
+ // eslint-disable-next-line no-console
74
+ console.warn(`⚠️ WARNING
75
+ Setting 'useContentHash' to 'false' in the production environment (specified by NODE_ENV environment variable) is not allowed!
76
+ Content hashes get added to the filenames regardless of setting useContentHash in 'shakapacker.yml' to false.
77
+ `)
78
+ }
79
+
80
+ module.exports = merge(baseConfig, productionConfig)
@@ -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 }
data/package/index.d.ts CHANGED
@@ -1,97 +1,3 @@
1
- declare module 'shakapacker' {
2
- import { Configuration, RuleSetRule } from 'webpack'
3
- import * as https from 'node:https';
4
-
5
- export interface Config {
6
- source_path: string
7
- source_entry_path: string
8
- nested_entries: boolean
9
- css_extract_ignore_order_warnings: boolean
10
- public_root_path: string
11
- public_output_path: string
12
- cache_path: string
13
- webpack_compile_output: boolean
14
- shakapacker_precompile: boolean
15
- additional_paths: string[]
16
- cache_manifest: boolean
17
- webpack_loader: string
18
- ensure_consistent_versioning: boolean
19
- compiler_strategy: string
20
- useContentHash: boolean
21
- compile: boolean,
22
- outputPath: string
23
- publicPath: string
24
- publicPathWithoutCDN: string
25
- manifestPath: string
26
- }
27
-
28
- export interface Env {
29
- railsEnv: string
30
- nodeEnv: string
31
- isProduction: boolean
32
- isDevelopment: boolean
33
- runningWebpackDevServer: boolean
34
- }
35
-
36
- type Header = Array<{ key: string; value: string }> | Record<string, string | string[]>
37
- type ServerType = 'http' | 'https' | 'spdy'
38
- type WebSocketType = 'sockjs' | 'ws'
39
-
40
- /**
41
- * This has the same keys and behavior as https://webpack.js.org/configuration/dev-server/ except:
42
- * 1. `hot` is replaced by `hmr`;
43
- * 2. Camel-cased properties are replaced by snake-cased ones.
44
- * @see {import('webpack-dev-server').Configuration}
45
- */
46
- interface DevServerConfig {
47
- allowed_hosts?: 'all' | 'auto' | string | string[]
48
- bonjour?: boolean | Record<string, unknown> // bonjour.BonjourOptions
49
- client?: Record<string, unknown> // Client
50
- compress?: boolean
51
- dev_middleware?: Record<string, unknown> // webpackDevMiddleware.Options
52
- headers?: Header | (() => Header)
53
- history_api_fallback?: boolean | Record<string, unknown> // HistoryApiFallbackOptions
54
- hmr?: 'only' | boolean
55
- host?: 'local-ip' | 'local-ipv4' | 'local-ipv6' | string
56
- http2?: boolean
57
- https?: boolean | https.ServerOptions
58
- ipc?: boolean | string
59
- magic_html?: boolean
60
- live_reload?: boolean
61
- open?: boolean | string | string[] | Record<string, unknown> | Record<string, unknown>[]
62
- port?: 'auto' | string | number
63
- proxy?: unknown // ProxyConfigMap | ProxyConfigArray
64
- setup_exit_signals?: boolean
65
- static?: boolean | string | unknown // Static | Array<string | Static>
66
- watch_files?: string | string[] | unknown // WatchFiles | Array<WatchFiles | string>
67
- web_socket_server?: string | boolean | WebSocketType | { type?: string | boolean | WebSocketType, options?: Record<string, unknown> }
68
- server?: string | boolean | ServerType | { type?: string | boolean | ServerType, options?: https.ServerOptions }
69
- [otherWebpackDevServerConfigKey: string]: unknown
70
- }
71
-
72
- export const config: Config
73
- export const devServer: DevServerConfig
74
- export function generateWebpackConfig(extraConfig?: Configuration): Configuration
75
- export const baseConfig: Configuration
76
- export const env: Env
77
- export const rules: RuleSetRule[]
78
- export function moduleExists(packageName: string): boolean
79
- export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null
80
- export const inliningCss: boolean
81
- export * from 'webpack-merge'
82
- }
83
-
84
- declare module 'shakapacker/package/babel/preset.js' {
85
- import { ConfigAPI, PluginItem, TransformOptions } from '@babel/core'
86
-
87
- interface RequiredTransformOptions {
88
- plugins: PluginItem[]
89
- presets: PluginItem[]
90
- }
91
-
92
- const defaultConfigFunc: (
93
- api: ConfigAPI
94
- ) => TransformOptions & RequiredTransformOptions
95
-
96
- export = defaultConfigFunc
97
- }
1
+ declare const _default: any;
2
+ export = _default;
3
+ //# sourceMappingURL=index.d.ts.map
data/package/index.ts ADDED
@@ -0,0 +1,52 @@
1
+ /* eslint global-require: 0 */
2
+ /* eslint import/no-dynamic-require: 0 */
3
+
4
+ const webpackMerge = require("webpack-merge")
5
+ import { resolve } from "path"
6
+ import { existsSync } from "fs"
7
+ // @ts-ignore: webpack is an optional peer dependency (using type-only import)
8
+ import type { Configuration } from "webpack"
9
+ const config = require("./config")
10
+ const baseConfig = require("./environments/base")
11
+ const devServer = require("./dev_server")
12
+ const env = require("./env")
13
+ const { moduleExists, canProcess } = require("./utils/helpers")
14
+ const inliningCss = require("./utils/inliningCss")
15
+
16
+ const rulesPath = resolve(__dirname, "rules", `${config.assets_bundler}.js`)
17
+ const rules = require(rulesPath)
18
+
19
+ const generateWebpackConfig = (extraConfig: Configuration = {}, ...extraArgs: any[]): Configuration => {
20
+ if (extraArgs.length > 0) {
21
+ throw new Error(
22
+ `Invalid usage: generateWebpackConfig() accepts only one configuration object.\n\n` +
23
+ `You passed ${extraArgs.length + 1} arguments. Only one extra config may be passed here - use webpack-merge to merge configs before passing them to Shakapacker.\n\n` +
24
+ `Example:\n` +
25
+ ` const { merge } = require('webpack-merge')\n` +
26
+ ` const mergedConfig = merge(config1, config2, config3)\n` +
27
+ ` const finalConfig = generateWebpackConfig(mergedConfig)\n\n` +
28
+ `Or if using ES6:\n` +
29
+ ` import { merge } from 'webpack-merge'\n` +
30
+ ` const finalConfig = generateWebpackConfig(merge(config1, config2))`
31
+ )
32
+ }
33
+
34
+ const { nodeEnv } = env
35
+ const path = resolve(__dirname, "environments", `${nodeEnv}.js`)
36
+ const environmentConfig = existsSync(path) ? require(path) : baseConfig
37
+
38
+ return webpackMerge.merge({}, environmentConfig, extraConfig)
39
+ }
40
+
41
+ export = {
42
+ config, // shakapacker.yml
43
+ devServer,
44
+ generateWebpackConfig,
45
+ baseConfig,
46
+ env,
47
+ rules,
48
+ moduleExists,
49
+ canProcess,
50
+ inliningCss,
51
+ ...webpackMerge
52
+ }
@@ -0,0 +1,28 @@
1
+ // @ts-ignore: webpack is an optional peer dependency (using type-only import)
2
+ import type { LoaderDefinitionFunction } from 'webpack'
3
+
4
+ export interface ShakapackerLoaderOptions {
5
+ [key: string]: any
6
+ }
7
+
8
+ export interface ShakapackerLoader {
9
+ loader: string
10
+ options?: ShakapackerLoaderOptions
11
+ }
12
+
13
+ export type LoaderResolver = (name: string) => string
14
+
15
+ export interface LoaderConfig {
16
+ test: RegExp | ((value: string) => boolean)
17
+ use: Array<string | ShakapackerLoader | LoaderDefinitionFunction>
18
+ exclude?: RegExp | string | Array<string>
19
+ include?: RegExp | string | Array<string>
20
+ type?: string
21
+ generator?: {
22
+ filename?: string
23
+ publicPath?: string
24
+ }
25
+ }
26
+
27
+ export function resolveLoader(name: string): string
28
+ export function createLoader(config: LoaderConfig): LoaderConfig
@@ -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
+ }
@@ -0,0 +1,57 @@
1
+ const { requireOrError } = require("../utils/requireOrError")
2
+
3
+ const TerserPlugin = requireOrError("terser-webpack-plugin")
4
+ const { moduleExists } = require("../utils/helpers")
5
+
6
+ const tryCssMinimizer = (): unknown | null => {
7
+ if (
8
+ moduleExists("css-loader") &&
9
+ moduleExists("css-minimizer-webpack-plugin")
10
+ ) {
11
+ const CssMinimizerPlugin = requireOrError("css-minimizer-webpack-plugin")
12
+ return new CssMinimizerPlugin()
13
+ }
14
+
15
+ return null
16
+ }
17
+
18
+ interface OptimizationConfig {
19
+ minimizer: unknown[]
20
+ }
21
+
22
+ const getOptimization = (): OptimizationConfig => {
23
+ return {
24
+ minimizer: [
25
+ tryCssMinimizer(),
26
+ new TerserPlugin({
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,
32
+ terserOptions: {
33
+ parse: {
34
+ // Let terser parse ecma 8 code but always output
35
+ // ES5 compliant code for older browsers
36
+ ecma: 8
37
+ },
38
+ compress: {
39
+ ecma: 5,
40
+ warnings: false,
41
+ comparisons: false
42
+ },
43
+ mangle: { safari10: true },
44
+ output: {
45
+ ecma: 5,
46
+ comments: false,
47
+ ascii_only: true
48
+ }
49
+ }
50
+ })
51
+ ].filter(Boolean)
52
+ }
53
+ }
54
+
55
+ export = {
56
+ getOptimization
57
+ }
@@ -0,0 +1,103 @@
1
+ const { requireOrError } = require("../utils/requireOrError")
2
+
3
+ const { RspackManifestPlugin } = requireOrError("rspack-manifest-plugin")
4
+ const rspack = requireOrError("@rspack/core")
5
+ const config = require("../config")
6
+ const { isProduction } = require("../env")
7
+ const { moduleExists } = require("../utils/helpers")
8
+
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[] => {
25
+ const plugins = [
26
+ new rspack.EnvironmentPlugin(process.env),
27
+ new RspackManifestPlugin({
28
+ fileName: config.manifestPath.split("/").pop(), // Get just the filename
29
+ publicPath: config.publicPathWithoutCDN,
30
+ writeToFileEmit: true,
31
+ // rspack-manifest-plugin uses different option names than webpack-assets-manifest
32
+ generate: (seed: Manifest | null, files: ManifestFile[], entrypoints: Record<string, string[]>) => {
33
+ const manifest: Manifest = seed || {}
34
+
35
+ // Add files mapping first
36
+ files.forEach((file) => {
37
+ manifest[file.name] = file.path
38
+ })
39
+
40
+ // Add entrypoints information compatible with Shakapacker expectations
41
+ const entrypointsManifest: Record<string, { assets: EntrypointAssets }> = {}
42
+ Object.entries(entrypoints).forEach(
43
+ ([entrypointName, entrypointFiles]) => {
44
+ const jsFiles = entrypointFiles
45
+ .filter(
46
+ (file) => file.endsWith(".js") && !file.includes(".hot-update.")
47
+ )
48
+ .map((file) => config.publicPathWithoutCDN + file)
49
+ const cssFiles = entrypointFiles
50
+ .filter(
51
+ (file) =>
52
+ file.endsWith(".css") && !file.includes(".hot-update.")
53
+ )
54
+ .map((file) => config.publicPathWithoutCDN + file)
55
+
56
+ entrypointsManifest[entrypointName] = {
57
+ assets: {
58
+ js: jsFiles,
59
+ css: cssFiles
60
+ }
61
+ }
62
+ }
63
+ )
64
+ manifest.entrypoints = entrypointsManifest
65
+
66
+ return manifest
67
+ }
68
+ })
69
+ ]
70
+
71
+ if (moduleExists("css-loader")) {
72
+ const hash = isProduction || config.useContentHash ? "-[contenthash:8]" : ""
73
+ // Use Rspack's built-in CSS extraction
74
+ const { CssExtractRspackPlugin } = rspack
75
+ plugins.push(
76
+ new CssExtractRspackPlugin({
77
+ filename: `css/[name]${hash}.css`,
78
+ chunkFilename: `css/[id]${hash}.css`,
79
+ // For projects where css ordering has been mitigated through consistent use of scoping or naming conventions,
80
+ // the css order warnings can be disabled by setting the ignoreOrder flag.
81
+ ignoreOrder: config.css_extract_ignore_order_warnings,
82
+ // Force writing CSS files to disk in development for Rails compatibility
83
+ emit: true
84
+ })
85
+ )
86
+ }
87
+
88
+ // Use Rspack's built-in SubresourceIntegrityPlugin
89
+ if (config.integrity.enabled) {
90
+ plugins.push(
91
+ new rspack.SubresourceIntegrityPlugin({
92
+ hashFuncNames: config.integrity.hash_functions,
93
+ enabled: isProduction
94
+ })
95
+ )
96
+ }
97
+
98
+ return plugins
99
+ }
100
+
101
+ export = {
102
+ getPlugins
103
+ }