@cruxplug/spa 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -10
- package/dist/index.cjs +13 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div align="center">
|
|
11
|
-
<img src="https://img.shields.io/badge/v-0.0.
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.0.3-black"/>
|
|
12
12
|
<img src="https://img.shields.io/badge/🔥-@cruxplug-black"/>
|
|
13
13
|
<br>
|
|
14
14
|
<img src="https://img.shields.io/badge/coverage----%25-brightgreen" alt="Test Coverage" />
|
|
@@ -59,7 +59,8 @@
|
|
|
59
59
|
const spaPlugin = serverSPA({
|
|
60
60
|
baseUrl: 'https://example.com',
|
|
61
61
|
clientEntry: './src/client/browser.tsx',
|
|
62
|
-
clientScriptPath: '/static/dist/js/
|
|
62
|
+
clientScriptPath: ['/static/dist/js/app.js'],
|
|
63
|
+
clientStylePath: ['/static/dist/css/style.css'],
|
|
63
64
|
enableAutoNotFound: true,
|
|
64
65
|
pages: [
|
|
65
66
|
{
|
|
@@ -85,7 +86,8 @@
|
|
|
85
86
|
```typescript
|
|
86
87
|
const spaPlugin = serverSPA({
|
|
87
88
|
baseUrl: 'https://example.com',
|
|
88
|
-
clientScriptPath: '/js/app.js',
|
|
89
|
+
clientScriptPath: ['/js/app.js'],
|
|
90
|
+
clientStylePath: ['/css/min.css'],
|
|
89
91
|
clientEntry: './src/client/index.tsx',
|
|
90
92
|
author: 'Your Company Name',
|
|
91
93
|
authorUrl: 'https://example.com/about',
|
|
@@ -108,7 +110,8 @@
|
|
|
108
110
|
```typescript
|
|
109
111
|
const spaPlugin = serverSPA({
|
|
110
112
|
baseUrl: 'https://example.com',
|
|
111
|
-
clientScriptPath: '/js/app.js',
|
|
113
|
+
clientScriptPath: ['/js/app.js'],
|
|
114
|
+
clientStylePath: ['/css/min.css'],
|
|
112
115
|
clientEntry: './src/client/index.tsx',
|
|
113
116
|
enableAutoNotFound: true,
|
|
114
117
|
errorPages: [
|
|
@@ -140,7 +143,8 @@
|
|
|
140
143
|
|
|
141
144
|
**Parameters:**
|
|
142
145
|
- `baseUrl`: Base URL for canonical links and SEO (required)
|
|
143
|
-
- `clientScriptPath`:
|
|
146
|
+
- `clientScriptPath`: Array of paths to client-side JS bundles (required)
|
|
147
|
+
- `clientStylePath`: Array of paths to client-side CSS files (optional)
|
|
144
148
|
- `clientEntry`: Path to client entry point (required)
|
|
145
149
|
- `pages`: Array of pages to serve as SPA (optional)
|
|
146
150
|
- `errorPages`: Array of error page configurations (optional)
|
|
@@ -156,7 +160,8 @@
|
|
|
156
160
|
```typescript
|
|
157
161
|
const plugin = serverSPA({
|
|
158
162
|
baseUrl: 'https://example.com',
|
|
159
|
-
clientScriptPath: '/js/app.js',
|
|
163
|
+
clientScriptPath: ['/js/app.js'],
|
|
164
|
+
clientStylePath: ['/css/min.css'],
|
|
160
165
|
clientEntry: './src/client/index.tsx',
|
|
161
166
|
enableAutoNotFound: true,
|
|
162
167
|
pages: [
|
|
@@ -190,8 +195,9 @@
|
|
|
190
195
|
|
|
191
196
|
// Content
|
|
192
197
|
contentType?: 'article' | 'product' | 'service' | 'app' | 'workspace' | 'page';
|
|
193
|
-
clientScriptPath?: string; // Override client script
|
|
194
|
-
|
|
198
|
+
clientScriptPath?: string[]; // Override client script paths
|
|
199
|
+
clientStylePath?: string[]; // Override client style paths
|
|
200
|
+
clientEntry?: string; // Override client entry
|
|
195
201
|
}
|
|
196
202
|
```
|
|
197
203
|
|
|
@@ -213,7 +219,8 @@
|
|
|
213
219
|
pages?: SPAPageConfig[];
|
|
214
220
|
errorPages?: ErrorPageConfig[];
|
|
215
221
|
clientEntry: string;
|
|
216
|
-
clientScriptPath: string;
|
|
222
|
+
clientScriptPath: string[]; // Array of script paths
|
|
223
|
+
clientStylePath?: string[]; // Array of style paths
|
|
217
224
|
author?: string;
|
|
218
225
|
authorUrl?: string;
|
|
219
226
|
defaultDescription?: string;
|
|
@@ -252,7 +259,8 @@
|
|
|
252
259
|
- Full HTML5 shell with doctype
|
|
253
260
|
- Integrated SEO and structured data
|
|
254
261
|
- App mount point (#app)
|
|
255
|
-
-
|
|
262
|
+
- Multiple module script loading (from array)
|
|
263
|
+
- Multiple stylesheet loading (from array)
|
|
256
264
|
|
|
257
265
|
- #### `createSPARoute(pageConfig, baseConfig): RouteDefinition`
|
|
258
266
|
> Creates CruxJS route definition
|
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
'use strict';function
|
|
1
|
+
'use strict';function l(e,t){let n=e.canonical||`${t.baseUrl}${e.path}`,o=e.robots||t.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",a=e.description||t.defaultDescription||"A modern single-page application",r=(e.keywords||t.defaultKeywords||[]).join(", ");return `
|
|
2
2
|
<!-- \u{1F50D} Core SEO Meta Tags -->
|
|
3
3
|
<meta charset="UTF-8" />
|
|
4
4
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
5
5
|
<meta name="description" content="${a}" />
|
|
6
|
-
${
|
|
6
|
+
${r?`<meta name="keywords" content="${r}" />`:""}
|
|
7
7
|
<meta name="robots" content="${o}" />
|
|
8
8
|
<meta name="language" content="en" />
|
|
9
9
|
<meta http-equiv="content-language" content="en-us" />
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
<meta name="theme-color" content="#000000" />
|
|
22
22
|
|
|
23
23
|
<!-- \u{1F517} Canonical & Prefetch -->
|
|
24
|
-
<link rel="canonical" href="${
|
|
25
|
-
|
|
24
|
+
<link rel="canonical" href="${n}" />
|
|
25
|
+
${(e.clientScriptPath||t.clientScriptPath)?.map(s=>`<link rel="prefetch" href="${s}" />`).join(`
|
|
26
|
+
`)}
|
|
26
27
|
|
|
27
28
|
<!-- \u26A1 Performance & Security -->
|
|
28
29
|
<meta name="format-detection" content="telephone=no" />
|
|
@@ -30,15 +31,18 @@
|
|
|
30
31
|
${e.ogImage?`<meta property="og:image" content="${e.ogImage}" />`:""}
|
|
31
32
|
<meta property="og:title" content="${e.title}" />
|
|
32
33
|
<meta property="og:description" content="${a}" />
|
|
33
|
-
<meta property="og:url" content="${
|
|
34
|
+
<meta property="og:url" content="${n}" />`}function c(e,t,n="WebPage"){let o=e.canonical||`${t.baseUrl}${e.path}`,a={"@context":"https://schema.org","@type":n,name:e.title,url:o,description:e.description||t.defaultDescription,inLanguage:"en",...e.contentType&&{genre:e.contentType},...t.author&&{author:{"@type":"Person",name:t.author,...t.authorUrl&&{url:t.authorUrl}}},...(e.expertise||e.experience||e.authority)&&{creator:{"@type":"Person",name:t.author||"Unknown",...e.expertise&&{expertise:e.expertise},...e.experience&&{experience:e.experience},...e.authority&&{authority:e.authority}}}};return `<script type="application/ld+json">${JSON.stringify(a,null,2)}</script>`}function p(e,t){let n=e.clientScriptPath||t.clientScriptPath,o=e.clientStylePath||t.clientStylePath||[],a=n.map(s=>` <script type="module" src="${s}"></script>`).join(`
|
|
35
|
+
`),r=o.map(s=>` <link rel="stylesheet" href="${s}" />`).join(`
|
|
36
|
+
`);return `<!DOCTYPE html>
|
|
34
37
|
<html lang="en">
|
|
35
38
|
<head>
|
|
36
|
-
${
|
|
37
|
-
${
|
|
39
|
+
${l(e,t)}
|
|
40
|
+
${c(e,t,e.contentType==="article"?"Article":"WebPage")}
|
|
41
|
+
${r||""}
|
|
38
42
|
</head>
|
|
39
43
|
<body>
|
|
40
44
|
<div id="app"></div>
|
|
41
|
-
|
|
45
|
+
${a}
|
|
42
46
|
</body>
|
|
43
|
-
</html>`}function
|
|
47
|
+
</html>`}function i(e,t){return {method:"GET",path:e.path,handler:n=>{let o=p(e,t);return n.html(o)}}}function P(e,t,n,o){if(o.startsWith("/api/"))return {status:e,headers:{"Content-Type":"application/json"},body:JSON.stringify({error:`Error ${e}`})};if(t.has(e)){let a=t.get(e),r=p(a,n);return {status:e,headers:{"Content-Type":"text/html; charset=utf-8"},body:r}}return {status:e,headers:{"Content-Type":"text/plain"},body:`Error ${e}`}}function u(e,t){return (n,o)=>{let a=P(n,e,t,o);return new Response(a.body,{status:a.status,headers:a.headers})}}function m(){return {statusCode:404,title:"404 - Page Not Found",path:"/404",description:"The page you are looking for could not be found.",keywords:["404","not found","error"],robots:"noindex, nofollow"}}function b(e){let t=[],n=new Map;if(e.pages&&e.pages.length>0)for(let r of e.pages)t.push(i(r,e));if(e.errorPages&&e.errorPages.length>0)for(let r of e.errorPages)n.set(r.statusCode,r),t.push(i(r,e));if(e.enableAutoNotFound&&!n.has(404)){let r=m();n.set(404,r),t.push(i(r,e));}let o=u(n,e);return {name:"@cruxplug/SPA",version:"0.1.0",routes:t,__spaErrorHandler:o,onRegister:async r=>{if(console.log(`[SPA Plugin] Registered ${t.length} SPA routes`),n.size>0){let s=Array.from(n.keys()).join(", ");console.log(`[SPA Plugin] Error pages configured for: ${s}`);}},onAwake:async r=>{console.log("[SPA Plugin] Awake phase - SPA routes ready");},onStart:async r=>{console.log("[SPA Plugin] Start phase - serving SPA");},onReady:async r=>{console.log("[SPA Plugin] Ready phase - SPA is fully operational");}}}exports.serverSPA=b;//# sourceMappingURL=index.cjs.map
|
|
44
48
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/seo.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","keywords","generateStructuredData","pageConfig","contentType","schema","generateSPAHTML","clientScript","createSPARoute","c","html","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","routes","errorPageConfig","defaultErrorPage","errorHandler","app","statusCodes","ctx"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n */\r\n export function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n const description = config.description || baseConfig.defaultDescription || 'A modern single-page application';\r\n const keywords = (config.keywords || baseConfig.defaultKeywords || []).join(', ');\r\n\r\n return `\r\n <!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.expertise ? `<meta name=\"expertise\" content=\"${config.expertise}\" />` : ''}\r\n ${config.experience ? `<meta name=\"experience\" content=\"${config.experience}\" />` : ''}\r\n ${config.authority ? `<meta name=\"authority\" content=\"${config.authority}\" />` : ''}\r\n\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n <link rel=\"prefetch\" href=\"${config.clientScriptPath || baseConfig.clientScriptPath}\" />\r\n\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n <meta property=\"og:title\" content=\"${config.title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />`;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n */\r\n export function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage'\r\n ): string {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': pageConfig.title,\r\n 'url': canonicalUrl,\r\n 'description': pageConfig.description || baseConfig.defaultDescription,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...(pageConfig.expertise || pageConfig.experience || pageConfig.authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(pageConfig.expertise && { 'expertise': pageConfig.expertise }),\r\n ...(pageConfig.experience && { 'experience': pageConfig.experience }),\r\n ...(pageConfig.authority && { 'authority': pageConfig.authority })\r\n }\r\n }\r\n };\r\n\r\n return `<script type=\"application/ld+json\">${JSON.stringify(schema, null, 2)}</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript entry point\r\n */\r\n export function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const clientScript = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n ${generateSEOMetaTags(pageConfig, baseConfig)}\r\n ${generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage')}\r\n</head>\r\n<body>\r\n <div id=\"app\"></div>\r\n <script type=\"module\" src=\"${clientScript}\"></script>\r\n</body>\r\n</html>`;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: (c: AppContext) => {\r\n const html = generateSPAHTML(pageConfig, baseConfig);\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n * \r\n * Returns HTML for web requests and JSON for API requests\r\n */\r\n export function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): ErrorResponse {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n const html = generateSPAHTML(errorConfig, baseConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n * \r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Response {\r\n return (statusCode: number, path: string) => {\r\n const errorResponse = buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { generateSPAHTML, createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page } from './utils/errors';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl: 'https://example.com',\r\n * clientEntry: './src/client/browser.tsx',\r\n * clientScriptPath: '/static/dist/js/browser.js',\r\n * enableAutoNotFound: true, // Auto-handle 404s\r\n * pages: [\r\n * {\r\n * title: 'Home',\r\n * path: '/',\r\n * description: 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode: 404,\r\n * title: '404 - Not Found',\r\n * path: '/404',\r\n * description: 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config));\r\n }\r\n\r\n // Create error handler function\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.0',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/seo.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","keywords","script","generateStructuredData","pageConfig","contentType","schema","generateSPAHTML","clientScripts","clientStyles","scriptTags","styleTags","style","createSPARoute","c","html","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","routes","errorPageConfig","defaultErrorPage","errorHandler","app","statusCodes","ctx"],"mappings":"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","file":"index.cjs","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n */\r\n export function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n const description = config.description || baseConfig.defaultDescription || 'A modern single-page application';\r\n const keywords = (config.keywords || baseConfig.defaultKeywords || []).join(', ');\r\n\r\n return `\r\n <!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.expertise ? `<meta name=\"expertise\" content=\"${config.expertise}\" />` : ''}\r\n ${config.experience ? `<meta name=\"experience\" content=\"${config.experience}\" />` : ''}\r\n ${config.authority ? `<meta name=\"authority\" content=\"${config.authority}\" />` : ''}\r\n\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n ${(config.clientScriptPath || baseConfig.clientScriptPath)?.map(script => `<link rel=\"prefetch\" href=\"${script}\" />`).join('\\n ')}\r\n\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n <meta property=\"og:title\" content=\"${config.title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />`;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n */\r\n export function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage'\r\n ): string {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': pageConfig.title,\r\n 'url': canonicalUrl,\r\n 'description': pageConfig.description || baseConfig.defaultDescription,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...(pageConfig.expertise || pageConfig.experience || pageConfig.authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(pageConfig.expertise && { 'expertise': pageConfig.expertise }),\r\n ...(pageConfig.experience && { 'experience': pageConfig.experience }),\r\n ...(pageConfig.authority && { 'authority': pageConfig.authority })\r\n }\r\n }\r\n };\r\n\r\n return `<script type=\"application/ld+json\">${JSON.stringify(schema, null, 2)}</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript and style entry points\r\n */\r\n export function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const clientScripts = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n const clientStyles = pageConfig.clientStylePath || baseConfig.clientStylePath || [];\r\n\r\n const scriptTags = clientScripts\r\n .map(script => ` <script type=\"module\" src=\"${script}\"></script>`)\r\n .join('\\n');\r\n\r\n const styleTags = clientStyles\r\n .map(style => ` <link rel=\"stylesheet\" href=\"${style}\" />`)\r\n .join('\\n');\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n ${generateSEOMetaTags(pageConfig, baseConfig)}\r\n ${generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage')}\r\n ${styleTags ? styleTags : ''}\r\n</head>\r\n<body>\r\n <div id=\"app\"></div>\r\n${scriptTags}\r\n</body>\r\n</html>`;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: (c: AppContext) => {\r\n const html = generateSPAHTML(pageConfig, baseConfig);\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n * \r\n * Returns HTML for web requests and JSON for API requests\r\n */\r\n export function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): ErrorResponse {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n const html = generateSPAHTML(errorConfig, baseConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n * \r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Response {\r\n return (statusCode: number, path: string) => {\r\n const errorResponse = buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { generateSPAHTML, createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page } from './utils/errors';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl: 'https://example.com',\r\n * clientEntry: './src/client/browser.tsx',\r\n * clientScriptPath: '/static/dist/js/min.js',\r\n * clientStylePath: '/static/dist/css/min.css',\r\n * enableAutoNotFound: true, // Auto-handle 404s\r\n * pages: [\r\n * {\r\n * title: 'Home',\r\n * path: '/',\r\n * description: 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode: 404,\r\n * title: '404 - Not Found',\r\n * path: '/404',\r\n * description: 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config));\r\n }\r\n\r\n // Create error handler function\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.0',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -17,7 +17,8 @@ interface SPAPageConfig {
|
|
|
17
17
|
canonical?: string;
|
|
18
18
|
robots?: string;
|
|
19
19
|
clientEntry?: string;
|
|
20
|
-
clientScriptPath?: string;
|
|
20
|
+
clientScriptPath?: string[];
|
|
21
|
+
clientStylePath?: string[];
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* Error Page Configuration
|
|
@@ -34,7 +35,8 @@ interface ServerSPAPluginConfig {
|
|
|
34
35
|
pages?: SPAPageConfig[];
|
|
35
36
|
errorPages?: ErrorPageConfig[];
|
|
36
37
|
clientEntry: string;
|
|
37
|
-
clientScriptPath: string;
|
|
38
|
+
clientScriptPath: string[];
|
|
39
|
+
clientStylePath?: string[];
|
|
38
40
|
author?: string;
|
|
39
41
|
authorUrl?: string;
|
|
40
42
|
defaultDescription?: string;
|
|
@@ -54,7 +56,8 @@ interface ServerSPAPluginConfig {
|
|
|
54
56
|
* const spaPlugin = serverSPA({
|
|
55
57
|
* baseUrl: 'https://example.com',
|
|
56
58
|
* clientEntry: './src/client/browser.tsx',
|
|
57
|
-
* clientScriptPath: '/static/dist/js/
|
|
59
|
+
* clientScriptPath: '/static/dist/js/min.js',
|
|
60
|
+
* clientStylePath: '/static/dist/css/min.css',
|
|
58
61
|
* enableAutoNotFound: true, // Auto-handle 404s
|
|
59
62
|
* pages: [
|
|
60
63
|
* {
|
package/dist/index.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ interface SPAPageConfig {
|
|
|
17
17
|
canonical?: string;
|
|
18
18
|
robots?: string;
|
|
19
19
|
clientEntry?: string;
|
|
20
|
-
clientScriptPath?: string;
|
|
20
|
+
clientScriptPath?: string[];
|
|
21
|
+
clientStylePath?: string[];
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* Error Page Configuration
|
|
@@ -34,7 +35,8 @@ interface ServerSPAPluginConfig {
|
|
|
34
35
|
pages?: SPAPageConfig[];
|
|
35
36
|
errorPages?: ErrorPageConfig[];
|
|
36
37
|
clientEntry: string;
|
|
37
|
-
clientScriptPath: string;
|
|
38
|
+
clientScriptPath: string[];
|
|
39
|
+
clientStylePath?: string[];
|
|
38
40
|
author?: string;
|
|
39
41
|
authorUrl?: string;
|
|
40
42
|
defaultDescription?: string;
|
|
@@ -54,7 +56,8 @@ interface ServerSPAPluginConfig {
|
|
|
54
56
|
* const spaPlugin = serverSPA({
|
|
55
57
|
* baseUrl: 'https://example.com',
|
|
56
58
|
* clientEntry: './src/client/browser.tsx',
|
|
57
|
-
* clientScriptPath: '/static/dist/js/
|
|
59
|
+
* clientScriptPath: '/static/dist/js/min.js',
|
|
60
|
+
* clientStylePath: '/static/dist/css/min.css',
|
|
58
61
|
* enableAutoNotFound: true, // Auto-handle 404s
|
|
59
62
|
* pages: [
|
|
60
63
|
* {
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
function
|
|
1
|
+
function l(e,t){let n=e.canonical||`${t.baseUrl}${e.path}`,o=e.robots||t.defaultRobots||"index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1",a=e.description||t.defaultDescription||"A modern single-page application",r=(e.keywords||t.defaultKeywords||[]).join(", ");return `
|
|
2
2
|
<!-- \u{1F50D} Core SEO Meta Tags -->
|
|
3
3
|
<meta charset="UTF-8" />
|
|
4
4
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
5
5
|
<meta name="description" content="${a}" />
|
|
6
|
-
${
|
|
6
|
+
${r?`<meta name="keywords" content="${r}" />`:""}
|
|
7
7
|
<meta name="robots" content="${o}" />
|
|
8
8
|
<meta name="language" content="en" />
|
|
9
9
|
<meta http-equiv="content-language" content="en-us" />
|
|
@@ -21,8 +21,9 @@ function p(e,t){let r=e.canonical||`${t.baseUrl}${e.path}`,o=e.robots||t.default
|
|
|
21
21
|
<meta name="theme-color" content="#000000" />
|
|
22
22
|
|
|
23
23
|
<!-- \u{1F517} Canonical & Prefetch -->
|
|
24
|
-
<link rel="canonical" href="${
|
|
25
|
-
|
|
24
|
+
<link rel="canonical" href="${n}" />
|
|
25
|
+
${(e.clientScriptPath||t.clientScriptPath)?.map(s=>`<link rel="prefetch" href="${s}" />`).join(`
|
|
26
|
+
`)}
|
|
26
27
|
|
|
27
28
|
<!-- \u26A1 Performance & Security -->
|
|
28
29
|
<meta name="format-detection" content="telephone=no" />
|
|
@@ -30,15 +31,18 @@ function p(e,t){let r=e.canonical||`${t.baseUrl}${e.path}`,o=e.robots||t.default
|
|
|
30
31
|
${e.ogImage?`<meta property="og:image" content="${e.ogImage}" />`:""}
|
|
31
32
|
<meta property="og:title" content="${e.title}" />
|
|
32
33
|
<meta property="og:description" content="${a}" />
|
|
33
|
-
<meta property="og:url" content="${
|
|
34
|
+
<meta property="og:url" content="${n}" />`}function c(e,t,n="WebPage"){let o=e.canonical||`${t.baseUrl}${e.path}`,a={"@context":"https://schema.org","@type":n,name:e.title,url:o,description:e.description||t.defaultDescription,inLanguage:"en",...e.contentType&&{genre:e.contentType},...t.author&&{author:{"@type":"Person",name:t.author,...t.authorUrl&&{url:t.authorUrl}}},...(e.expertise||e.experience||e.authority)&&{creator:{"@type":"Person",name:t.author||"Unknown",...e.expertise&&{expertise:e.expertise},...e.experience&&{experience:e.experience},...e.authority&&{authority:e.authority}}}};return `<script type="application/ld+json">${JSON.stringify(a,null,2)}</script>`}function p(e,t){let n=e.clientScriptPath||t.clientScriptPath,o=e.clientStylePath||t.clientStylePath||[],a=n.map(s=>` <script type="module" src="${s}"></script>`).join(`
|
|
35
|
+
`),r=o.map(s=>` <link rel="stylesheet" href="${s}" />`).join(`
|
|
36
|
+
`);return `<!DOCTYPE html>
|
|
34
37
|
<html lang="en">
|
|
35
38
|
<head>
|
|
36
|
-
${
|
|
37
|
-
${
|
|
39
|
+
${l(e,t)}
|
|
40
|
+
${c(e,t,e.contentType==="article"?"Article":"WebPage")}
|
|
41
|
+
${r||""}
|
|
38
42
|
</head>
|
|
39
43
|
<body>
|
|
40
44
|
<div id="app"></div>
|
|
41
|
-
|
|
45
|
+
${a}
|
|
42
46
|
</body>
|
|
43
|
-
</html>`}function
|
|
47
|
+
</html>`}function i(e,t){return {method:"GET",path:e.path,handler:n=>{let o=p(e,t);return n.html(o)}}}function P(e,t,n,o){if(o.startsWith("/api/"))return {status:e,headers:{"Content-Type":"application/json"},body:JSON.stringify({error:`Error ${e}`})};if(t.has(e)){let a=t.get(e),r=p(a,n);return {status:e,headers:{"Content-Type":"text/html; charset=utf-8"},body:r}}return {status:e,headers:{"Content-Type":"text/plain"},body:`Error ${e}`}}function u(e,t){return (n,o)=>{let a=P(n,e,t,o);return new Response(a.body,{status:a.status,headers:a.headers})}}function m(){return {statusCode:404,title:"404 - Page Not Found",path:"/404",description:"The page you are looking for could not be found.",keywords:["404","not found","error"],robots:"noindex, nofollow"}}function b(e){let t=[],n=new Map;if(e.pages&&e.pages.length>0)for(let r of e.pages)t.push(i(r,e));if(e.errorPages&&e.errorPages.length>0)for(let r of e.errorPages)n.set(r.statusCode,r),t.push(i(r,e));if(e.enableAutoNotFound&&!n.has(404)){let r=m();n.set(404,r),t.push(i(r,e));}let o=u(n,e);return {name:"@cruxplug/SPA",version:"0.1.0",routes:t,__spaErrorHandler:o,onRegister:async r=>{if(console.log(`[SPA Plugin] Registered ${t.length} SPA routes`),n.size>0){let s=Array.from(n.keys()).join(", ");console.log(`[SPA Plugin] Error pages configured for: ${s}`);}},onAwake:async r=>{console.log("[SPA Plugin] Awake phase - SPA routes ready");},onStart:async r=>{console.log("[SPA Plugin] Start phase - serving SPA");},onReady:async r=>{console.log("[SPA Plugin] Ready phase - SPA is fully operational");}}}export{b as serverSPA};//# sourceMappingURL=index.js.map
|
|
44
48
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/seo.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","keywords","generateStructuredData","pageConfig","contentType","schema","generateSPAHTML","clientScript","createSPARoute","c","html","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","routes","errorPageConfig","defaultErrorPage","errorHandler","app","statusCodes","ctx"],"mappings":"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","file":"index.js","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n */\r\n export function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n const description = config.description || baseConfig.defaultDescription || 'A modern single-page application';\r\n const keywords = (config.keywords || baseConfig.defaultKeywords || []).join(', ');\r\n\r\n return `\r\n <!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.expertise ? `<meta name=\"expertise\" content=\"${config.expertise}\" />` : ''}\r\n ${config.experience ? `<meta name=\"experience\" content=\"${config.experience}\" />` : ''}\r\n ${config.authority ? `<meta name=\"authority\" content=\"${config.authority}\" />` : ''}\r\n\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n <link rel=\"prefetch\" href=\"${config.clientScriptPath || baseConfig.clientScriptPath}\" />\r\n\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n <meta property=\"og:title\" content=\"${config.title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />`;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n */\r\n export function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage'\r\n ): string {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': pageConfig.title,\r\n 'url': canonicalUrl,\r\n 'description': pageConfig.description || baseConfig.defaultDescription,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...(pageConfig.expertise || pageConfig.experience || pageConfig.authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(pageConfig.expertise && { 'expertise': pageConfig.expertise }),\r\n ...(pageConfig.experience && { 'experience': pageConfig.experience }),\r\n ...(pageConfig.authority && { 'authority': pageConfig.authority })\r\n }\r\n }\r\n };\r\n\r\n return `<script type=\"application/ld+json\">${JSON.stringify(schema, null, 2)}</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript entry point\r\n */\r\n export function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const clientScript = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n ${generateSEOMetaTags(pageConfig, baseConfig)}\r\n ${generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage')}\r\n</head>\r\n<body>\r\n <div id=\"app\"></div>\r\n <script type=\"module\" src=\"${clientScript}\"></script>\r\n</body>\r\n</html>`;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: (c: AppContext) => {\r\n const html = generateSPAHTML(pageConfig, baseConfig);\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n * \r\n * Returns HTML for web requests and JSON for API requests\r\n */\r\n export function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): ErrorResponse {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n const html = generateSPAHTML(errorConfig, baseConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n * \r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Response {\r\n return (statusCode: number, path: string) => {\r\n const errorResponse = buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { generateSPAHTML, createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page } from './utils/errors';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl: 'https://example.com',\r\n * clientEntry: './src/client/browser.tsx',\r\n * clientScriptPath: '/static/dist/js/browser.js',\r\n * enableAutoNotFound: true, // Auto-handle 404s\r\n * pages: [\r\n * {\r\n * title: 'Home',\r\n * path: '/',\r\n * description: 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode: 404,\r\n * title: '404 - Not Found',\r\n * path: '/404',\r\n * description: 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config));\r\n }\r\n\r\n // Create error handler function\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.0',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/seo.ts","../src/utils/spa.ts","../src/utils/errors.ts","../src/index.ts"],"names":["generateSEOMetaTags","config","baseConfig","canonicalUrl","robots","description","keywords","script","generateStructuredData","pageConfig","contentType","schema","generateSPAHTML","clientScripts","clientStyles","scriptTags","styleTags","style","createSPARoute","c","html","buildErrorResponse","statusCode","errorPageMap","path","errorConfig","createErrorHandler","errorResponse","createDefault404Page","serverSPA","routes","errorPageConfig","defaultErrorPage","errorHandler","app","statusCodes","ctx"],"mappings":"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","file":"index.js","sourcesContent":["// src/utils/seo.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SEO Meta Tags with E-E-A-T signals\r\n *\r\n * Includes:\r\n * - Core SEO metadata (charset, viewport, description, keywords, robots)\r\n * - E-E-A-T signals (expertise, experience, authority)\r\n * - Mobile optimization (web app capable, status bar style)\r\n * - Performance & security (prefetch, x-ua-compatible)\r\n * - Open Graph protocol tags\r\n */\r\n export function generateSEOMetaTags(\r\n config: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const canonicalUrl = config.canonical || `${baseConfig.baseUrl}${config.path}`;\r\n const robots = config.robots || baseConfig.defaultRobots || 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1';\r\n const description = config.description || baseConfig.defaultDescription || 'A modern single-page application';\r\n const keywords = (config.keywords || baseConfig.defaultKeywords || []).join(', ');\r\n\r\n return `\r\n <!-- 🔍 Core SEO Meta Tags -->\r\n <meta charset=\"UTF-8\" />\r\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\r\n <meta name=\"description\" content=\"${description}\" />\r\n ${keywords ? `<meta name=\"keywords\" content=\"${keywords}\" />` : ''}\r\n <meta name=\"robots\" content=\"${robots}\" />\r\n <meta name=\"language\" content=\"en\" />\r\n <meta http-equiv=\"content-language\" content=\"en-us\" />\r\n\r\n <!-- 👥 E-E-A-T Signals for AI Search -->\r\n ${baseConfig.author ? `<meta name=\"author\" content=\"${baseConfig.author}\" />` : ''}\r\n ${config.expertise ? `<meta name=\"expertise\" content=\"${config.expertise}\" />` : ''}\r\n ${config.experience ? `<meta name=\"experience\" content=\"${config.experience}\" />` : ''}\r\n ${config.authority ? `<meta name=\"authority\" content=\"${config.authority}\" />` : ''}\r\n\r\n <!-- 📱 Mobile & Performance -->\r\n <meta name=\"mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-capable\" content=\"yes\" />\r\n <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black-translucent\" />\r\n <meta name=\"theme-color\" content=\"#000000\" />\r\n\r\n <!-- 🔗 Canonical & Prefetch -->\r\n <link rel=\"canonical\" href=\"${canonicalUrl}\" />\r\n ${(config.clientScriptPath || baseConfig.clientScriptPath)?.map(script => `<link rel=\"prefetch\" href=\"${script}\" />`).join('\\n ')}\r\n\r\n <!-- ⚡ Performance & Security -->\r\n <meta name=\"format-detection\" content=\"telephone=no\" />\r\n <meta http-equiv=\"x-ua-compatible\" content=\"IE=edge\" />\r\n ${config.ogImage ? `<meta property=\"og:image\" content=\"${config.ogImage}\" />` : ''}\r\n <meta property=\"og:title\" content=\"${config.title}\" />\r\n <meta property=\"og:description\" content=\"${description}\" />\r\n <meta property=\"og:url\" content=\"${canonicalUrl}\" />`;\r\n }\r\n\r\n /**\r\n * Generate JSON-LD Structured Data\r\n *\r\n * Creates schema.org compatible structured data for:\r\n * - Rich snippets in search results\r\n * - AI overviews and knowledge panels\r\n * - Better indexing and SEO\r\n *\r\n * Supports multiple content types: WebPage, Article, Product, Service, etc.\r\n */\r\n export function generateStructuredData(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig,\r\n contentType: string = 'WebPage'\r\n ): string {\r\n const canonicalUrl = pageConfig.canonical || `${baseConfig.baseUrl}${pageConfig.path}`;\r\n\r\n const schema = {\r\n '@context': 'https://schema.org',\r\n '@type': contentType,\r\n 'name': pageConfig.title,\r\n 'url': canonicalUrl,\r\n 'description': pageConfig.description || baseConfig.defaultDescription,\r\n 'inLanguage': 'en',\r\n ...(pageConfig.contentType && { 'genre': pageConfig.contentType }),\r\n ...(baseConfig.author && {\r\n 'author': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author,\r\n ...(baseConfig.authorUrl && { 'url': baseConfig.authorUrl })\r\n }\r\n }),\r\n ...(pageConfig.expertise || pageConfig.experience || pageConfig.authority) && {\r\n 'creator': {\r\n '@type': 'Person',\r\n 'name': baseConfig.author || 'Unknown',\r\n ...(pageConfig.expertise && { 'expertise': pageConfig.expertise }),\r\n ...(pageConfig.experience && { 'experience': pageConfig.experience }),\r\n ...(pageConfig.authority && { 'authority': pageConfig.authority })\r\n }\r\n }\r\n };\r\n\r\n return `<script type=\"application/ld+json\">${JSON.stringify(schema, null, 2)}</script>`;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/spa.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { RouteDefinition, AppContext } from '@cruxjs/base';\r\n import type { SPAPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSEOMetaTags, generateStructuredData } from './seo';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Generate SPA HTML shell with SEO metadata\r\n *\r\n * Creates a complete HTML document with:\r\n * - SEO meta tags\r\n * - Structured data (JSON-LD)\r\n * - App mount point (#app)\r\n * - Client-side JavaScript and style entry points\r\n */\r\n export function generateSPAHTML(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): string {\r\n const clientScripts = pageConfig.clientScriptPath || baseConfig.clientScriptPath;\r\n const clientStyles = pageConfig.clientStylePath || baseConfig.clientStylePath || [];\r\n\r\n const scriptTags = clientScripts\r\n .map(script => ` <script type=\"module\" src=\"${script}\"></script>`)\r\n .join('\\n');\r\n\r\n const styleTags = clientStyles\r\n .map(style => ` <link rel=\"stylesheet\" href=\"${style}\" />`)\r\n .join('\\n');\r\n\r\n return `<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n ${generateSEOMetaTags(pageConfig, baseConfig)}\r\n ${generateStructuredData(pageConfig, baseConfig, pageConfig.contentType === 'article' ? 'Article' : 'WebPage')}\r\n ${styleTags ? styleTags : ''}\r\n</head>\r\n<body>\r\n <div id=\"app\"></div>\r\n${scriptTags}\r\n</body>\r\n</html>`;\r\n }\r\n\r\n /**\r\n * Create SPA route definition for a page\r\n *\r\n * Generates a RouteDefinition that handles GET requests\r\n * and returns the full SPA HTML shell with SEO data\r\n */\r\n export function createSPARoute(\r\n pageConfig: SPAPageConfig,\r\n baseConfig: ServerSPAPluginConfig\r\n ): RouteDefinition {\r\n return {\r\n method: 'GET',\r\n path: pageConfig.path,\r\n handler: (c: AppContext) => {\r\n const html = generateSPAHTML(pageConfig, baseConfig);\r\n return c.html(html);\r\n }\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","// src/utils/errors.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type { ErrorPageConfig, ServerSPAPluginConfig } from '../types';\r\n import { generateSPAHTML } from './spa';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ TYPE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Error response definition\r\n */\r\n interface ErrorResponse {\r\n status: number;\r\n headers: Record<string, string>;\r\n body: string;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ UTIL ════════════════════════════════════════╗\r\n\r\n /**\r\n * Build error response with appropriate content type\r\n * \r\n * Returns HTML for web requests and JSON for API requests\r\n */\r\n export function buildErrorResponse(\r\n statusCode: number,\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig,\r\n path: string\r\n ): ErrorResponse {\r\n // API requests get JSON responses\r\n if (path.startsWith('/api/')) {\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'application/json' },\r\n body: JSON.stringify({ error: `Error ${statusCode}` })\r\n };\r\n }\r\n\r\n // Try to find custom error page\r\n if (errorPageMap.has(statusCode)) {\r\n const errorConfig = errorPageMap.get(statusCode)!;\r\n const html = generateSPAHTML(errorConfig, baseConfig);\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\r\n body: html\r\n };\r\n }\r\n\r\n // Fallback: plain text error\r\n return {\r\n status: statusCode,\r\n headers: { 'Content-Type': 'text/plain' },\r\n body: `Error ${statusCode}`\r\n };\r\n }\r\n\r\n /**\r\n * Create error handler function for CruxJS\r\n * \r\n * Handles:\r\n * - 404 Not Found pages (with auto-generation support)\r\n * - Custom error pages by status code\r\n * - API vs web request differentiation\r\n * - Fallback error responses\r\n */\r\n export function createErrorHandler(\r\n errorPageMap: Map<number, ErrorPageConfig>,\r\n baseConfig: ServerSPAPluginConfig\r\n ): (statusCode: number, path: string) => Response {\r\n return (statusCode: number, path: string) => {\r\n const errorResponse = buildErrorResponse(statusCode, errorPageMap, baseConfig, path);\r\n return new Response(errorResponse.body, {\r\n status: errorResponse.status,\r\n headers: errorResponse.headers\r\n });\r\n };\r\n }\r\n\r\n /**\r\n * Create default 404 error page config\r\n */\r\n export function createDefault404Page(): ErrorPageConfig {\r\n return {\r\n statusCode: 404,\r\n title: '404 - Page Not Found',\r\n path: '/404',\r\n description: 'The page you are looking for could not be found.',\r\n keywords: ['404', 'not found', 'error'],\r\n robots: 'noindex, nofollow'\r\n };\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n","/* eslint-disable @typescript-eslint/no-unused-vars */\r\n/* eslint-disable @typescript-eslint/no-explicit-any */\r\n// src/index.ts\r\n//\r\n// Made with ❤️ by Maysara.\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ PACK ════════════════════════════════════════╗\r\n\r\n import type {\r\n CruxPlugin,\r\n AppInstance\r\n } from '@cruxjs/base';\r\n\r\n import * as types from './types';\r\n import { generateSPAHTML, createSPARoute } from './utils/spa';\r\n import { createErrorHandler, createDefault404Page } from './utils/errors';\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝\r\n\r\n\r\n\r\n// ╔════════════════════════════════════════ CORE ════════════════════════════════════════╗\r\n\r\n /**\r\n * Create Server SPA Plugin\r\n *\r\n * Generates SPA routes with SEO/CEO metadata and structured data\r\n * Error pages are handled via CruxJS error hooks\r\n *\r\n * @example\r\n * ```typescript\r\n * const spaPlugin = serverSPA({\r\n * baseUrl: 'https://example.com',\r\n * clientEntry: './src/client/browser.tsx',\r\n * clientScriptPath: '/static/dist/js/min.js',\r\n * clientStylePath: '/static/dist/css/min.css',\r\n * enableAutoNotFound: true, // Auto-handle 404s\r\n * pages: [\r\n * {\r\n * title: 'Home',\r\n * path: '/',\r\n * description: 'Welcome to our platform'\r\n * }\r\n * ],\r\n * errorPages: [\r\n * {\r\n * statusCode: 404,\r\n * title: '404 - Not Found',\r\n * path: '/404',\r\n * description: 'Page not found'\r\n * }\r\n * ]\r\n * });\r\n * ```\r\n */\r\n export function serverSPA(config: types.ServerSPAPluginConfig): CruxPlugin & { __spaErrorHandler?: any } {\r\n const routes = [];\r\n const errorPageMap = new Map<number, types.ErrorPageConfig>();\r\n\r\n // Generate routes from config\r\n if (config.pages && config.pages.length > 0) {\r\n for (const pageConfig of config.pages) {\r\n routes.push(createSPARoute(pageConfig, config));\r\n }\r\n }\r\n\r\n // Setup error pages\r\n if (config.errorPages && config.errorPages.length > 0) {\r\n for (const errorPageConfig of config.errorPages) {\r\n errorPageMap.set(errorPageConfig.statusCode, errorPageConfig);\r\n // Register error page as a regular route too (for direct access like /404)\r\n routes.push(createSPARoute(errorPageConfig, config));\r\n }\r\n }\r\n\r\n // Auto-generate 404 page if enabled and not already defined\r\n if (config.enableAutoNotFound && !errorPageMap.has(404)) {\r\n const defaultErrorPage = createDefault404Page();\r\n errorPageMap.set(404, defaultErrorPage);\r\n // Also register as a regular route\r\n routes.push(createSPARoute(defaultErrorPage, config));\r\n }\r\n\r\n // Create error handler function\r\n const errorHandler = createErrorHandler(errorPageMap, config);\r\n\r\n const plugin: CruxPlugin & { __spaErrorHandler?: any } = {\r\n name: '@cruxplug/SPA',\r\n version: '0.1.0',\r\n\r\n routes,\r\n\r\n // Attach error handler for CruxJS to use\r\n __spaErrorHandler: errorHandler,\r\n\r\n onRegister: async (app: AppInstance) => {\r\n console.log(`[SPA Plugin] Registered ${routes.length} SPA routes`);\r\n if (errorPageMap.size > 0) {\r\n const statusCodes = Array.from(errorPageMap.keys()).join(', ');\r\n console.log(`[SPA Plugin] Error pages configured for: ${statusCodes}`);\r\n }\r\n },\r\n\r\n onAwake: async (ctx: any) => {\r\n console.log('[SPA Plugin] Awake phase - SPA routes ready');\r\n },\r\n\r\n onStart: async (ctx: any) => {\r\n console.log('[SPA Plugin] Start phase - serving SPA');\r\n },\r\n\r\n onReady: async (ctx: any) => {\r\n console.log('[SPA Plugin] Ready phase - SPA is fully operational');\r\n }\r\n };\r\n\r\n return plugin;\r\n }\r\n\r\n// ╚══════════════════════════════════════════════════════════════════════════════════════╝"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cruxplug/spa",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "Single Page Application (SPA) plugin for CruxJS with built-in SEO/CEO support, E-E-A-T signals, and JSON-LD structured data generation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cruxjs",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"bun": "^1.3.3"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@cruxjs/base": "
|
|
50
|
+
"@cruxjs/base": "0.0.4"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@eslint/js": "^9.39.2",
|