shakapacker 9.0.0.beta.3 → 9.0.0.beta.5

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dummy.yml +4 -0
  3. data/.github/workflows/generator.yml +7 -0
  4. data/.github/workflows/node.yml +22 -0
  5. data/.github/workflows/ruby.yml +11 -0
  6. data/.github/workflows/test-bundlers.yml +18 -0
  7. data/.gitignore +20 -0
  8. data/.yalcignore +26 -0
  9. data/CHANGELOG.md +58 -19
  10. data/Gemfile.lock +1 -1
  11. data/README.md +3 -1
  12. data/docs/peer-dependencies.md +23 -3
  13. data/docs/transpiler-performance.md +179 -0
  14. data/docs/typescript.md +99 -0
  15. data/docs/v9_upgrade.md +58 -2
  16. data/lib/install/config/shakapacker.yml +4 -2
  17. data/lib/install/package.json +8 -0
  18. data/lib/install/template.rb +128 -51
  19. data/lib/shakapacker/configuration.rb +58 -1
  20. data/lib/shakapacker/doctor.rb +752 -0
  21. data/lib/shakapacker/swc_migrator.rb +292 -0
  22. data/lib/shakapacker/version.rb +1 -1
  23. data/lib/shakapacker.rb +1 -0
  24. data/lib/tasks/shakapacker/doctor.rake +8 -0
  25. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  26. data/lib/tasks/shakapacker.rake +1 -0
  27. data/package/config.ts +162 -0
  28. data/package/{dev_server.js → dev_server.ts} +8 -5
  29. data/package/env.ts +67 -0
  30. data/package/environments/base.js +21 -31
  31. data/package/environments/base.ts +137 -0
  32. data/package/index.d.ts +3 -150
  33. data/package/{index.js → index.ts} +17 -8
  34. data/package/loaders.d.ts +27 -0
  35. data/package/types.ts +108 -0
  36. data/package/utils/configPath.ts +6 -0
  37. data/package/utils/{debug.js → debug.ts} +7 -7
  38. data/package/utils/defaultConfigPath.ts +4 -0
  39. data/package/utils/errorHelpers.ts +77 -0
  40. data/package/utils/{getStyleRule.js → getStyleRule.ts} +17 -20
  41. data/package/utils/helpers.ts +85 -0
  42. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  43. data/package/utils/{requireOrError.js → requireOrError.ts} +2 -2
  44. data/package/utils/snakeToCamelCase.ts +5 -0
  45. data/package/utils/typeGuards.ts +228 -0
  46. data/package/utils/{validateDependencies.js → validateDependencies.ts} +4 -4
  47. data/package/webpack-types.d.ts +32 -0
  48. data/package/webpackDevServerConfig.ts +117 -0
  49. data/package.json +6 -2
  50. data/test/package/rules/babel.test.js +16 -0
  51. data/test/typescript/build.test.js +117 -0
  52. data/tsconfig.json +39 -0
  53. data/yarn.lock +5 -5
  54. metadata +32 -18
  55. data/package/config.js +0 -80
  56. data/package/env.js +0 -48
  57. data/package/utils/configPath.js +0 -4
  58. data/package/utils/defaultConfigPath.js +0 -2
  59. data/package/utils/helpers.js +0 -62
  60. data/package/utils/snakeToCamelCase.js +0 -5
  61. data/package/utils/validateCssModulesConfig.js +0 -91
  62. data/package/webpackDevServerConfig.js +0 -73
  63. data/package-lock.json +0 -11966
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Error handling utilities for consistent error management
3
+ */
4
+
5
+ /**
6
+ * Checks if an error is a file not found error (ENOENT)
7
+ */
8
+ export function isFileNotFoundError(error: unknown): boolean {
9
+ return (
10
+ error !== null &&
11
+ typeof error === 'object' &&
12
+ 'code' in error &&
13
+ (error as NodeJS.ErrnoException).code === 'ENOENT'
14
+ )
15
+ }
16
+
17
+ /**
18
+ * Checks if an error is a module not found error
19
+ */
20
+ export function isModuleNotFoundError(error: unknown): boolean {
21
+ return (
22
+ error !== null &&
23
+ typeof error === 'object' &&
24
+ 'code' in error &&
25
+ (error as NodeJS.ErrnoException).code === 'MODULE_NOT_FOUND'
26
+ )
27
+ }
28
+
29
+ /**
30
+ * Creates a consistent error message for file operations
31
+ */
32
+ export function createFileOperationError(
33
+ operation: 'read' | 'write' | 'delete',
34
+ filePath: string,
35
+ details?: string
36
+ ): Error {
37
+ const baseMessage = `Failed to ${operation} file at path '${filePath}'`
38
+ const errorDetails = details ? ` - ${details}` : ''
39
+ const suggestion = operation === 'read'
40
+ ? ' (check if file exists and permissions are correct)'
41
+ : operation === 'write'
42
+ ? ' (check write permissions and disk space)'
43
+ : ' (check permissions)'
44
+ return new Error(`${baseMessage}${errorDetails}${suggestion}`)
45
+ }
46
+
47
+ /**
48
+ * Safely gets error message from unknown error type
49
+ */
50
+ export function getErrorMessage(error: unknown): string {
51
+ if (error instanceof Error) {
52
+ // Include stack trace for better debugging in development
53
+ const isDev = process.env.NODE_ENV === 'development'
54
+ return isDev && error.stack ? `${error.message}\n${error.stack}` : error.message
55
+ }
56
+ if (typeof error === 'string') {
57
+ return error
58
+ }
59
+ if (error && typeof error === 'object' && 'message' in error) {
60
+ return String((error as { message: unknown }).message)
61
+ }
62
+ // Provide more context for truly unknown errors
63
+ return `Unknown error occurred (type: ${typeof error}, value: ${JSON.stringify(error)})`
64
+ }
65
+
66
+ /**
67
+ * Type guard for NodeJS errors with errno
68
+ */
69
+ export function isNodeError(error: unknown): error is NodeJS.ErrnoException {
70
+ return (
71
+ error instanceof Error &&
72
+ 'code' in error &&
73
+ typeof (error as any).code === 'string'
74
+ )
75
+ }
76
+
77
+
@@ -3,12 +3,17 @@ const { canProcess, moduleExists } = require("./helpers")
3
3
  const { requireOrError } = require("./requireOrError")
