shakapacker 8.0.2 → 9.2.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 (198) 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 +9 -4
  9. data/.github/workflows/generator.yml +32 -10
  10. data/.github/workflows/node.yml +23 -1
  11. data/.github/workflows/ruby.yml +33 -2
  12. data/.github/workflows/test-bundlers.yml +170 -0
  13. data/.gitignore +20 -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 +302 -16
  20. data/CLAUDE.md +29 -0
  21. data/CONTRIBUTING.md +138 -20
  22. data/Gemfile.lock +83 -89
  23. data/README.md +343 -105
  24. data/Rakefile +39 -4
  25. data/TODO.md +50 -0
  26. data/TODO_v9.md +87 -0
  27. data/bin/export-bundler-config +11 -0
  28. data/conductor-setup.sh +70 -0
  29. data/conductor.json +7 -0
  30. data/docs/cdn_setup.md +379 -0
  31. data/docs/common-upgrades.md +615 -0
  32. data/docs/css-modules-export-mode.md +512 -0
  33. data/docs/deployment.md +62 -9
  34. data/docs/optional-peer-dependencies.md +198 -0
  35. data/docs/peer-dependencies.md +60 -0
  36. data/docs/react.md +6 -14
  37. data/docs/releasing.md +197 -0
  38. data/docs/rspack.md +190 -0
  39. data/docs/rspack_migration_guide.md +305 -0
  40. data/docs/subresource_integrity.md +54 -0
  41. data/docs/transpiler-migration.md +209 -0
  42. data/docs/transpiler-performance.md +179 -0
  43. data/docs/troubleshooting.md +157 -22
  44. data/docs/typescript-migration.md +379 -0
  45. data/docs/typescript.md +99 -0
  46. data/docs/using_esbuild_loader.md +3 -3
  47. data/docs/using_swc_loader.md +112 -10
  48. data/docs/v6_upgrade.md +10 -0
  49. data/docs/v8_upgrade.md +3 -5
  50. data/docs/v9_upgrade.md +458 -0
  51. data/gemfiles/Gemfile-rails.6.0.x +2 -1
  52. data/gemfiles/Gemfile-rails.6.1.x +1 -1
  53. data/gemfiles/Gemfile-rails.7.0.x +2 -2
  54. data/gemfiles/Gemfile-rails.7.1.x +1 -2
  55. data/gemfiles/Gemfile-rails.7.2.x +11 -0
  56. data/gemfiles/Gemfile-rails.8.0.x +11 -0
  57. data/lib/install/bin/export-bundler-config +11 -0
  58. data/lib/install/bin/shakapacker +4 -6
  59. data/lib/install/bin/shakapacker-dev-server +1 -1
  60. data/lib/install/config/rspack/rspack.config.js +6 -0
  61. data/lib/install/config/rspack/rspack.config.ts +7 -0
  62. data/lib/install/config/shakapacker.yml +25 -5
  63. data/lib/install/config/webpack/webpack.config.ts +7 -0
  64. data/lib/install/package.json +38 -0
  65. data/lib/install/template.rb +194 -44
  66. data/lib/shakapacker/bundler_switcher.rb +329 -0
  67. data/lib/shakapacker/compiler.rb +2 -1
  68. data/lib/shakapacker/compiler_strategy.rb +2 -2
  69. data/lib/shakapacker/configuration.rb +173 -2
  70. data/lib/shakapacker/dev_server_runner.rb +29 -8
  71. data/lib/shakapacker/digest_strategy.rb +2 -1
  72. data/lib/shakapacker/doctor.rb +905 -0
  73. data/lib/shakapacker/helper.rb +64 -16
  74. data/lib/shakapacker/manifest.rb +10 -3
  75. data/lib/shakapacker/mtime_strategy.rb +1 -1
  76. data/lib/shakapacker/railtie.rb +4 -4
  77. data/lib/shakapacker/rspack_runner.rb +19 -0
  78. data/lib/shakapacker/runner.rb +159 -10
  79. data/lib/shakapacker/swc_migrator.rb +384 -0
  80. data/lib/shakapacker/utils/manager.rb +15 -2
  81. data/lib/shakapacker/version.rb +1 -1
  82. data/lib/shakapacker/version_checker.rb +2 -2
  83. data/lib/shakapacker/webpack_runner.rb +6 -43
  84. data/lib/shakapacker.rb +22 -11
  85. data/lib/tasks/shakapacker/doctor.rake +8 -0
  86. data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
  87. data/lib/tasks/shakapacker/install.rake +12 -2
  88. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  89. data/lib/tasks/shakapacker/switch_bundler.rake +82 -0
  90. data/lib/tasks/shakapacker.rake +2 -0
  91. data/package/.npmignore +4 -0
  92. data/package/babel/preset.ts +56 -0
  93. data/package/config.ts +175 -0
  94. data/package/configExporter/cli.ts +683 -0
  95. data/package/configExporter/configDocs.ts +102 -0
  96. data/package/configExporter/fileWriter.ts +92 -0
  97. data/package/configExporter/index.ts +5 -0
  98. data/package/configExporter/types.ts +36 -0
  99. data/package/configExporter/yamlSerializer.ts +266 -0
  100. data/package/{dev_server.js → dev_server.ts} +8 -5
  101. data/package/env.ts +92 -0
  102. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +30 -0
  103. data/package/environments/{base.js → base.ts} +56 -60
  104. data/package/environments/development.ts +90 -0
  105. data/package/environments/production.ts +80 -0
  106. data/package/environments/test.ts +53 -0
  107. data/package/environments/types.ts +98 -0
  108. data/package/esbuild/index.ts +42 -0
  109. data/package/index.d.ts +3 -60
  110. data/package/index.ts +55 -0
  111. data/package/loaders.d.ts +28 -0
  112. data/package/optimization/rspack.ts +36 -0
  113. data/package/optimization/webpack.ts +57 -0
  114. data/package/plugins/rspack.ts +103 -0
  115. data/package/plugins/webpack.ts +62 -0
  116. data/package/rspack/index.ts +64 -0
  117. data/package/rules/{babel.js → babel.ts} +2 -2
  118. data/package/rules/{coffee.js → coffee.ts} +1 -1
  119. data/package/rules/css.ts +3 -0
  120. data/package/rules/{erb.js → erb.ts} +1 -1
  121. data/package/rules/esbuild.ts +10 -0
  122. data/package/rules/file.ts +40 -0
  123. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  124. data/package/rules/{less.js → less.ts} +4 -4
  125. data/package/rules/raw.ts +25 -0
  126. data/package/rules/rspack.ts +176 -0
  127. data/package/rules/{sass.js → sass.ts} +7 -3
  128. data/package/rules/{stylus.js → stylus.ts} +4 -8
  129. data/package/rules/swc.ts +10 -0
  130. data/package/rules/webpack.ts +16 -0
  131. data/package/swc/index.ts +56 -0
  132. data/package/types/README.md +88 -0
  133. data/package/types/index.ts +61 -0
  134. data/package/types.ts +108 -0
  135. data/package/utils/configPath.ts +6 -0
  136. data/package/utils/debug.ts +49 -0
  137. data/package/utils/defaultConfigPath.ts +4 -0
  138. data/package/utils/errorCodes.ts +219 -0
  139. data/package/utils/errorHelpers.ts +143 -0
  140. data/package/utils/getStyleRule.ts +64 -0
  141. data/package/utils/helpers.ts +85 -0
  142. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  143. data/package/utils/pathValidation.ts +139 -0
  144. data/package/utils/requireOrError.ts +15 -0
  145. data/package/utils/snakeToCamelCase.ts +5 -0
  146. data/package/utils/typeGuards.ts +342 -0
  147. data/package/utils/validateDependencies.ts +61 -0
  148. data/package/webpack-types.d.ts +33 -0
  149. data/package/webpackDevServerConfig.ts +117 -0
  150. data/package-lock.json +13047 -0
  151. data/package.json +154 -18
  152. data/scripts/remove-use-strict.js +45 -0
  153. data/scripts/type-check-no-emit.js +27 -0
  154. data/test/helpers.js +1 -1
  155. data/test/package/config.test.js +43 -0
  156. data/test/package/env.test.js +42 -7
  157. data/test/package/environments/base.test.js +5 -1
  158. data/test/package/rules/babel.test.js +16 -0
  159. data/test/package/rules/esbuild.test.js +1 -1
  160. data/test/package/rules/raw.test.js +40 -7
  161. data/test/package/rules/swc.test.js +1 -1
  162. data/test/package/rules/webpack.test.js +35 -0
  163. data/test/package/staging.test.js +4 -3
  164. data/test/package/transpiler-defaults.test.js +127 -0
  165. data/test/peer-dependencies.sh +85 -0
  166. data/test/scripts/remove-use-strict.test.js +125 -0
  167. data/test/typescript/build.test.js +118 -0
  168. data/test/typescript/environments.test.js +107 -0
  169. data/test/typescript/pathValidation.test.js +142 -0
  170. data/test/typescript/securityValidation.test.js +182 -0
  171. data/tools/README.md +124 -0
  172. data/tools/css-modules-v9-codemod.js +179 -0
  173. data/tsconfig.eslint.json +16 -0
  174. data/tsconfig.json +38 -0
  175. data/yarn.lock +4165 -2706
  176. metadata +129 -41
  177. data/package/babel/preset.js +0 -37
  178. data/package/config.js +0 -54
  179. data/package/env.js +0 -48
  180. data/package/environments/development.js +0 -13
  181. data/package/environments/production.js +0 -88
  182. data/package/environments/test.js +0 -3
  183. data/package/esbuild/index.js +0 -40
  184. data/package/index.js +0 -40
  185. data/package/rules/css.js +0 -3
  186. data/package/rules/esbuild.js +0 -10
  187. data/package/rules/file.js +0 -29
  188. data/package/rules/index.js +0 -20
  189. data/package/rules/raw.js +0 -5
  190. data/package/rules/swc.js +0 -10
  191. data/package/swc/index.js +0 -50
  192. data/package/utils/configPath.js +0 -4
  193. data/package/utils/defaultConfigPath.js +0 -2
  194. data/package/utils/getStyleRule.js +0 -40
  195. data/package/utils/helpers.js +0 -58
  196. data/package/utils/snakeToCamelCase.js +0 -5
  197. data/package/webpackDevServerConfig.js +0 -71
  198. data/test/package/rules/index.test.js +0 -16
