@everystack/metro-config
v0.2.1
Published
Metro config preset for everystack web bundles — native-only web shims + @everystack subpath resolution
Readme
@everystack/metro-config
Metro preset for everystack web bundles. Pure machinery — it maintains no list of "native-only" packages. everystack can't know your dependencies' web-capability, and any such list goes stale the moment a package adds web support. Instead:
- Prefer the pattern, not a shim. For your own native imports, gate them
behind
Platform.OS !== 'web'+ dynamicimport(), or split them with a.native.ts/.web.tsextension. Then the native module is never in the web graph — no shim, no bytes, by construction. - Use
webShimsonly for what you can't reach — a third-party dependency that statically imports a native-only module you don't control. - Enforce it with
assertWebBundleCleanfrom@everystack/server/testing(size budget + composition + your ownexpectAbsentlist).
Usage
// metro.config.js
const { getDefaultConfig } = require('@expo/metro-config');
const { withEverystack } = require('@everystack/metro-config');
const config = getDefaultConfig(__dirname);
module.exports = withEverystack(config, {
webShims: {
// generic no-op for a native-only dep a third-party package drags in:
'react-native-view-shot': 'noop',
// or your own shim file when you need real web behavior:
'@rnmapbox/maps': require.resolve('./web-shims/mapbox'),
},
});What it does
@everystack/*subpath resolution. Metro doesn't honor our subpathexports(e.g.@everystack/logging/admin/config); Node does. The preset resolves them through Node and hands Metro the real file.- Web no-op redirect. On
platform === 'web', the packages you name inwebShimsresolve to the generic no-op stub ('noop') or your file. drop_consolein production (dropConsole: falseto disable).
Monorepos
Resolution is robust to either pnpm node-linker mode:
hoisted(flatnode_modules, the recommended setup for RN/Metro apps — the consumer uses it): workspace packages sit at the root, resolvable from anywhere.isolated(pnpm default): a workspace package is symlinked into its consumer'snode_modules, not a shared root.
The preset handles both: Node's paths option walks up the node_modules
hierarchy from the app's projectRoot (finding deps in the app's own
node_modules or anything above it), and the preset additionally passes Metro's
watchFolders (your workspace roots) as resolution roots. No extra config beyond
the watchFolders / nodeModulesPaths you already set for the monorepo.
Options
| option | default | meaning |
|---|---|---|
| webShims | {} | { [pkg]: absolutePath \| 'noop' }. Redirected only on web. |
| dropConsole | true | strip console.* in production builds. |
The no-op stub
@everystack/metro-config/noop is a generic web no-op: importing succeeds and
any access returns a callable/constructable no-op, so Platform.OS-guarded code
that never runs on web won't crash. It's a floor, not a polyfill — if a package
needs real web behavior, pass your own shim file instead of 'noop'.
See docs/bundle-size.md in the everystack repo for the full runtime-boundary
model and the web-slim plan.
License
AGPL-3.0-only © Scalable Technology, Inc.
A commercial license is available for organizations that cannot or do not wish to comply with the AGPL-3.0 terms. For commercial licensing, contact [email protected].
