@standhigher/local-fetch-mock
v0.1.1
Published
Browser window.fetch interceptor for Next.js local JSON scene mocks.
Readme
@standhigher/local-fetch-mock
Next.js 本地入口用 window.fetch 拦截器,按接口返回 JSON 场景。未命中的请求继续走真实后端。
只拦浏览器 fetch。Mock 入口必须放在 Next 应用目录内(例如 apps/admin/mock/),不要放仓库根。
安装
pnpm add @standhigher/local-fetch-mockNext 配置
把下面几项 合并进现有 next.config.ts,不要整文件替换。单仓应用若已有 turbopack.root、rewrites、webpack 其它 alias,全部保留。
1. transpilePackages
transpilePackages: ["@standhigher/local-fetch-mock"];不要把整个 node_modules 或 workspace 根加进 webpack include。
2. 拷贝 alias helper
不要在 next.config.ts 里写:
import { resolveLocalDevMockAlias } from "@standhigher/local-fetch-mock/next";Next 会把 next.config.ts 编成 CJS require,子路径解析会失败。
把包内文件拷到应用:
cp node_modules/@standhigher/local-fetch-mock/src/next.ts config/local-dev-mock.ts然后在 next.config.ts 里从本地文件引入,第一参数必须是 Next 应用目录,不是 monorepo 根:
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import type { NextConfig } from "next";
import {
resolveLocalDevMockAlias,
toTurbopackResolveAlias,
} from "./config/local-dev-mock";
const projectDir = dirname(fileURLToPath(import.meta.url));
const workspaceRoot = resolve(projectDir, "../.."); // 仅 monorepo 需要,用于 turbopack.root
const localDevMockAlias = resolveLocalDevMockAlias(
projectDir,
undefined,
process.env.LOCAL_DEV === "1" ||
process.env.NEXT_PUBLIC_MOCK_ENABLED === "1",
);
const localDevMockTurbopackAlias = toTurbopackResolveAlias(
projectDir,
localDevMockAlias,
);
const nextConfig: NextConfig = {
transpilePackages: ["@standhigher/local-fetch-mock"],
turbopack: {
// monorepo 若已有 turbopack.root,保持原值(常见是 workspace 根)
root: workspaceRoot,
resolveAlias: {
"@local-dev-mock": localDevMockTurbopackAlias,
},
},
webpack: (config) => {
config.resolve ??= {};
config.resolve.alias = {
...config.resolve.alias,
"@local-dev-mock": localDevMockAlias,
};
return config;
},
};
export default nextConfig;单仓且没有 turbopack.root 时,删掉 root 那一行即可。完整独立示例见包内 example/next.config.snippet.ts。
启用条件:LOCAL_DEV=1 或 NEXT_PUBLIC_MOCK_ENABLED=1。未启用时 alias 落到 stub,不拦截。
stub
若应用还没有 stub,新建 lib/local-dev/mock-stub.ts:
export {};客户端入口
在最早的客户端壳层顶部静态引入(不要动态 import):
"use client";
import "@local-dev-mock";一个应用只需一处最早的壳层;若已有 import "@local-dev-mock",跳过这一步。
Mock 目录
从零(没有业务 JSON)
cp -R node_modules/@standhigher/local-fetch-mock/example/mock ./mock目录应在 Next 应用内,例如 apps/admin/mock/。
已有完整 mock
不要覆盖 现有 mock/json/ 和业务 handlers.ts。
mock/index.ts改为从包引入并调用setupMock:
import { setupMock, toRequestUrl } from "@standhigher/local-fetch-mock";
import { MOCK_ENABLED } from "./config";
import { registerMocks } from "./handlers";
setupMock({
enabled: MOCK_ENABLED,
handlers: registerMocks(toRequestUrl),
});
export {};handlers.ts的getMockRequestMethod/jsonResponse/MockHandler同样改为从@standhigher/local-fetch-mock引入。- 建议保留健康检查:
GET /openapi/v1/mock/example返回data.message: Local mock is active。可从example/mock/json/example/ping/和example/mock/handlers.ts把 ping 规则并进现有 handlers(ping 放数组前面,不删业务规则)。
健康检查
启动本地 webpack(LOCAL_DEV=1)。本仓库 admin:
pnpm --filter sh-shopify-app-admin-template dev:local浏览器控制台:
fetch("/openapi/v1/mock/example").then((r) => r.json());应得到 data.message: "Local mock is active"。未注册路径继续走真实后端。
加接口
- 复制
mock/json/example/ping/为mock/json/<domain>/<operation>/。 - 写
scenes/*.json、active-scene.ts、scene-modules.ts、_scene.ts。场景 JSON 必须显式 import,不要用目录通配。 - 在
handlers.ts追加 match/handle。具体业务路径优先匹配;ping 规则放数组前面。
场景文件形状:
{
"code": "SUCCESS",
"message": "success",
"traceId": "example-default",
"data": {},
"status": 200
}status 可选,缺省 200。code / message / traceId / data 必填。
.gitignore
禁止裸规则 mock(会误伤任何名为 mock 的目录)。按应用路径写:
/mock
/apps/<app>/mock本仓库 admin 使用 /apps/admin/mock。
边界
- 只拦截浏览器
window.fetch,不拦 Node / RSC /http。 - Mock 入口必须在 Next 应用目录内。
- 只
transpilePackages: ["@standhigher/local-fetch-mock"]。 - 只在客户端壳层静态
import "@local-dev-mock"。 resolveLocalDevMockAlias第一参数是 Next 应用目录,不是仓库根。- 不要把其它项目的业务 JSON 拷来拷去;每个应用维护自己的
mock/json/。
