@surf/forgepack
v0.1.0
Published
Webpack configuration factories for browser, SSR, and library builds — batteries included, no config file sprawl.
Downloads
27
Readme
forgepack
Webpack configuration factories for browser, SSR, and library builds.
Webpack configs sprawl. A real application ends up with several hundred lines of loader rules, plugin wiring, cache config, and mode branching — then the SSR build duplicates most of it with a handful of differences, and the shared component library duplicates it again. forgepack packages that whole surface as three functions you call with your project's paths.
// webpack.config.js
import { browserConfig } from '@surf/forgepack'
export default browserConfig({
appDirectory: import.meta.dirname,
appSrc: 'src',
appBuild: 'build',
})That returns a function webpack calls with its --env values, so webpack --env
mode=production gets a production config and webpack serve --env
mode=development gets a dev-server config, from the same file.
Install
npm install --save-dev @surf/forgepackRequires Node 20+. The package is native ESM — it ships no build step and
no CJS bundle. Use webpack.config.mjs, or set "type": "module" in your
package.json.
What it builds
browserConfig(options) — client bundles
The full application build: babel/TypeScript via babel-loader (with a custom
loader wrapper that lets webpack override preset-env targets), CSS/Sass with
CSS Modules, PostCSS with preset-env + flexbugs-fixes + normalize, SVG via
SVGR, asset inlining under a size threshold, web workers, HTML generation per
page, a manifest, code splitting, filesystem caching, and Terser +
css-minimizer for minification.
React Fast Refresh is wired automatically when the dev server has HMR enabled.
Service worker precaching is available through workbox-webpack-plugin.
ssrConfig(options) — server bundles
target: 'node', dependencies externalized via webpack-node-externals (with
CSS deliberately kept in the bundle, since Node cannot require a stylesheet),
and the same style pipeline so server-rendered markup carries the right class
names.
Unlike the other two, ssrConfig reads its mode from NODE_ENV at
construction time rather than from webpack's --env.
libraryConfig(options) — distributable packages
Library output targets for publishing a package rather than an application.
Dev server
devServerConfig and friends under @surf/forgepack/utils cover HTTPS certificate
resolution, host/port selection, the eval-source-map middleware, and a no-op
service-worker middleware that stops a stale production service worker from
hijacking local development.
Monorepo support
If your app lives in a yarn/npm workspace, forgepack finds the workspace root
and works out which sibling packages your app actually depends on. Those
packages' source directories are added to the babel-loader include paths and
their node_modules to resolution, so you can consume workspace packages
straight from source with no build step between them.
Opt in per-package via the workspaces block in your app's package.json:
{
"workspaces": {
"package-entry": "main:src", // the package.json field pointing at source
"development": true, // use source in development builds
"production": false // use built output in production builds
}
}Outside a monorepo this degrades to a no-op — nothing to configure.
Requirements on your project
A few things forgepack expects rather than invents for you:
- A browserslist config. The browser build compiles with
target: 'browserslist', which webpack rejects outright if your project declares no browser targets. Add abrowserslistkey topackage.jsonor a.browserslistrc. NODE_ENVandENVset. The configs wire webpack'sEnvironmentPluginwithout defaults, so a build with these unset fails loudly by design.contextdefaults toprocess.cwd(). If you run webpack from somewhere other than your app root, passcontextexplicitly.
Provenance
forgepack is extracted from a private monorepo's internal webpack tooling. The extraction removed seven internal dependencies. For anyone auditing what is first-party here:
| Original internal dependency | Resolution |
| --- | --- |
| config | Real dependency on @cogs/config — signatures matched exactly, no shim needed |
| casting | Vendored (src/vendor/casting.js) — resolveApp, getPublicUrlOrPath, formatWebpackMessages. The latter two originate in react-dev-utils (MIT) |
| browserslist-config | Vendored (src/vendor/browserslist-utils.js), derived from @babel/helper-compilation-targets (MIT), plus a real browserslist dependency |
| monorepo-utils | Vendored (src/vendor/workspace/) — only the workspace-discovery slice, with its locate-path dependency inlined |
| basic-utils | Reimplemented (src/vendor/basic-utils.js) — two functions, existy and yn |
| enums | Reimplemented (src/vendor/active-env.js) — the deployment-tier vocabulary, now read from ACTIVE_ENV |
| node-pkg | Dropped. It existed only to proxy nested binaries around a private hoisting quirk. forgepack ships no bins; install webpack-cli directly |
An eighth dependency, the internal babel preset used to transpile node_modules,
is reimplemented as src/presets/dependencies.js — @babel/preset-env plus
@babel/plugin-transform-runtime, which is what it always was underneath.
Nothing is stubbed. Every code path that shipped in the original ships here.
Deliberate changes
- Native ESM, no build step. The original shipped dual CJS/ESM via a Babel build. forgepack publishes its source directly.
- No
binentries. Seenode-pkgabove. - Three bugs found and fixed during extraction, each covered by a test:
- Builds crashed outside a git worktree (Docker, packed tarballs) because the
version stamp shelled out to
git rev-parseunguarded. ssrConfigcrashed on any non-monorepo project, passing anullworkspace root intopath.resolve.- Omitting the optional
entriesoption crashed the browser and library factories, and the empty case emittedentry: [], which webpack rejects.
- Builds crashed outside a git worktree (Docker, packed tarballs) because the
version stamp shelled out to
Development
npm install
npm run lint # eslint
npm run build # emit type declarations to types/
npm test # vitest
npm run verify # all threeThe test suite validates every generated config against webpack's own options schema and compiles a real fixture bundle end to end.
License
MIT © Andrew Cates