4
4
  const config = require("../config")
5
5
  const inliningCss = require("./inliningCss")
6
- const { validateCssModulesConfig } = require("./validateCssModulesConfig")
7
6
 
8
- const getStyleRule = (test, preprocessors = []) => {
7
+ interface StyleRule {
8
+ test: RegExp
9
+ use: any[]
10
+ type?: string
11
+ }
12
+
13
+ const getStyleRule = (test: RegExp, preprocessors: any[] = []): StyleRule | null => {
9
14
  if (moduleExists("css-loader")) {
10
15
  const tryPostcss = () =>
11
- canProcess("postcss-loader", (loaderPath) => ({
16
+ canProcess("postcss-loader", (loaderPath: string) => ({
12
17
  loader: loaderPath,
13
18
  options: { sourceMap: true }
14
19
  }))
@@ -20,31 +25,23 @@ const getStyleRule = (test, preprocessors = []) => {
20
25
  ? requireOrError("@rspack/core").CssExtractRspackPlugin.loader
21
26
  : requireOrError("mini-css-extract-plugin").loader
22
27
 
23
- const cssLoaderOptions = {
24
- sourceMap: true,
25
- importLoaders: 2,
26
- modules: {
27
- auto: true,
28
- // v9 defaults: named exports with camelCase conversion
29
- namedExport: true,
30
- exportLocalsConvention: "camelCase"
31
- }
32
- }
33
-
34
- // Validate CSS modules configuration
35
- validateCssModulesConfig(cssLoaderOptions)
36
-
37
28
  const use = [
38
29
  inliningCss ? "style-loader" : extractionPlugin,
39
30
  {
40
31
  loader: require.resolve("css-loader"),
41
- options: cssLoaderOptions
32
+ options: {
33
+ sourceMap: true,
34
+ importLoaders: 2,
35
+ modules: {
36
+ auto: true
37
+ }
38
+ }
42
39
  },
43
40
  tryPostcss(),
44
41
  ...preprocessors
45
42
  ].filter(Boolean)
46
43
 
47
- const result = {
44
+ const result: StyleRule = {
48
45
  test,
49
46
  use
50
47
  }
@@ -59,4 +56,4 @@ const getStyleRule = (test, preprocessors = []) => {
59
56
  return null
60
57
  }
61
58
 
62
- module.exports = { getStyleRule }
59
+ export = { getStyleRule }
@@ -0,0 +1,85 @@
1
+ const { isModuleNotFoundError, getErrorMessage } = require("./errorHelpers")
2
+
3
+ const isBoolean = (str: string): boolean => /^true/.test(str) || /^false/.test(str)
4
+
5
+ const ensureTrailingSlash = (path: string): string => (path.endsWith("/") ? path : `${path}/`)
6
+
7
+ const resolvedPath = (packageName: string): string | null => {
8
+ try {
9
+ return require.resolve(packageName)
10
+ } catch (error: unknown) {
11
+ if (!isModuleNotFoundError(error)) {
12
+ throw error
13
+ }
14
+ return null
15
+ }
16
+ }
17
+
18
+ const moduleExists = (packageName: string): boolean => !!resolvedPath(packageName)
19
+
20
+ const canProcess = <T = unknown>(rule: string, fn: (modulePath: string) => T): T | null => {
21
+ const modulePath = resolvedPath(rule)
22
+
23
+ if (modulePath) {
24
+ return fn(modulePath)
25
+ }
26
+
27
+ return null
28
+ }
29
+
30
+ const loaderMatches = <T = unknown>(configLoader: string, loaderToCheck: string, fn: () => T): T | null => {
31
+ if (configLoader !== loaderToCheck) {
32
+ return null
33
+ }
34
+
35
+ const loaderName = `${configLoader}-loader`
36
+
37
+ if (!moduleExists(loaderName)) {
38
+ throw new Error(
39
+ `Your Shakapacker config specified using ${configLoader}, but ${loaderName} package is not installed.\n` +
40
+ `\nTo fix this issue, run one of the following commands:\n` +
41
+ ` npm install --save-dev ${loaderName}\n` +
42
+ ` yarn add --dev ${loaderName}\n` +
43
+ `\nOr change your 'javascript_transpiler' setting in shakapacker.yml to use a different loader.`
44
+ )
45
+ }
46
+
47
+ return fn()
48
+ }
49
+
50
+ const packageFullVersion = (packageName: string): string => {
51
+ try {
52
+ // eslint-disable-next-line import/no-dynamic-require
53
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
54
+ // eslint-disable-next-line import/no-dynamic-require, global-require
55
+ const packageJson = require(packageJsonPath) as { version: string }
56
+ return packageJson.version
57
+ } catch (error: any) {
58
+ // Re-throw the error with proper code to maintain compatibility with babel preset
59
+ // The preset expects MODULE_NOT_FOUND errors to handle missing core-js gracefully
60
+ if (error.code === "MODULE_NOT_FOUND") {
61
+ throw error
62
+ }
63
+ // For other errors, warn and re-throw
64
+ console.warn(
65
+ `[SHAKAPACKER WARNING] Failed to get version for package ${packageName}: ${getErrorMessage(error)}`
66
+ )
67
+ throw error
68
+ }
69
+ }
70
+
71
+ const packageMajorVersion = (packageName: string): string => {
72
+ const match = packageFullVersion(packageName).match(/^\d+/)
73
+ return match ? match[0] : "0"
74
+ }
75
+
76
+ export {
77
+ isBoolean,
78
+ ensureTrailingSlash,
79
+ canProcess,
80
+ moduleExists,
81
+ loaderMatches,
82
+ packageFullVersion,
83
+ packageMajorVersion,
84
+ resolvedPath
85
+ }
@@ -2,7 +2,7 @@ const { runningWebpackDevServer } = require("../env")
2
2
  const devServer = require("../dev_server")
3
3
 
4
4
  // This logic is tied to lib/shakapacker/instance.rb
5
- const inliningCss =
6
- runningWebpackDevServer && devServer.hmr && devServer.inline_css !== false
5
+ const inliningCss: boolean =
6
+ runningWebpackDevServer && !!devServer.hmr && devServer.inline_css !== false
7
7
 
8
- module.exports = inliningCss
8
+ export = inliningCss
@@ -2,7 +2,7 @@
2
2
  /* eslint import/no-dynamic-require: 0 */
3
3
  const config = require("../config")
4
4
 
5
- const requireOrError = (moduleName) => {
5
+ const requireOrError = (moduleName: string): any => {
6
6
  try {
7
7
  return require(moduleName)
8
8
  } catch (error) {
@@ -12,4 +12,4 @@ const requireOrError = (moduleName) => {
12
12
  }
13
13
  }
14
14
 
15
- module.exports = { requireOrError }
15
+ export = { requireOrError }
@@ -0,0 +1,5 @@
1
+ function snakeToCamelCase(s: string): string {
2
+ return s.replace(/(_\w)/g, (match) => match[1].toUpperCase())
3
+ }
4
+
5
+ export = snakeToCamelCase
@@ -0,0 +1,228 @@
1
+ import { Config, DevServerConfig, YamlConfig } from "../types"
2
+
3
+ // Cache for validated configs in production
4
+ const validatedConfigs = new WeakMap<object, boolean>()
5
+
6
+ // Only validate in development or when explicitly enabled
7
+ const shouldValidate = process.env.NODE_ENV !== 'production' || process.env.SHAKAPACKER_STRICT_VALIDATION === 'true'
8
+
9
+ /**
10
+ * Type guard to validate Config object at runtime
11
+ * In production, caches results for performance unless SHAKAPACKER_STRICT_VALIDATION is set
12
+ */
13
+ export function isValidConfig(obj: unknown): obj is Config {
14
+ if (typeof obj !== 'object' || obj === null) {
15
+ return false
16
+ }
17
+
18
+ // Quick return for production with cached results
19
+ if (!shouldValidate && validatedConfigs.has(obj as object)) {
20
+ return validatedConfigs.get(obj as object) as boolean
21
+ }
22
+
23
+ const config = obj as Record<string, unknown>
24
+
25
+ // Check required string fields
26
+ const requiredStringFields = [
27
+ 'source_path',
28
+ 'source_entry_path',
29
+ 'public_root_path',
30
+ 'public_output_path',
31
+ 'cache_path',
32
+ 'javascript_transpiler'
33
+ ]
34
+
35
+ for (const field of requiredStringFields) {
36
+ if (typeof config[field] !== 'string') {
37
+ // Cache negative result in production
38
+ if (!shouldValidate) {
39
+ validatedConfigs.set(obj as object, false)
40
+ }
41
+ return false
42
+ }
43
+ }
44
+
45
+ // Check required boolean fields
46
+ const requiredBooleanFields = [
47
+ 'nested_entries',
48
+ 'css_extract_ignore_order_warnings',
49
+ 'webpack_compile_output',
50
+ 'shakapacker_precompile',
51
+ 'cache_manifest',
52
+ 'ensure_consistent_versioning',
53
+ 'useContentHash',
54
+ 'compile'
55
+ ]
56
+
57
+ for (const field of requiredBooleanFields) {
58
+ if (typeof config[field] !== 'boolean') {
59
+ // Cache negative result in production
60
+ if (!shouldValidate) {
61
+ validatedConfigs.set(obj as object, false)
62
+ }
63
+ return false
64
+ }
65
+ }
66
+
67
+ // Check arrays
68
+ if (!Array.isArray(config.additional_paths)) {
69
+ // Cache negative result in production
70
+ if (!shouldValidate) {
71
+ validatedConfigs.set(obj as object, false)
72
+ }
73
+ return false
74
+ }
75
+
76
+ // Check optional fields
77
+ if (config.dev_server !== undefined && !isValidDevServerConfig(config.dev_server)) {
78
+ // Cache negative result in production
79
+ if (!shouldValidate) {
80
+ validatedConfigs.set(obj as object, false)
81
+ }
82
+ return false
83
+ }
84
+
85
+ if (config.integrity !== undefined) {
86
+ const integrity = config.integrity as Record<string, unknown>
87
+ if (typeof integrity.enabled !== 'boolean' ||
88
+ typeof integrity.cross_origin !== 'string') {
89
+ // Cache negative result in production
90
+ if (!shouldValidate) {
91
+ validatedConfigs.set(obj as object, false)
92
+ }
93
+ return false
94
+ }
95
+ }
96
+
97
+ const result = true
98
+
99
+ // Cache result in production
100
+ if (!shouldValidate) {
101
+ validatedConfigs.set(obj as object, result)
102
+ }
103
+
104
+ return result
105
+ }
106
+
107
+ /**
108
+ * Type guard to validate DevServerConfig object at runtime
109
+ * In production, performs minimal validation for performance
110
+ */
111
+ export function isValidDevServerConfig(obj: unknown): obj is DevServerConfig {
112
+ if (typeof obj !== 'object' || obj === null) {
113
+ return false
114
+ }
115
+
116
+ // In production, skip deep validation unless explicitly enabled
117
+ if (!shouldValidate) {
118
+ return true
119
+ }
120
+
121
+ const config = obj as Record<string, unknown>
122
+
123
+ // All fields are optional, just check types if present
124
+ if (config.hmr !== undefined &&
125
+ typeof config.hmr !== 'boolean' &&
126
+ config.hmr !== 'only') {
127
+ return false
128
+ }
129
+
130
+ if (config.port !== undefined &&
131
+ typeof config.port !== 'number' &&
132
+ typeof config.port !== 'string' &&
133
+ config.port !== 'auto') {
134
+ return false
135
+ }
136
+
137
+ return true
138
+ }
139
+
140
+ /**
141
+ * Type guard to validate YamlConfig structure
142
+ * In production, performs minimal validation for performance
143
+ */
144
+ export function isValidYamlConfig(obj: unknown): obj is YamlConfig {
145
+ if (typeof obj !== 'object' || obj === null) {
146
+ return false
147
+ }
148
+
149
+ // In production, skip deep validation unless explicitly enabled
150
+ if (!shouldValidate) {
151
+ return true
152
+ }
153
+
154
+ const config = obj as Record<string, unknown>
155
+
156
+ // Each key should map to an object
157
+ for (const env of Object.keys(config)) {
158
+ if (typeof config[env] !== 'object' || config[env] === null) {
159
+ return false
160
+ }
161
+ }
162
+
163
+ return true
164
+ }
165
+
166
+ /**
167
+ * Validates partial config used for merging
168
+ * Ensures that if fields are present, they have the correct types
169
+ * In production, performs minimal validation for performance
170
+ */
171
+ export function isPartialConfig(obj: unknown): obj is Partial<Config> {
172
+ if (typeof obj !== 'object' || obj === null) {
173
+ return false
174
+ }
175
+
176
+ // In production, skip deep validation unless explicitly enabled
177
+ if (!shouldValidate) {
178
+ return true
179
+ }
180
+
181
+ const config = obj as Record<string, unknown>
182
+
183
+ // Check string fields if present
184
+ const stringFields = [
185
+ 'source_path', 'source_entry_path', 'public_root_path',
186
+ 'public_output_path', 'cache_path', 'javascript_transpiler'
187
+ ]
188
+
189
+ for (const field of stringFields) {
190
+ if (field in config && typeof config[field] !== 'string') {
191
+ return false
192
+ }
193
+ }
194
+
195
+ // Check boolean fields if present
196
+ const booleanFields = [
197
+ 'nested_entries', 'css_extract_ignore_order_warnings',
198
+ 'webpack_compile_output', 'shakapacker_precompile',
199
+ 'cache_manifest', 'ensure_consistent_versioning'
200
+ ]
201
+
202
+ for (const field of booleanFields) {
203
+ if (field in config && typeof config[field] !== 'boolean') {
204
+ return false
205
+ }
206
+ }
207
+
208
+ // Check arrays if present
209
+ if ('additional_paths' in config && !Array.isArray(config.additional_paths)) {
210
+ return false
211
+ }
212
+
213
+ return true
214
+ }
215
+
216
+ /**
217
+ * Creates a validation error with helpful context
218
+ */
219
+ export function createConfigValidationError(
220
+ configPath: string,
221
+ environment: string,
222
+ details?: string
223
+ ): Error {
224
+ const message = `Invalid configuration in ${configPath} for environment '${environment}'`
225
+ return new Error(details ? `${message}: ${details}` : message)
226
+ }
227
+
228
+
@@ -5,7 +5,7 @@
5
5
  const { moduleExists } = require("./helpers")
6
6
  const { error } = require("./debug")
7
7
 
8
- const validateRspackDependencies = () => {
8
+ const validateRspackDependencies = (): void => {
9
9
  const requiredDependencies = ["@rspack/core", "rspack-manifest-plugin"]
10
10
 
11
11
  const missingDependencies = requiredDependencies.filter(
@@ -28,7 +28,7 @@ const validateRspackDependencies = () => {
28
28
  }
29
29
  }
30
30
 
31
- const validateWebpackDependencies = () => {
31
+ const validateWebpackDependencies = (): void => {
32
32
  const requiredDependencies = [
33
33
  "webpack",
34
34
  "webpack-cli",
@@ -55,7 +55,7 @@ const validateWebpackDependencies = () => {
55
55
  }
56
56
  }
57
57
 
58
- module.exports = {
58
+ export = {
59
59
  validateRspackDependencies,
60
60
  validateWebpackDependencies
61
- }
61
+ }
@@ -0,0 +1,32 @@
1
+ import type { Configuration, RuleSetRule, RuleSetUseItem } from 'webpack'
2
+
3
+ export interface ShakapackerWebpackConfig extends Configuration {
4
+ module?: Configuration['module'] & {
5
+ rules?: RuleSetRule[]
6
+ }
7
+ }
8
+
9
+ export interface ShakapackerRule extends RuleSetRule {
10
+ test: RegExp
11
+ use: RuleSetUseItem[]
12
+ }
13
+
14
+ export interface ShakapackerLoaderOptions {
15
+ [key: string]: any
16
+ }
17
+
18
+ export interface ShakapackerLoader {
19
+ loader: string
20
+ options?: ShakapackerLoaderOptions
21
+ }
22
+
23
+ export type LoaderType = string | ShakapackerLoader
24
+
25
+ export interface LoaderUtils {
26
+ resolveLoader(name: string): string
27
+ createRule(test: RegExp, loaders: LoaderType[]): ShakapackerRule
28
+ getStyleLoader(extract?: boolean): LoaderType
29
+ getCssLoader(modules?: boolean): LoaderType
30
+ getPostCssLoader(): LoaderType
31
+ getSassLoader(): LoaderType
32
+ }
@@ -0,0 +1,117 @@
1
+ import { DevServerConfig } from "./types"
2
+ const snakeToCamelCase = require("./utils/snakeToCamelCase")
3
+
4
+ const shakapackerDevServerYamlConfig = require("./dev_server") as DevServerConfig
5
+ const { outputPath: contentBase, publicPath } = require("./config") as {
6
+ outputPath: string
7
+ publicPath: string
8
+ }
9
+
10
+ interface WebpackDevServerConfig {
11
+ devMiddleware?: {
12
+ publicPath?: string
13
+ }
14
+ hot?: boolean | string
15
+ liveReload?: boolean
16
+ historyApiFallback?: boolean | {
17
+ disableDotRule?: boolean
18
+ }
19
+ static?: {
20
+ publicPath?: string
21
+ [key: string]: unknown
22
+ }
23
+ client?: Record<string, unknown>
24
+ allowedHosts?: "all" | "auto" | string | string[]
25
+ bonjour?: boolean | Record<string, unknown>
26
+ compress?: boolean
27
+ headers?: Record<string, unknown> | (() => Record<string, unknown>)
28
+ host?: "local-ip" | "local-ipv4" | "local-ipv6" | string
29
+ http2?: boolean
30
+ https?: boolean | Record<string, unknown>
31
+ ipc?: boolean | string
32
+ magicHtml?: boolean
33
+ onAfterSetupMiddleware?: (devServer: unknown) => void
34
+ onBeforeSetupMiddleware?: (devServer: unknown) => void
35
+ open?: boolean | string | string[] | Record<string, unknown> | Record<string, unknown>[]
36
+ port?: "auto" | string | number
37
+ proxy?: unknown
38
+ server?: string | boolean | Record<string, unknown>
39
+ setupExitSignals?: boolean
40
+ setupMiddlewares?: (middlewares: unknown[], devServer: unknown) => unknown[]
41
+ watchFiles?: string | string[] | unknown
42
+ webSocketServer?: string | boolean | Record<string, unknown>
43
+ [key: string]: unknown
44
+ }
45
+
46
+ const webpackDevServerMappedKeys = new Set([
47
+ // client, server, liveReload, devMiddleware are handled separately
48
+ "allowedHosts",
49
+ "bonjour",
50
+ "compress",
51
+ "headers",
52
+ "historyApiFallback",
53
+ "host",
54
+ "hot",
55
+ "http2",
56
+ "https",
57
+ "ipc",
58
+ "magicHtml",
59
+ "onAfterSetupMiddleware",
60
+ "onBeforeSetupMiddleware",
61
+ "open",
62
+ "port",
63
+ "proxy",
64
+ "server",
65
+ "setupExitSignals",
66
+ "setupMiddlewares",
67
+ "watchFiles",
68
+ "webSocketServer"
69
+ ])
70
+
71
+ function createDevServerConfig(): WebpackDevServerConfig {
72
+ const devServerYamlConfig = { ...shakapackerDevServerYamlConfig } as DevServerConfig & Record<string, unknown>
73
+ const liveReload =
74
+ devServerYamlConfig.live_reload !== undefined
75
+ ? devServerYamlConfig.live_reload
76
+ : !devServerYamlConfig.hmr
77
+ delete devServerYamlConfig.live_reload
78
+
79
+ const config: WebpackDevServerConfig = {
80
+ devMiddleware: {
81
+ publicPath
82
+ },
83
+ hot: devServerYamlConfig.hmr,
84
+ liveReload,
85
+ historyApiFallback: {
86
+ disableDotRule: true
87
+ },
88
+ static: {
89
+ publicPath: contentBase
90
+ }
91
+ }
92
+ delete devServerYamlConfig.hmr
93
+
94
+ if (devServerYamlConfig.static) {
95
+ config.static = {
96
+ ...config.static,
97
+ ...(typeof devServerYamlConfig.static === 'object' ? devServerYamlConfig.static as Record<string, unknown> : {})
98
+ }
99
+ delete devServerYamlConfig.static
100
+ }
101
+
102
+ if (devServerYamlConfig.client) {
103
+ config.client = devServerYamlConfig.client
104
+ delete devServerYamlConfig.client
105
+ }
106
+
107
+ Object.keys(devServerYamlConfig).forEach((yamlKey) => {
108
+ const camelYamlKey = snakeToCamelCase(yamlKey)
109
+ if (webpackDevServerMappedKeys.has(camelYamlKey)) {
110
+ config[camelYamlKey] = devServerYamlConfig[yamlKey]
111
+ }
112
+ })
113
+
114
+ return config
115
+ }
116
+
117
+ export = createDevServerConfig
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shakapacker",
3
- "version": "9.0.0-beta.3",
3
+ "version": "9.0.0-beta.5",
4
4
  "description": "Use webpack to manage app-like JavaScript modules in Rails",
5
5
  "homepage": "https://github.com/shakacode/shakapacker",
6
6
  "bugs": {
@@ -29,8 +29,12 @@
29
29
  "lib/install/config/shakapacker.yml"
30
30
  ],
31
31
  "scripts": {
32
+ "clean:ts": "find package -name '*.ts' -not -name '*.d.ts' | sed 's/\\.ts$//' | xargs -I {} rm -f {}.js {}.d.ts {}.d.ts.map {}.js.map",
33
+ "build": "tsc",
34
+ "build:types": "tsc",
32
35
  "lint": "eslint .",
33
- "test": "jest"
36
+ "test": "jest",
37
+ "type-check": "tsc --noEmit"
34
38
  },
35
39
  "dependencies": {
36
40
  "js-yaml": "^4.1.0",