sokra mode
This skill applies sokra's webpack design discipline to building and maintaining this repo. packages/enhanced is a TypeScript fork of the container and sharing plugins he wrote, so his rules apply there directly. The rules come from his public webpack/webpack record: 739 authored PRs and 717 reviews (2019–2022), and 400 issues plus 49 discussions (2020–2024). His webpack code stops in November 2022. Don't impersonate him: never sign as him, post as him, or claim his approval. Detailed patterns with evidence links are in references/plugin-patterns.md, and the per-package map of this repo's thirteen plugin packages is in references/repo-map.md. Read both before writing a new plugin, Module, Dependency, RuntimeModule, or runtime hook.
Architecture
- Build one feature as a slice of small classes. Each class has one job and its own file: Plugin (wiring), Dependency plus Template, Module subclass, ModuleFactory, RuntimeModule, and a
RuntimeGlobalsentry. - Umbrella plugins only compose.
ModuleFederationPlugin→ContainerPlugin+ContainerReferencePlugin+SharePlugin→ConsumeSharedPlugin/ProvideSharedPlugin. - Reuse existing machinery. Remotes are externals. A container is a normal entry with a
library. Fallbacks go throughnormalModuleFactory. - Connect parts through hooks and requirements, not direct calls.
- Code declares
runtimeRequirements, andruntimeRequirementInTreeadds the modules they need. - Extension points are
static getCompilationHooks(compilation)backed by aWeakMap.FederationModulesPluginis this repo's example.
- Code declares
- Put logic where the concern lives. Loading goes in the loading or runtime plugin. Error text goes in the error. Build-phase work goes in
Module.build. - Keep build and codeGeneration apart. Never mutate
buildInfoin codegen. Pass data out through codegendataand custom source types. Anything that crosses from build to codegen must be serializable. - Keep the core small, and challenge the premise first.
- Ask for the use case, and check whether an existing option or hook already covers it.
- For rare needs, expose a small override or a replaceable module, not new config.
- Never add globals.
- Don't branch on
target; gate on environment or feature flags.
Discipline
- Builds must be deterministic. Sort every emitted map and key, and use tiebreakers. Output must not depend on build order, the machine, or the Node version. Adding a chunk must not rewrite shared chunks.
- Caching must be correct by default.
- Cached classes use
makeSerializableand serialize every field, including subclass fields. - Per-compilation state goes in a
WeakMap. - Dependency paths are absolute.
- Anything unsafe stays opt-in.
- Cached classes use
- Performance is a correctness concern. Hot paths do one
getand compare toundefined. Avoid throwaway arrays and quadratic scans. Hoist constantSets and comparators. Lazy-require withmemoize. Measure perf claims. - Memory-trading optimizations are not plain wins. A cache that grows, or that keeps modules which have left the compilation, goes behind
experiments.*with eviction, or gets reverted (#14436, #14319). - Never break a public or plugin-facing interface in a minor. New behavior ships behind
experiments.*. Renames keep a deprecation shim. - Report problems as errors when the output can't be correct, otherwise as warnings. Messages name the cause and the fix.
- Every option gets a schema and a precise type. Name options positively. Use the graph APIs (
moduleGraph,chunkGraph), never legacymodule.*fields. UseRuntimeGlobalsandruntimeTemplate.basicFunction/returningFunction, never a literal__webpack_require__.x.
This repo
- Package roles. Thirteen packages are bundler plugins; references/repo-map.md lists each with its hooks and gaps.
enhancedis the webpack plugin layer, and every rule above applies in full.node,nextjs-mf,dts-plugin,manifest,utilities, and the legacy plugins compose on top of it. Same rules: umbrella plugins compose, runtime goes through requirements, assets go throughprocessAssetswith a named stage, options are validated at the boundary.rspackandrsbuild-pluginwrap rspack's native MF plugin. They shape options; module and dependency internals live in rspack itself.runtime-coreandwebpack-bundler-runtimehold the runtime logic.sdkowns the shared option types.
- Runtime split. RuntimeModules in
enhancedstay thin: they emit data (mappings, ids) and callfederationGlobal.bundlerRuntime.*. Logic lives inruntime-coreorwebpack-bundler-runtime, with unit tests there. This is a deliberate MF 2 change to sokra's "generate the runtime" rule. His rules still govern the glue:RuntimeGlobals, requirements, and template helpers. - Extend at runtime through plugins. Keep
runtime-coresmall. Add a hook in its plugin system (src/utils/hooks) or a runtime plugin (runtime-plugins,retry-plugin) before adding an option. - Webpack internals.
- Load them through
normalizeWebpackPath(seeAGENTS.md). makeSerializablekeys are'enhanced/lib/<path>', and there is nointernalSerializablesregistry.- Types are TypeScript. Avoid
anyand unchecked casts where a precise type exists.
- Load them through
- Options. Types go in
packages/sdk/src/types/plugins/ModuleFederationPlugin.tsand schemas inpackages/enhanced/src/schemas. Then runpnpm --filter @module-federation/enhanced run generate:schema; the pre-commit hook also runs it when schema JSON changes. New behavior goes under the plugin'sexperiments. - Errors. Runtime errors carry codes from
@module-federation/error-codes, which is this repo's equivalent ofDEP_WEBPACK_*. Build problems go tocompilation.errorsorcompilation.warnings. - Compatibility. Every package is published. Add a changeset (
pnpm run changeset) for publishable behavior changes.
Verification
- Every fix and feature needs a regression test that hits the edge that broke.
- Build-side:
packages/enhanced/test/configCases/<area>/<case>, ortest/compiler-unit. Other plugin packages use jest or rstest cases next to the source. - Runtime:
packages/runtime-core/__tests__/*.spec.ts. - E2E: the matching app in
apps/, viapnpm run ci:local --only=<job>. - Run with
pnpm --filter <pkg> run test. - A PR without a test is blocked.
- Build-side:
- When you fix one path, check its siblings. Grep every path that emits the same thing. Check the counterparts in
rspack,runtime-core,webpack-bundler-runtime,node, andnextjs-mftoo. - Regenerate derived files in the same change: snapshots, types, schema outputs.
Reviews
- Approve without comment, and put every objection inline. Prefer a
suggestionblock to a description. - Lead with the verdict ("working as expected", "that's a bug", "not supported"). Then give the mechanism in one or two sentences, then a snippet or a permalink.
- Keep requests short and imperative: "Please add a test case", "Use
Xinstead". When refusing, give the reason and name the alternative. Reject refactors that bring no concrete benefit. - Push guards back to the root cause. "This should never be undefined" means find where the value is produced.
- Triage: get a minimal repro repo before debugging, and profiling data before accepting a perf claim. Keep one problem per issue, and name the owning tool when the bug is not ours.
Process
- Titles and commits are short imperatives. Use this repo's conventional-commit prefix (
fix(enhanced): …), which commitlint enforces. Put regeneration in separate commits (chore: update snapshots). - Land a feature as one experimental PR, then small single-purpose fix PRs. Write the PR body the way
AGENTS.mdasks, and say plainly what was not tested. - Defer scope creep to "before this leaves experimental" or to a tracked TODO. Revert quickly when a change costs more than it gives.