@@ -1,22 +1,40 @@
1
1
  /* eslint global-require: 0 */
2
2
  /* eslint import/no-dynamic-require: 0 */
3
3
 
4
- const { existsSync, readdirSync } = require("fs")
5
4
  const { basename, dirname, join, relative, resolve } = require("path")
5
+ const { existsSync, readdirSync } = require("fs")
6
+ import { Dirent } from "fs"
6
7
  const extname = require("path-complete-extname")
7
- const WebpackAssetsManifest = require("webpack-assets-manifest")
8
- const webpack = require("webpack")
9
- const rules = require("../rules")
8
+ // @ts-ignore: webpack is an optional peer dependency (using type-only import)
9
+ import type { Configuration, Entry } from "webpack"
10
10
  const config = require("../config")
11
11
  const { isProduction } = require("../env")
12
- const { moduleExists } = require("../utils/helpers")
13
12
 
14
- const getFilesInDirectory = (dir, includeNested) => {
13
+ const pluginsPath = resolve(
14
+ __dirname,
15
+ "..",
16
+ "plugins",
17
+ `${config.assets_bundler}.js`
18
+ )
19
+ const { getPlugins } = require(pluginsPath)
20
+ const rulesPath = resolve(
21
+ __dirname,
22
+ "..",
23
+ "rules",
24
+ `${config.assets_bundler}.js`
25
+ )
26
+ const rules = require(rulesPath)
27
+
28
+ // Don't use contentHash except for production for performance
29
+ // https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
30
+ const hash = isProduction || config.useContentHash ? "-[contenthash]" : ""
31
+
32
+ const getFilesInDirectory = (dir: string, includeNested: boolean): string[] => {
15
33
  if (!existsSync(dir)) {
16
34
  return []
17
35
  }
18
36
 
19
- return readdirSync(dir, { withFileTypes: true }).flatMap((dirent) => {
37
+ return readdirSync(dir, { withFileTypes: true }).flatMap((dirent: Dirent) => {
20
38
  const filePath = join(dir, dirent.name)
21
39
 
22
40
  if (dirent.isDirectory() && includeNested) {
@@ -29,84 +47,56 @@ const getFilesInDirectory = (dir, includeNested) => {
29
47
  })
30
48
  }
31
49
 
32
- const getEntryObject = () => {
33
- const entries = {}
50
+ const getEntryObject = (): Entry => {
51
+ const entries: Entry = {}
34
52
  const rootPath = join(config.source_path, config.source_entry_path)
35
53
  if (config.source_entry_path === "/" && config.nested_entries) {
36
54
  throw new Error(
37
- "Your shakapacker config specified using a source_entry_path of '/' with 'nested_entries' == " +
38
- "'true'. Doing this would result in packs for every one of your source files"
55
+ `Invalid Shakapacker configuration detected!\n\n` +
56
+ `You have set source_entry_path to '/' with nested_entries enabled.\n` +
57
+ `This would create webpack entry points for EVERY file in your source directory,\n` +
58
+ `which would severely impact build performance.\n\n` +
59
+ `To fix this issue, either:\n` +
60
+ `1. Set 'nested_entries: false' in your shakapacker.yml\n` +
61
+ `2. Change 'source_entry_path' to a specific subdirectory (e.g., 'packs')\n` +
62
+ `3. Or use both options for better organization of your entry points`
39
63
  )
40
64
  }
41
65
 
42
66
  getFilesInDirectory(rootPath, config.nested_entries).forEach((path) => {
43
67
  const namespace = relative(join(rootPath), dirname(path))
44
68
  const name = join(namespace, basename(path, extname(path)))
45
- let assetPaths = resolve(path)
69
+ const assetPath: string = resolve(path)
46
70
 
47
71
  // Allows for multiple filetypes per entry (https://webpack.js.org/guides/entry-advanced/)
48
72
  // Transforms the config object value to an array with all values under the same name
49
- let previousPaths = entries[name]
73
+ const previousPaths = entries[name]
50
74
  if (previousPaths) {
51
- previousPaths = Array.isArray(previousPaths)
52
- ? previousPaths
53
- : [previousPaths]
54
- previousPaths.push(assetPaths)
55
- assetPaths = previousPaths
75
+ const pathArray = Array.isArray(previousPaths)
76
+ ? previousPaths as string[]
77
+ : [previousPaths as string]
78
+ pathArray.push(assetPath)
79
+ entries[name] = pathArray
80
+ } else {
81
+ entries[name] = assetPath
56
82
  }
57
-
58
- entries[name] = assetPaths
59
83
  })
60
84
 
61
85
  return entries
62
86
  }
63
87
 
64
- const getModulePaths = () => {
88
+ const getModulePaths = (): string[] => {
65
89
  const result = [resolve(config.source_path)]
66
90
 
67
91
  if (config.additional_paths) {
68
- config.additional_paths.forEach((path) => result.push(resolve(path)))
92
+ config.additional_paths.forEach((path: string) => result.push(resolve(path)))
69
93
  }
70
94
  result.push("node_modules")
71
95
 
72
96
  return result
73
97
  }
74
98
 
75
- const getPlugins = () => {
76
- const plugins = [
77
- new webpack.EnvironmentPlugin(process.env),
78
- new WebpackAssetsManifest({
79
- entrypoints: true,
80
- writeToDisk: true,
81
- output: config.manifestPath,
82
- entrypointsUseAssets: true,
83
- publicPath: config.publicPathWithoutCDN
84
- })
85
- ]
86
-
87
- if (moduleExists("css-loader") && moduleExists("mini-css-extract-plugin")) {
88
- const hash = isProduction || config.useContentHash ? "-[contenthash:8]" : ""
89
- const MiniCssExtractPlugin = require("mini-css-extract-plugin")
90
- plugins.push(
91
- new MiniCssExtractPlugin({
92
- filename: `css/[name]${hash}.css`,
93
- chunkFilename: `css/[id]${hash}.css`,
94
- // For projects where css ordering has been mitigated through consistent use of scoping or naming conventions,
95
- // the css order warnings can be disabled by setting the ignoreOrder flag.
96
- // Read: https://stackoverflow.com/questions/51971857/mini-css-extract-plugin-warning-in-chunk-chunkname-mini-css-extract-plugin-con
97
- ignoreOrder: config.css_extract_ignore_order_warnings
98
- })
99
- )
100
- }
101
-
102
- return plugins
103
- }
104
-
105
- // Don't use contentHash except for production for performance
106
- // https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
107
- const hash = isProduction || config.useContentHash ? "-[contenthash]" : ""
108
-
109
- module.exports = {
99
+ const baseConfig: Configuration = {
110
100
  mode: "production",
111
101
  output: {
112
102
  filename: `js/[name]${hash}.js`,
@@ -115,7 +105,12 @@ module.exports = {
115
105
  // https://webpack.js.org/configuration/output/#outputhotupdatechunkfilename
116
106
  hotUpdateChunkFilename: "js/[id].[fullhash].hot-update.js",
117
107
  path: config.outputPath,
118
- publicPath: config.publicPath
108
+ publicPath: config.publicPath,
109
+
110
+ // This is required for SRI to work.
111
+ crossOriginLoading: config.integrity && config.integrity.enabled
112
+ ? (config.integrity.cross_origin as "anonymous" | "use-credentials" | false)
113
+ : false
119
114
  },
120
115
  entry: getEntryObject(),
121
116
  resolve: {
@@ -131,12 +126,13 @@ module.exports = {
131
126
 
132
127
  optimization: {
133
128
  splitChunks: { chunks: "all" },
134
-
135
129
  runtimeChunk: "single"
136
130
  },
137
131
 
138
132
  module: {
139
- strictExportPresence: true,
140
133
  rules
141
134
  }
142
135
  }
136
+
137
+ export = baseConfig
138
+
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Development environment configuration for webpack and rspack bundlers
3
+ * @module environments/development
4
+ */
5
+
6
+ const { merge } = require("webpack-merge")
7
+ const config = require("../config")
8
+ const baseConfig = require("./base")
9
+ const webpackDevServerConfig = require("../webpackDevServerConfig")
10
+ const { runningWebpackDevServer } = require("../env")
11
+ const { moduleExists } = require("../utils/helpers")
12
+ import type {
13
+ WebpackConfigWithDevServer,
14
+ RspackConfigWithDevServer,
15
+ ReactRefreshWebpackPlugin,
16
+ ReactRefreshRspackPlugin
17
+ } from "./types"
18
+
19
+ /**
20
+ * Base development configuration shared between webpack and rspack
21
+ */
22
+ const baseDevConfig = {
23
+ mode: "development" as const,
24
+ devtool: "cheap-module-source-map" as const
25
+ }
26
+
27
+ /**
28
+ * Generate webpack-specific development configuration
29
+ * @returns Webpack configuration with dev server settings
30
+ */
31
+ const webpackDevConfig = (): WebpackConfigWithDevServer => {
32
+ const webpackConfig: WebpackConfigWithDevServer = {
33
+ ...baseDevConfig,
34
+ ...(runningWebpackDevServer && { devServer: webpackDevServerConfig() })
35
+ }
36
+
37
+ const devServerConfig = webpackDevServerConfig()
38
+ if (
39
+ runningWebpackDevServer &&
40
+ devServerConfig.hot &&
41
+ moduleExists("@pmmmwh/react-refresh-webpack-plugin")
42
+ ) {
43
+ // eslint-disable-next-line global-require
44
+ const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin")
45
+ webpackConfig.plugins = [
46
+ ...(webpackConfig.plugins || []),
47
+ new ReactRefreshWebpackPlugin()
48
+ ]
49
+ }
50
+
51
+ return webpackConfig
52
+ }
53
+
54
+ /**
55
+ * Generate rspack-specific development configuration
56
+ * @returns Rspack configuration with dev server settings
57
+ */
58
+ const rspackDevConfig = (): RspackConfigWithDevServer => {
59
+ const devServerConfig = webpackDevServerConfig()
60
+ const rspackConfig: RspackConfigWithDevServer = {
61
+ ...baseDevConfig,
62
+ devServer: {
63
+ ...devServerConfig,
64
+ devMiddleware: {
65
+ ...(devServerConfig.devMiddleware || {}),
66
+ writeToDisk: (filePath: string) => !filePath.includes(".hot-update.")
67
+ }
68
+ }
69
+ }
70
+
71
+ if (
72
+ runningWebpackDevServer &&
73
+ devServerConfig.hot &&
74
+ moduleExists("@rspack/plugin-react-refresh")
75
+ ) {
76
+ // eslint-disable-next-line global-require
77
+ const ReactRefreshPlugin = require("@rspack/plugin-react-refresh")
78
+ rspackConfig.plugins = [
79
+ ...(rspackConfig.plugins || []),
80
+ new ReactRefreshPlugin()
81
+ ]
82
+ }
83
+
84
+ return rspackConfig
85
+ }
86
+
87
+ const bundlerConfig =
88
+ config.assets_bundler === "rspack" ? rspackDevConfig() : webpackDevConfig()
89
+
90
+ module.exports = merge(baseConfig, bundlerConfig)
@@ -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,98 @@
1
+ /**
2
+ * Type definitions for environment configurations
3
+ * These types are exported for consumer use
4
+ */
5
+
6
+ import type {
7
+ Configuration as WebpackConfiguration,
8
+ WebpackPluginInstance
9
+ } from "webpack"
10
+ import type { Configuration as DevServerConfiguration } from "webpack-dev-server"
11
+ import type { RspackPluginInstance as ImportedRspackPluginInstance } from "@rspack/core"
12
+
13
+ /**
14
+ * Webpack configuration extended with dev server support
15
+ */
16
+ export interface WebpackConfigWithDevServer extends WebpackConfiguration {
17
+ devServer?: DevServerConfiguration
18
+ plugins?: WebpackPluginInstance[]
19
+ }
20
+
21
+ /**
22
+ * Rspack plugin instance interface
23
+ * Uses the RspackPluginInstance type from @rspack/core
24
+ */
25
+ export type RspackPluginInstance = ImportedRspackPluginInstance
26
+
27
+ /**
28
+ * Rspack plugin type alias
29
+ * @deprecated Use RspackPluginInstance instead
30
+ * Retained for backward compatibility with existing code
31
+ */
32
+ export type RspackPlugin = RspackPluginInstance
33
+
34
+ /**
35
+ * Rspack dev server configuration
36
+ * Similar to webpack-dev-server but with some rspack-specific options
37
+ */
38
+ export interface RspackDevServerConfig {
39
+ port?: number | string | "auto"
40
+ host?: string
41
+ hot?: boolean | "only"
42
+ historyApiFallback?: boolean | Record<string, unknown>
43
+ headers?: Record<string, string | string[]>
44
+ proxy?: unknown
45
+ static?: boolean | string | Array<string | Record<string, unknown>>
46
+ devMiddleware?: {
47
+ writeToDisk?: boolean | ((filePath: string) => boolean)
48
+ publicPath?: string
49
+ [key: string]: unknown
50
+ }
51
+ [key: string]: unknown
52
+ }
53
+
54
+ /**
55
+ * Rspack configuration with dev server support
56
+ */
57
+ export interface RspackConfigWithDevServer {
58
+ mode?: "development" | "production" | "none"
59
+ devtool?: string | false
60
+ devServer?: RspackDevServerConfig
61
+ plugins?: RspackPluginInstance[]
62
+ module?: WebpackConfiguration["module"]
63
+ resolve?: WebpackConfiguration["resolve"]
64
+ entry?: WebpackConfiguration["entry"]
65
+ output?: WebpackConfiguration["output"]
66
+ optimization?: WebpackConfiguration["optimization"]
67
+ [key: string]: unknown
68
+ }
69
+
70
+ /**
71
+ * Compression plugin options interface
72
+ */
73
+ export interface CompressionPluginOptions {
74
+ filename: string
75
+ algorithm: string | "gzip" | "brotliCompress"
76
+ test: RegExp
77
+ threshold?: number
78
+ minRatio?: number
79
+ deleteOriginalAssets?: boolean
80
+ }
81
+
82
+ /**
83
+ * Compression plugin constructor type
84
+ */
85
+ export type CompressionPluginConstructor = new (
86
+ options: CompressionPluginOptions
87
+ ) => WebpackPluginInstance
88
+
89
+ /**
90
+ * React Refresh plugin types
91
+ */
92
+ export interface ReactRefreshWebpackPlugin {
93
+ new (options?: Record<string, unknown>): WebpackPluginInstance
94
+ }
95
+
96
+ export interface ReactRefreshRspackPlugin {
97
+ new (options?: Record<string, unknown>): RspackPluginInstance
98
+ }
@@ -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,60 +1,3 @@
1
- declare module 'shakapacker' {
2
- import { Configuration } from 'webpack'
3
-
4
- export interface Config {
5
- source_path: string
6
- source_entry_path: string
7
- nested_entries: boolean
8
- css_extract_ignore_order_warnings: boolean
9
- public_root_path: string
10
- public_output_path: string
11
- cache_path: string
12
- webpack_compile_output: boolean
13
- shakapacker_precompile: boolean
14
- additional_paths: string[]
15
- cache_manifest: boolean
16
- webpack_loader: string
17
- ensure_consistent_versioning: boolean
18
- compiler_strategy: string
19
- useContentHash: boolean
20
- compile: boolean,
21
- outputPath: string
22
- publicPath: string
23
- publicPathWithoutCDN: string
24
- manifestPath: string
25
- }
26
-
27
- export interface Env {
28
- railsEnv: string
29
- nodeEnv: string
30
- isProduction: boolean
31
- isDevelopment: boolean
32
- runningWebpackDevServer: boolean
33
- }
34
-
35
- export const config: Config
36
- export const devServer: Record<string, unknown>
37
- export function generateWebpackConfig(extraConfig?: Configuration): Configuration
38
- export const baseConfig: Configuration
39
- export const env: Env
40
- export const rules: Record<string, unknown>
41
- export function moduleExists(packageName: string): boolean
42
- export function canProcess<T = unknown>(rule: string, fn: (modulePath: string) => T): T | null
43
- export const inliningCss: boolean
44
- export * from 'webpack-merge'
45
- }
46
-
47
- declare module 'shakapacker/package/babel/preset.js' {
48
- import { ConfigAPI, PluginItem, TransformOptions } from '@babel/core'
49
-
50
- interface RequiredTransformOptions {
51
- plugins: PluginItem[]
52
- presets: PluginItem[]
53
- }
54
-
55
- const defaultConfigFunc: (
56
- api: ConfigAPI
57
- ) => TransformOptions & RequiredTransformOptions
58
-
59
- export = defaultConfigFunc
60
- }
1
+ declare const _default: any;
2
+ export = _default;
3
+ //# sourceMappingURL=index.d.ts.map
data/package/index.ts ADDED
@@ -0,0 +1,55 @@
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 = (
20
+ extraConfig: Configuration = {},
21
+ ...extraArgs: unknown[]
22
+ ): Configuration => {
23
+ if (extraArgs.length > 0) {
24
+ throw new Error(
25
+ `Invalid usage: generateWebpackConfig() accepts only one configuration object.\n\n` +
26
+ `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` +
27
+ `Example:\n` +
28
+ ` const { merge } = require('webpack-merge')\n` +
29
+ ` const mergedConfig = merge(config1, config2, config3)\n` +
30
+ ` const finalConfig = generateWebpackConfig(mergedConfig)\n\n` +
31
+ `Or if using ES6:\n` +
32
+ ` import { merge } from 'webpack-merge'\n` +
33
+ ` const finalConfig = generateWebpackConfig(merge(config1, config2))`
34
+ )
35
+ }
36
+
37
+ const { nodeEnv } = env
38
+ const path = resolve(__dirname, "environments", `${nodeEnv}.js`)
39
+ const environmentConfig = existsSync(path) ? require(path) : baseConfig
40
+
41
+ return webpackMerge.merge({}, environmentConfig, extraConfig)
42
+ }
43
+
44
+ export = {
45
+ config, // shakapacker.yml
46
+ devServer,
47
+ generateWebpackConfig,
48
+ baseConfig,
49
+ env,
50
+ rules,
51
+ moduleExists,
52
+ canProcess,
53
+ inliningCss,
54
+ ...webpackMerge
55
+ }