shakapacker 9.0.0.beta.4 → 9.0.0.beta.6

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintignore +1 -0
  3. data/.github/workflows/claude-code-review.yml +1 -1
  4. data/.github/workflows/dummy.yml +4 -0
  5. data/.github/workflows/generator.yml +7 -0
  6. data/.github/workflows/node.yml +22 -0
  7. data/.github/workflows/ruby.yml +11 -0
  8. data/.github/workflows/test-bundlers.yml +27 -9
  9. data/.gitignore +20 -0
  10. data/.yalcignore +26 -0
  11. data/CHANGELOG.md +58 -40
  12. data/CONTRIBUTING.md +64 -0
  13. data/Gemfile.lock +1 -1
  14. data/README.md +80 -1
  15. data/docs/optional-peer-dependencies.md +198 -0
  16. data/docs/typescript.md +99 -0
  17. data/docs/v9_upgrade.md +79 -2
  18. data/lib/install/template.rb +8 -1
  19. data/lib/shakapacker/configuration.rb +58 -1
  20. data/lib/shakapacker/doctor.rb +751 -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 +94 -117
  31. data/package/environments/base.ts +138 -0
  32. data/package/index.d.ts +3 -150
  33. data/package/{index.js → index.ts} +18 -8
  34. data/package/loaders.d.ts +28 -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 +33 -0
  48. data/package/webpackDevServerConfig.ts +117 -0
  49. data/package.json +112 -4
  50. data/test/peer-dependencies.sh +85 -0
  51. data/test/typescript/build.test.js +117 -0
  52. data/tsconfig.json +39 -0
  53. data/yarn.lock +1 -1
  54. metadata +34 -17
  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 -127
  60. data/package/utils/snakeToCamelCase.js +0 -5
  61. data/package/utils/validateCssModulesConfig.js +0 -91
  62. data/package/webpackDevServerConfig.js +0 -73
