{"_id":"@edgeone/is-spa","_rev":"7-8cb1a816991420d33bb3519792d35c65","name":"@edgeone/is-spa","dist-tags":{"latest":"0.0.3"},"versions":{"0.0.1":{"name":"@edgeone/is-spa","version":"0.0.1","keywords":["spa","mpa","hybrid","ssg","detect","single-page-application","multi-page-application","static-site-generation","nginx","fallback","deploy","vue","react","angular","svelte","nextjs","nuxt","sveltekit","remix","vite"],"author":{"name":"corbinlin"},"license":"MIT","_id":"@edgeone/is-spa@0.0.1","maintainers":[{"name":"xiaban","email":"980892894@qq.com"},{"name":"tomcomtang","email":"364786053@qq.com"},{"name":"corbinlin","email":"corbinlin@tencent.com"},{"name":"shirlyyang","email":"shirlyyang@tencent.com"},{"name":"sagacheng","email":"sagacheng@tencent.com"},{"name":"venzil","email":"q153877011@gmail.com"},{"name":"shiyangchen","email":"shiyangchen@tencent.com"},{"name":"tencent-player","email":"vincentlli@tencent.com"},{"name":"simxin","email":"simxin@tencent.com"}],"homepage":"https://git.woa.com/corbinlin/spa-map-test/tree/main/is-spa-project#readme","bugs":{"url":"https://git.woa.com/corbinlin/spa-map-test/issues"},"bin":{"is-spa":"bin/cli.js"},"dist":{"shasum":"02797acb624133c293d081301bd1b0041a911400","tarball":"https://registry.npmjs.org/@edgeone/is-spa/-/is-spa-0.0.1.tgz","fileCount":10,"integrity":"sha512-w3UTEb2z1nNR2jNuUURpKj0Fj8WKikpqw/R9Chy/RVuf1E2Jw8refhhTnB/VG89GuND8SIhtr9MZc8J9BOuLOg==","signatures":[{"sig":"MEUCIQDx0M4gyEZKEuUCMmHEl1l9q77w3EJiFVeDnByMLChvCAIgK4gK2a56xBfoGurNBEk7jzz8shKqKCm4C+VbsQ4S3Gk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51627},"main":"lib/index.js","engines":{"node":">=14.0.0"},"exports":{".":"./lib/index.js"},"gitHead":"45eded3172a7abfd1daff17a7cd1a13b7b1d9c54","scripts":{"test":"node test/run.js","test:all":"node test/run.js && node test/run-dist.js","test:dist":"node test/run-dist.js","prepublishOnly":"npm run test:all"},"_npmUser":{"name":"corbinlin","email":"corbinlin@tencent.com"},"repository":{"url":"git+https://git.woa.com/corbinlin/spa-map-test.git","type":"git","directory":"is-spa-project"},"_npmVersion":"10.8.2","description":"Detect whether a frontend project is SPA, MPA, Hybrid, or SSG. Determine if Nginx/CDN fallback to index.html is needed.","directories":{},"_nodeVersion":"20.19.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/is-spa_0.0.1_1776061595394_0.44163359969478777","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@edgeone/is-spa","version":"0.0.2","keywords":["spa","mpa","hybrid","ssg","detect","single-page-application","multi-page-application","static-site-generation","nginx","fallback","deploy","vue","react","angular","svelte","nextjs","nuxt","sveltekit","remix","vite"],"author":{"name":"corbinlin"},"license":"MIT","_id":"@edgeone/is-spa@0.0.2","maintainers":[{"name":"xiaban","email":"980892894@qq.com"},{"name":"tomcomtang","email":"364786053@qq.com"},{"name":"corbinlin","email":"corbinlin@tencent.com"},{"name":"shirlyyang","email":"shirlyyang@tencent.com"},{"name":"sagacheng","email":"sagacheng@tencent.com"},{"name":"venzil","email":"q153877011@gmail.com"},{"name":"shiyangchen","email":"shiyangchen@tencent.com"},{"name":"tencent-player","email":"vincentlli@tencent.com"},{"name":"simxin","email":"simxin@tencent.com"}],"bin":{"is-spa":"bin/cli.js"},"dist":{"shasum":"e08b17c87049533c264c71caa9f5f5e3ba2dfae7","tarball":"https://registry.npmjs.org/@edgeone/is-spa/-/is-spa-0.0.2.tgz","fileCount":10,"integrity":"sha512-rMe0RxQ9YIDPQ1p4lP8uFkocMnkX4z2JKAnnrzRdWkjB3u6gsjRdjIn1DjDSZgrJUhLgYYVsd6+MuC8LJGu8uA==","signatures":[{"sig":"MEUCIQCnqS3QbQGUZSEckmxhllOexAkahbvw2tI1imOb0RYiSQIgGIj0OemDrUmg6vBpCWhrlSuSznkA3R+75GjCzI4QHtU=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":51314},"main":"lib/index.js","engines":{"node":">=14.0.0"},"exports":{".":"./lib/index.js"},"gitHead":"45eded3172a7abfd1daff17a7cd1a13b7b1d9c54","scripts":{"test":"node test/run.js","test:all":"node test/run.js && node test/run-dist.js","test:dist":"node test/run-dist.js","prepublishOnly":"npm run test:all"},"_npmUser":{"name":"corbinlin","email":"corbinlin@tencent.com"},"_npmVersion":"10.8.2","description":"Detect whether a frontend project is SPA, MPA, Hybrid, or SSG. Determine if Nginx/CDN fallback to index.html is needed.","directories":{},"_nodeVersion":"20.19.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/is-spa_0.0.2_1776064521249_0.6010837483590143","host":"s3://npm-registry-packages-npm-production"}},"0.0.3":{"name":"@edgeone/is-spa","version":"0.0.3","keywords":["spa","mpa","hybrid","ssg","detect","single-page-application","multi-page-application","static-site-generation","nginx","fallback","deploy","vue","react","angular","svelte","nextjs","nuxt","sveltekit","remix","vite"],"author":{"name":"corbinlin"},"license":"MIT","_id":"@edgeone/is-spa@0.0.3","maintainers":[{"name":"xiaban","email":"980892894@qq.com"},{"name":"tomcomtang","email":"364786053@qq.com"},{"name":"corbinlin","email":"corbinlin@tencent.com"},{"name":"shirlyyang","email":"shirlyyang@tencent.com"},{"name":"sagacheng","email":"sagacheng@tencent.com"},{"name":"venzil","email":"q153877011@gmail.com"},{"name":"shiyangchen","email":"shiyangchen@tencent.com"},{"name":"tencent-player","email":"vincentlli@tencent.com"},{"name":"simxin","email":"simxin@tencent.com"}],"bin":{"is-spa":"bin/cli.js"},"dist":{"shasum":"983d6b903710146f7073ffb02526f00121931b91","tarball":"https://registry.npmjs.org/@edgeone/is-spa/-/is-spa-0.0.3.tgz","fileCount":10,"integrity":"sha512-kZbp5831oH3dUnpD5QOT3UfWJtV1ga7f/USf06bRlDxGXfRkI+ij5SXGBCJHsfNX3qN0sRxdxey9xnf5nzcamw==","signatures":[{"sig":"MEQCIF2bRhh3+y/leeDzqiWvLdhsiGNprByv5zBaa6W3R/19AiABb0NsWBnbDh8mx66hBWJ0VxLWFX/SVmQFM/Y/09zl/w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":52989},"main":"lib/index.js","engines":{"node":">=14.0.0"},"exports":{".":"./lib/index.js"},"gitHead":"0b3ffe2e7bbed2391b60752cbd347acfb50f9556","scripts":{"test":"node test/run.js","test:all":"node test/run.js && node test/run-dist.js","test:dist":"node test/run-dist.js","prepublishOnly":"npm run test:all"},"_npmUser":{"name":"corbinlin","email":"corbinlin@tencent.com"},"_npmVersion":"10.8.2","description":"Detect whether a frontend project is SPA, MPA, Hybrid, or SSG. Determine if Nginx/CDN fallback to index.html is needed.","directories":{},"_nodeVersion":"20.19.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/is-spa_0.0.3_1776940567786_0.01807654471521669","host":"s3://npm-registry-packages-npm-production"}}},"time":{"created":"2026-04-13T06:26:35.272Z","modified":"2026-09-15T06:26:55.270Z","0.0.1":"2026-04-13T06:26:35.516Z","0.0.2":"2026-04-13T07:15:21.408Z","0.0.3":"2026-04-23T10:36:07.940Z"},"author":{"name":"corbinlin"},"license":"MIT","keywords":["spa","mpa","hybrid","ssg","detect","single-page-application","multi-page-application","static-site-generation","nginx","fallback","deploy","vue","react","angular","svelte","nextjs","nuxt","sveltekit","remix","vite"],"description":"Detect whether a frontend project is SPA, MPA, Hybrid, or SSG. Determine if Nginx/CDN fallback to index.html is needed.","maintainers":[{"email":"980892894@qq.com","name":"xiaban"},{"email":"364786053@qq.com","name":"tomcomtang"},{"email":"corbinlin@tencent.com","name":"corbinlin"},{"email":"shirlyyang@tencent.com","name":"shirlyyang"},{"email":"sagacheng@tencent.com","name":"sagacheng"},{"email":"q153877011@gmail.com","name":"venzil"},{"email":"shiyangchen@tencent.com","name":"shiyangchen"},{"email":"vincentlli@tencent.com","name":"tencent-player"},{"email":"zhaodanzhao@tencent.com","name":"zhaodanzhao"},{"email":"xindeli@tencent.com","name":"xindeli"},{"email":"drogbaqb@gmail.com","name":"drogbaqu"},{"email":"946834832@qq.com","name":"xingchenhe"},{"email":"simxin@tencent.com","name":"simxin"}],"readme":"# is-spa-project\n\n检测前端项目是否为 SPA（单页应用），判断部署时是否需要配置 Nginx/CDN **回源兜底到 `index.html`**。\n\n支持**源码检测**和**构建产物检测**两种模式，覆盖 React、Vue、Angular、Svelte、Next.js、Nuxt、SvelteKit、Remix、React Router 等主流框架。\n\n## 快速开始\n\n```js\nconst { needsFallback } = require('is-spa-project')\n\n// 传入任意目录，自动判断\nneedsFallback('./my-project')       // true — 需要配置兜底\nneedsFallback('./my-project/dist')  // true\nneedsFallback('./my-nuxt-app')      // false — 不需要兜底\n```\n\n## 安装\n\n```bash\nnpm install is-spa-project\n```\n\n## API\n\n### `needsFallback(dir, options?): boolean`\n\n**推荐使用。** 判断是否需要配置回源兜底。\n\n```js\nconst { needsFallback } = require('is-spa-project')\n\n// 自动模式（默认）— 自动判断源码/产物\nneedsFallback('./cra-spa')          // true\nneedsFallback('./cra-spa/build')    // true\nneedsFallback('./nuxt-app')         // false\n\n// 强制源码检测\nneedsFallback('./my-project', { mode: 'source' })\n\n// 强制构建产物检测\nneedsFallback('./my-project/dist', { mode: 'dist' })\n```\n\n**options.mode**：\n\n| 值 | 说明 |\n|---|---|\n| `'auto'`（默认） | 自动判断目录是源码还是构建产物 |\n| `'source'` | 强制源码检测（分析 package.json、配置文件） |\n| `'dist'` | 强制构建产物检测（分析 HTML 和 JS 文件） |\n\n### `autoDetect(dir): DetectionResult`\n\n自动识别目录类型，返回完整检测结果。\n\n```js\nconst { autoDetect } = require('is-spa-project')\n\nconst result = autoDetect('./my-project')\nconsole.log(result.mode)       // 'source' | 'dist'\nconsole.log(result.type)       // 'spa' | 'mpa' | 'hybrid' | 'ssg' | 'unknown'\nconsole.log(result.confidence) // 'high' | 'medium' | 'low'\nconsole.log(result.reason)     // 人类可读的判定原因\n```\n\n### `detect(projectRoot): DetectionResult`\n\n源码检测模式。分析 `package.json`、配置文件、入口文件。\n\n### `detectBuildOutput(distDir): DetectionResult`\n\n构建产物检测模式。分析 HTML 文件和 JS bundle 中的路由代码。\n\n### `isSpa(projectRoot): boolean`\n\n源码检测简化版，等价于 `detect().type === 'spa' || 'hybrid'`。\n\n## CLI\n\n```bash\n# 自动模式（自动判断源码/产物）\nnpx is-spa ./my-project\nnpx is-spa ./my-project/dist\n\n# 强制构建产物模式\nnpx is-spa ./my-project/dist --dist\n\n# JSON 输出\nnpx is-spa ./my-project --json\nnpx is-spa ./my-project/dist --dist --json\n```\n\n## 检测结果类型\n\n| 类型 | 含义 | 需要兜底？ | 部署配置 |\n|------|------|-----------|---------|\n| **spa** | 构建产物只有单个 `index.html` | ✅ 需要 | `try_files $uri $uri/ /index.html` |\n| **hybrid** | SPA 主体 + 独立辅助页面 | ✅ 需要 | 主入口需要兜底 |\n| **mpa** | 多个独立 HTML 入口 | ❌ 不需要 | 每个路由有对应文件 |\n| **ssg** | SSR/SSG 框架，每个路由有对应 HTML | ❌ 不需要 | 框架构建工具自动处理 |\n| **unknown** | 无法判定 | ❌ 不需要 | — |\n\n## 支持的框架和路由库\n\n### 框架检测（快速路径）\n\n| 框架 | 依赖 | 默认判定 | 可切换为 SPA？ |\n|------|------|---------|-------------|\n| Create React App | `react-scripts` | spa | — |\n| Angular CLI | `@angular/cli` | spa | — |\n| Next.js | `next` | ssg | ❌ |\n| Nuxt | `nuxt` | ssg | ❌（generate 自动处理） |\n| Gatsby | `gatsby` | ssg | ❌ |\n| React Router (Framework) | `@react-router/dev` | ssg | ✅ `ssr: false` → spa |\n| SvelteKit | `@sveltejs/kit` | ssg | ✅ `fallback` → spa |\n| Remix | `@remix-run/react` | ssg | ✅ `ssr: false` → spa |\n\n### 路由库检测\n\n| 路由库 | 生态 |\n|--------|------|\n| `vue-router` | Vue |\n| `react-router-dom` / `react-router` | React |\n| `@tanstack/react-router` | React |\n| `wouter` | React |\n| `@angular/router` | Angular |\n| `svelte-routing` | Svelte |\n| `svelte-spa-router` | Svelte |\n| `page` (page.js) | Vanilla JS |\n| `navigo` | Vanilla JS |\n| `universal-router` | Vanilla JS |\n\n## 检测原理\n\n### 源码检测\n\n```\npackage.json → 匹配已知框架？→ 框架快速路径（可能读取配置文件）\n                    ↓ 否\n              检测路由库依赖 + 入口文件数量 + router 使用 → 综合判定\n```\n\n### 构建产物检测\n\n```\n根目录有 index.html？\n├── 没有 → unknown（不存在回源目标）\n└── 有 → 扫描 HTML + JS → 判定\n```\n\n| 信号 | SPA | SSG | MPA |\n|------|-----|-----|-----|\n| 应用 HTML 数 | 1 | 多个 | 多个 |\n| HTML body | 空壳 | 有预渲染内容 | 有各自内容 |\n| JS 含路由代码 | ✅ | ✅ | ❌ |\n| 多 HTML 共享 JS | — | ✅ | ❌ |\n\n### `needsFallback` 自动检测\n\n```\n传入目录 → 判断是源码还是产物\n├── 有 package.json（含 dependencies）或 src/ → 源码检测\n└── 否则 → 产物检测\n主模式结果 unknown(low) → 回退另一种模式（仅采纳 high 置信度）\n```\n\n## 测试\n\n```bash\nnpm test            # 源码检测（24 个测试项目）\nnpm run test:dist   # 构建产物检测（24 个测试项目）\nnpm run test:all    # 全部运行\n```\n\n测试覆盖 24 个项目：CRA、Vite+React、Vite+Vue、Angular、Vue CLI（SPA/MPA/Hybrid）、Next.js、Nuxt（SSG/SPA）、SvelteKit（SSG/SPA）、Remix（SSR/SPA）、React Router Framework（SPA）、TanStack Router、wouter、svelte-routing、svelte-spa-router、page.js、navigo、universal-router、原生 HTML（单页/多页）。\n\n## License\n\nMIT\n","readmeFilename":"README.md"}