Skip to content
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@

- **Added `bin/shakapacker-watch` binstub for clean Ctrl-C shutdown in Procfile-based workflows**. [PR #1026](https://github.com/shakacode/shakapacker/pull/1026) by [justin808](https://github.com/justin808). The new wrapper script traps INT/TERM signals and forwards TERM to the underlying `bin/shakapacker --watch` process, preventing Ruby interrupt backtraces when stopping `bin/dev`. Use `bin/shakapacker-watch --watch` in Procfiles instead of `bin/shakapacker --watch`.

### Fixed

- **Fixed webpack-dev-server `static` config defaulting to watch `public/` directory unnecessarily**. [PR #1032](https://github.com/shakacode/shakapacker/pull/1032) by [ihabadham](https://github.com/ihabadham). Three bugs fixed: (1) `static` now defaults to `false` instead of a misconfigured object that caused webpack-dev-server to watch the `public/` directory, which is already served by Rails via `ActionDispatch::Static`; (2) setting `static: false` in `shakapacker.yml` is no longer silently ignored; (3) the default template no longer includes `static.watch`, which was a v3→v4 migration artifact. Fixes [#1031](https://github.com/shakacode/shakapacker/issues/1031).

## [v9.7.0] - March 15, 2026

### Added
Expand Down
3 changes: 0 additions & 3 deletions lib/install/config/shakapacker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,6 @@ development:
# port than your Rails server and the browser blocks cross-origin asset requests):
# headers:
# "Access-Control-Allow-Origin": "*"
static:
watch:
ignored: "**/node_modules/**"

test:
<<: *default
Expand Down
36 changes: 21 additions & 15 deletions package/webpackDevServerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ const snakeToCamelCase = require("./utils/snakeToCamelCase")

const shakapackerDevServerYamlConfig =
require("./dev_server") as DevServerConfig
const { outputPath: contentBase, publicPath } = require("./config") as {
outputPath: string
const { publicPath } = require("./config") as {
publicPath: string
}

Expand All @@ -20,10 +19,14 @@ interface WebpackDevServerConfig {
| {
disableDotRule?: boolean
}
static?: {
publicPath?: string
[key: string]: unknown
}
static?:
| boolean
| string
| string[]
| {
publicPath?: string
[key: string]: unknown
}
client?: Record<string, unknown>
allowedHosts?: string | string[]
bonjour?: boolean | Record<string, unknown>
Expand Down Expand Up @@ -96,18 +99,21 @@ function createDevServerConfig(): WebpackDevServerConfig {
historyApiFallback: {
disableDotRule: true
},
static: {
publicPath: contentBase
}
static: false
}
delete devServerYamlConfig.hmr

if (devServerYamlConfig.static) {
config.static = {
...config.static,
...(typeof devServerYamlConfig.static === "object"
? (devServerYamlConfig.static as Record<string, unknown>)
: {})
if (
devServerYamlConfig.static !== undefined &&
devServerYamlConfig.static !== null
) {
if (devServerYamlConfig.static === false) {
config.static = false
} else if (typeof devServerYamlConfig.static === "object") {
config.static = devServerYamlConfig.static as Record<string, unknown>
} else {
config.static =
devServerYamlConfig.static as WebpackDevServerConfig["static"]
}
delete devServerYamlConfig.static
}
Expand Down
98 changes: 98 additions & 0 deletions test/package/webpackDevServerConfig.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const { chdirTestApp, resetEnv } = require("../helpers")

const rootPath = process.cwd()
chdirTestApp()

describe("webpackDevServerConfig", () => {
beforeEach(() => {
jest.resetModules()
resetEnv()
process.env.NODE_ENV = "development"
process.env.RAILS_ENV = "development"
})
afterAll(() => process.chdir(rootPath))

test("defaults static to false", () => {
const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.static).toBe(false)
})

test("passes through static: false from YAML config", () => {
const devServer = require("../../package/dev_server")
devServer.static = false

const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.static).toBe(false)
})

test("passes through static: true from YAML config", () => {
const devServer = require("../../package/dev_server")
devServer.static = true

const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.static).toBe(true)
})

test("passes through static object from YAML config", () => {
const devServer = require("../../package/dev_server")
devServer.static = { directory: "/custom/path", watch: false }

const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.static).toStrictEqual({
directory: "/custom/path",
watch: false
})
})

test("sets devMiddleware.publicPath to URL path", () => {
const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.devMiddleware.publicPath).toBe("/packs/")
})

test("maps hmr to hot", () => {
const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.hot).toBe(true)
expect(config.hmr).toBeUndefined()
})

test("defaults liveReload to inverse of hmr", () => {
const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

// Test app has hmr: true, so liveReload should default to false
expect(config.liveReload).toBe(false)
})

test("maps snake_case YAML keys to camelCase webpack-dev-server keys", () => {
const devServer = require("../../package/dev_server")
devServer.allowed_hosts = "auto"

const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.allowedHosts).toBe("auto")
expect(config.allowed_hosts).toBeUndefined()
})

test("passes through client config", () => {
const devServer = require("../../package/dev_server")
devServer.client = { overlay: true }

const createDevServerConfig = require("../../package/webpackDevServerConfig")
const config = createDevServerConfig()

expect(config.client).toStrictEqual({ overlay: true })
})
})
Loading