data/package/config.js DELETED
@@ -1,80 +0,0 @@
1
- const { resolve } = require("path")
2
- const { load } = require("js-yaml")
3
- const { existsSync, readFileSync } = require("fs")
4
-
5
- const { merge } = require("webpack-merge")
6
- const { ensureTrailingSlash } = require("./utils/helpers")
7
- const { railsEnv } = require("./env")
8
- const configPath = require("./utils/configPath")
9
-
10
- const defaultConfigPath = require("./utils/defaultConfigPath")
11
-
12
- const getDefaultConfig = () => {
13
- const defaultConfig = load(readFileSync(defaultConfigPath), "utf8")
14
- return defaultConfig[railsEnv] || defaultConfig.production
15
- }
16
-
17
- const defaults = getDefaultConfig()
18
- let config
19
-
20
- if (existsSync(configPath)) {
21
- const appYmlObject = load(readFileSync(configPath), "utf8")
22
- const envAppConfig = appYmlObject[railsEnv]
23
-
24
- if (!envAppConfig) {
25
- /* eslint no-console:0 */
26
- console.warn(
27
- `Warning: ${railsEnv} key not found in the configuration file. Using production configuration as a fallback.`
28
- )
29
- }
30
-
31
- config = merge(defaults, envAppConfig || {})
32
- } else {
33
- config = merge(defaults, {})
34
- }
35
-
36
- config.outputPath = resolve(config.public_root_path, config.public_output_path)
37
-
38
- // Ensure that the publicPath includes our asset host so dynamic imports
39
- // (code-splitting chunks and static assets) load from the CDN instead of a relative path.
40
- const getPublicPath = () => {
41
- const rootUrl = ensureTrailingSlash(process.env.SHAKAPACKER_ASSET_HOST || "/")
42
- return `${rootUrl}${config.public_output_path}/`
43
- }
44
-
45
- config.publicPath = getPublicPath()
46
- config.publicPathWithoutCDN = `/${config.public_output_path}/`
47
-
48
- if (config.manifest_path) {
49
- config.manifestPath = resolve(config.manifest_path)
50
- } else {
51
- config.manifestPath = resolve(config.outputPath, "manifest.json")
52
- }
53
- // Ensure no duplicate hash functions exist in the returned config object
54
- config.integrity.hash_functions = [...new Set(config.integrity.hash_functions)]
55
-
56
- // Allow ENV variable to override assets_bundler
57
- if (process.env.SHAKAPACKER_ASSETS_BUNDLER) {
58
- config.assets_bundler = process.env.SHAKAPACKER_ASSETS_BUNDLER
59
- }
60
-
61
- // Define clear defaults
62
- // SWC is now the default transpiler for both webpack and rspack
63
- const DEFAULT_JAVASCRIPT_TRANSPILER = "swc"
64
-
65
- // Backward compatibility: Add webpack_loader property that maps to javascript_transpiler
66
- // Show deprecation warning if webpack_loader is used
67
- if (config.webpack_loader && !config.javascript_transpiler) {
68
- console.warn(
69
- "⚠️ DEPRECATION WARNING: The 'webpack_loader' configuration option is deprecated. Please use 'javascript_transpiler' instead as it better reflects its purpose of configuring JavaScript transpilation regardless of the bundler used."
70
- )
71
- config.javascript_transpiler = config.webpack_loader
72
- } else if (!config.javascript_transpiler) {
73
- config.javascript_transpiler =
74
- config.webpack_loader || DEFAULT_JAVASCRIPT_TRANSPILER
75
- }
76
-
77
- // Ensure webpack_loader is always available for backward compatibility
78
- config.webpack_loader = config.javascript_transpiler
79
-
80
- module.exports = config
data/package/env.js DELETED
@@ -1,48 +0,0 @@
1
- const { load } = require("js-yaml")
2
- const { readFileSync } = require("fs")
3
- const defaultConfigPath = require("./utils/defaultConfigPath")
4
-
5
- const NODE_ENVIRONMENTS = ["development", "production", "test"]
6
- const DEFAULT = "production"
7
- const configPath = require("./utils/configPath")
8
-
9
- const railsEnv = process.env.RAILS_ENV
10
- const rawNodeEnv = process.env.NODE_ENV
11
- const nodeEnv =
12
- rawNodeEnv && NODE_ENVIRONMENTS.includes(rawNodeEnv) ? rawNodeEnv : DEFAULT
13
- const isProduction = nodeEnv === "production"
14
- const isDevelopment = nodeEnv === "development"
15
-
16
- let config
17
- try {
18
- config = load(readFileSync(configPath), "utf8")
19
- } catch (error) {
20
- if (error.code === "ENOENT") {
21
- // File not found, use default configuration
22
- config = load(readFileSync(defaultConfigPath), "utf8")
23
- } else {
24
- throw error
25
- }
26
- }
27
-
28
- const availableEnvironments = Object.keys(config).join("|")
29
- const regex = new RegExp(`^(${availableEnvironments})$`, "g")
30
-
31
- const runningWebpackDevServer = process.env.WEBPACK_SERVE === "true"
32
-
33
- const validatedRailsEnv = railsEnv && railsEnv.match(regex) ? railsEnv : DEFAULT
34
-
35
- if (railsEnv && validatedRailsEnv !== railsEnv) {
36
- /* eslint no-console:0 */
37
- console.warn(
38
- `Warning: '${railsEnv}' environment not found in the configuration. Using '${DEFAULT}' configuration as a fallback.`
39
- )
40
- }
41
-
42
- module.exports = {
43
- railsEnv: validatedRailsEnv,
44
- nodeEnv,
45
- isProduction,
46
- isDevelopment,
47
- runningWebpackDevServer
48
- }
@@ -1,4 +0,0 @@
1
- const { resolve } = require("path")
2
-
3
- module.exports =
4
- process.env.SHAKAPACKER_CONFIG || resolve("config", "shakapacker.yml")
@@ -1,2 +0,0 @@
1
- const path = require.resolve("../../lib/install/config/shakapacker.yml")
2
- module.exports = path
@@ -1,127 +0,0 @@
1
- const isBoolean = (str) => /^true/.test(str) || /^false/.test(str)
2
-
3
- const ensureTrailingSlash = (path) => (path.endsWith("/") ? path : `${path}/`)
4
-
5
- const resolvedPath = (packageName) => {
6
- try {
7
- return require.resolve(packageName)
8
- } catch (e) {
9
- if (e.code !== "MODULE_NOT_FOUND") {
10
- throw e
11
- }
12
- return null
13
- }
14
- }
15
-
16
- const moduleExists = (packageName) => !!resolvedPath(packageName)
17
-
18
- const canProcess = (rule, fn) => {
19
- const modulePath = resolvedPath(rule)
20
-
21
- if (modulePath) {
22
- return fn(modulePath)
23
- }
24
-
25
- return null
26
- }
27
-
28
- const validateBabelDependencies = () => {
29
- // Only validate core dependencies that are absolutely required
30
- // Additional packages like presets are optional and project-specific
31
- const coreRequiredPackages = ["@babel/core", "babel-loader"]
32
-
33
- const missingCorePackages = coreRequiredPackages.filter(
34
- (pkg) => !moduleExists(pkg)
35
- )
36
-
37
- if (missingCorePackages.length > 0) {
38
- const installCommand = `npm install --save-dev ${missingCorePackages.join(" ")}`
39
-
40
- // Check for commonly needed packages and suggest them
41
- const suggestedPackages = [
42
- "@babel/preset-env",
43
- "@babel/plugin-transform-runtime",
44
- "@babel/runtime"
45
- ]
46
-
47
- const missingSuggested = suggestedPackages.filter(
48
- (pkg) => !moduleExists(pkg)
49
- )
50
- let additionalHelp = ""
51
-
52
- if (missingSuggested.length > 0) {
53
- additionalHelp =
54
- `\n\nYou may also need: ${missingSuggested.join(", ")}\n` +
55
- `Install with: npm install --save-dev ${missingSuggested.join(" ")}`
56
- }
57
-
58
- throw new Error(
59
- `Babel is configured but core packages are missing: ${missingCorePackages.join(", ")}\n\n` +
60
- `To fix this, run:\n ${installCommand}${additionalHelp}\n\n` +
61
- `💡 Tip: Consider migrating to SWC for 20x faster compilation:\n` +
62
- ` 1. Set javascript_transpiler: 'swc' in config/shakapacker.yml\n` +
63
- ` 2. Run: npm install @swc/core swc-loader`
64
- )
65
- }
66
- }
67
-
68
- const loaderMatches = (configLoader, loaderToCheck, fn) => {
69
- if (configLoader !== loaderToCheck) {
70
- return null
71
- }
72
-
73
- const loaderName = `${configLoader}-loader`
74
-
75
- // Special validation for babel to check all dependencies
76
- if (configLoader === "babel") {
77
- validateBabelDependencies()
78
- }
79
-
80
- if (!moduleExists(loaderName)) {
81
- let installCommand = ""
82
- let migrationHelp = ""
83
-
84
- if (configLoader === "babel") {
85
- installCommand =
86
- "npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @babel/runtime"
87
- migrationHelp =
88
- "\n\n💡 Tip: Consider migrating to SWC for 20x faster compilation:\n" +
89
- " 1. Set javascript_transpiler: 'swc' in config/shakapacker.yml\n" +
90
- " 2. Run: npm install @swc/core swc-loader"
91
- } else if (configLoader === "swc") {
92
- installCommand = "npm install --save-dev @swc/core swc-loader"
93
- migrationHelp =
94
- "\n\n✨ SWC is 20x faster than Babel with zero configuration!"
95
- } else if (configLoader === "esbuild") {
96
- installCommand = "npm install --save-dev esbuild esbuild-loader"
97
- }
98
-
99
- throw new Error(
100
- `Your Shakapacker config specified using ${configLoader}, but ${loaderName} package is not installed.\n\n` +
101
- `To fix this, run:\n ${installCommand}${migrationHelp}`
102
- )
103
- }
104
-
105
- return fn()
106
- }
107
-
108
- const packageFullVersion = (packageName) => {
109
- // eslint-disable-next-line import/no-dynamic-require
110
- const packageJsonPath = require.resolve(`${packageName}/package.json`)
111
- // eslint-disable-next-line import/no-dynamic-require, global-require
112
- return require(packageJsonPath).version
113
- }
114
-
115
- const packageMajorVersion = (packageName) =>
116
- packageFullVersion(packageName).match(/^\d+/)[0]
117
-
118
- module.exports = {
119
- isBoolean,
120
- ensureTrailingSlash,
121
- canProcess,
122
- moduleExists,
123
- validateBabelDependencies,
124
- loaderMatches,
125
- packageFullVersion,
126
- packageMajorVersion
127
- }
@@ -1,5 +0,0 @@
1
- function snakeToCamelCase(s) {
2
- return s.replace(/(_\w)/g, (match) => match[1].toUpperCase())
3
- }
4
-
5
- module.exports = snakeToCamelCase
@@ -1,91 +0,0 @@
1
- /* eslint global-require: 0 */
2
- const { warn } = require("./debug")
3
-
4
- /**
5
- * Validates CSS modules configuration and warns about potential issues
6
- * with v9 defaults or conflicting settings.
7
- */
8
- const validateCssModulesConfig = (cssLoaderOptions) => {
9
- // Skip validation in production by default for performance
10
- if (
11
- process.env.NODE_ENV === "production" &&
12
- process.env.SHAKAPACKER_VALIDATE_CSS_MODULES !== "true"
13
- ) {
14
- return
15
- }
16
-
17
- if (!cssLoaderOptions || !cssLoaderOptions.modules) {
18
- return
19
- }
20
-
21
- const { modules } = cssLoaderOptions
22
-
23
- // Check for conflicting namedExport and esModule settings
24
- if (modules.namedExport === true && modules.esModule === false) {
25
- warn(
26
- "⚠️ CSS Modules Configuration Warning:\n" +
27
- " namedExport: true with esModule: false may cause issues.\n" +
28
- " Consider setting esModule: true or removing it (defaults to true)."
29
- )
30
- }
31
-
32
- // Check for v8-style configuration with v9
33
- if (modules.namedExport === false) {
34
- warn(
35
- "ℹ️ CSS Modules Configuration Note:\n" +
36
- " You are using namedExport: false (v8 behavior).\n" +
37
- " Shakapacker v9 defaults to namedExport: true for better tree-shaking.\n" +
38
- " See docs/css-modules-export-mode.md for migration instructions."
39
- )
40
- }
41
-
42
- // Check for inconsistent exportLocalsConvention with namedExport
43
- if (
44
- modules.namedExport === true &&
45
- modules.exportLocalsConvention === "asIs"
46
- ) {
47
- warn(
48
- "⚠️ CSS Modules Configuration Warning:\n" +
49
- " Using namedExport: true with exportLocalsConvention: 'asIs' may cause issues\n" +
50
- " with kebab-case class names (e.g., 'my-button').\n" +
51
- " Consider using exportLocalsConvention: 'camelCase' (v9 default)."
52
- )
53
- }
54
-
55
- // Check for deprecated localIdentName pattern
56
- if (
57
- modules.localIdentName &&
58
- modules.localIdentName.includes("[hash:base64]")
59
- ) {
60
- warn(
61
- "⚠️ CSS Modules Configuration Warning:\n" +
62
- " [hash:base64] is deprecated in css-loader v6+.\n" +
63
- " Use [hash] instead for better compatibility."
64
- )
65
- }
66
-
67
- // Check for potential TypeScript issues
68
- if (
69
- modules.namedExport === true &&
70
- process.env.SHAKAPACKER_ASSET_COMPILER_TYPESCRIPT === "true"
71
- ) {
72
- warn(
73
- "ℹ️ TypeScript CSS Modules Note:\n" +
74
- " With namedExport: true, TypeScript projects should use:\n" +
75
- " import * as styles from './styles.module.css'\n" +
76
- " instead of: import styles from './styles.module.css'\n" +
77
- " See docs/css-modules-export-mode.md for TypeScript setup."
78
- )
79
- }
80
-
81
- // Check for auto: true with getLocalIdent (potential conflict)
82
- if (modules.auto === true && modules.getLocalIdent) {
83
- warn(
84
- "⚠️ CSS Modules Configuration Warning:\n" +
85
- " Using both 'auto: true' and 'getLocalIdent' may cause conflicts.\n" +
86
- " The 'auto' option determines which files are treated as CSS modules."
87
- )
88
- }
89
- }
90
-
91
- module.exports = { validateCssModulesConfig }
@@ -1,73 +0,0 @@
1
- const shakapackerDevServerYamlConfig = require("./dev_server")
2
- const snakeToCamelCase = require("./utils/snakeToCamelCase")
3
- const { outputPath: contentBase, publicPath } = require("./config")
4
-
5
- const webpackDevServerMappedKeys = new Set([
6
- // client, server, liveReload, devMiddleware are handled separately
7
- "allowedHosts",
8
- "bonjour",
9
- "compress",
10
- "headers",
11
- "historyApiFallback",
12
- "host",
13
- "hot",
14
- "http2",
15
- "https",
16
- "ipc",
17
- "magicHtml",
18
- "onAfterSetupMiddleware",
19
- "onBeforeSetupMiddleware",
20
- "open",
21
- "port",
22
- "proxy",
23
- "server",
24
- "setupExitSignals",
25
- "setupMiddlewares",
26
- "watchFiles",
27
- "webSocketServer"
28
- ])
29
-
30
- function createDevServerConfig() {
31
- const devServerYamlConfig = { ...shakapackerDevServerYamlConfig }
32
- const liveReload =
33
- devServerYamlConfig.live_reload !== undefined
34
- ? devServerYamlConfig.live_reload
35
- : !devServerYamlConfig.hmr
36
- delete devServerYamlConfig.live_reload
37
-
38
- const config = {
39
- devMiddleware: {
40
- publicPath
41
- },
42
- hot: devServerYamlConfig.hmr,
43
- liveReload,
44
- historyApiFallback: {
45
- disableDotRule: true
46
- },
47
- static: {
48
- publicPath: contentBase
49
- }
50
- }
51
- delete devServerYamlConfig.hmr
52
-
53
- if (devServerYamlConfig.static) {
54
- config.static = { ...config.static, ...devServerYamlConfig.static }
55
- delete devServerYamlConfig.static
56
- }
57
-
58
- if (devServerYamlConfig.client) {
59
- config.client = devServerYamlConfig.client
60
- delete devServerYamlConfig.client
61
- }
62
-
63
- Object.keys(devServerYamlConfig).forEach((yamlKey) => {
64
- const camelYamlKey = snakeToCamelCase(yamlKey)
65
- if (webpackDevServerMappedKeys.has(camelYamlKey)) {
66
- config[camelYamlKey] = devServerYamlConfig[yamlKey]
67
- }
68
- })
69
-
70
- return config
71
- }
72
-
73
- module.exports = createDevServerConfig