@edgeone/nuxt-pages 1.0.0-beta.1
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 +255 -0
- package/dist/build/content/server.js +18 -0
- package/dist/build/content/static.js +17 -0
- package/dist/build/functions/server.js +17 -0
- package/dist/build/plugin-context.js +16 -0
- package/dist/build/routes.js +18 -0
- package/dist/build/templates/nuxt-handler-monorepo.tmpl.js +260 -0
- package/dist/esm-chunks/chunk-5JK44IEA.js +15106 -0
- package/dist/esm-chunks/chunk-6BT4RYQJ.js +43 -0
- package/dist/esm-chunks/chunk-7RNB5RB6.js +131 -0
- package/dist/esm-chunks/chunk-L23O2KDO.js +120 -0
- package/dist/esm-chunks/chunk-TP3RAVPL.js +5635 -0
- package/dist/esm-chunks/chunk-UFRAZNP3.js +261 -0
- package/dist/esm-chunks/chunk-V2LFVP3C.js +838 -0
- package/dist/esm-chunks/chunk-Y3YAV6NZ.js +439 -0
- package/dist/index.js +89 -0
- package/dist/types/options.js +6 -0
- package/dist/utils.js +29 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# EdgeOne Nuxt Deploy
|
|
2
|
+
|
|
3
|
+
A professional deployment package that seamlessly deploys your Nuxt 3/4 applications to Tencent Cloud EdgeOne platform with optimized performance and intelligent caching.
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **One-Click Deployment** - Automated build and deployment process for EdgeOne
|
|
8
|
+
- 🏗️ **Nitro Integration** - Full compatibility with Nuxt 3's Nitro engine
|
|
9
|
+
- 📦 **Monorepo Support** - Optimized templates for complex project structures
|
|
10
|
+
- 🎯 **Smart Caching** - Multi-layer caching with memory, regional blobs, and tag invalidation
|
|
11
|
+
- ⚡ **Performance Optimized** - Static asset handling, lazy loading, and OpenTelemetry tracing
|
|
12
|
+
- 🔧 **Auto Configuration** - Intelligent Nuxt config detection and modification
|
|
13
|
+
- 🌐 **SSR Ready** - Full server-side rendering support on EdgeOne
|
|
14
|
+
|
|
15
|
+
## 📋 Requirements
|
|
16
|
+
|
|
17
|
+
- **Nuxt**: 3+
|
|
18
|
+
- **Node.js**: 18.x or higher
|
|
19
|
+
- **EdgeOne**: Tencent Cloud EdgeOne account
|
|
20
|
+
|
|
21
|
+
## 🚀 Quick Start
|
|
22
|
+
|
|
23
|
+
### Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @edgeone/nuxt-pages
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Basic Usage
|
|
30
|
+
|
|
31
|
+
1. **Add to your build process:**
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
// In your build script or CI/CD pipeline
|
|
35
|
+
import { onPreBuild, onBuild, onPostBuild } from '@edgeone/nuxt-pages'
|
|
36
|
+
|
|
37
|
+
const buildOptions = {
|
|
38
|
+
cwd: process.cwd(),
|
|
39
|
+
env: process.env,
|
|
40
|
+
meta: {},
|
|
41
|
+
functions: {},
|
|
42
|
+
constants: {
|
|
43
|
+
PUBLISH_DIR: 'dist'
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Execute build phases
|
|
48
|
+
await onPreBuild(buildOptions)
|
|
49
|
+
await onBuild(buildOptions)
|
|
50
|
+
await onPostBuild(buildOptions)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
2. **Your Nuxt project will be automatically configured:**
|
|
54
|
+
|
|
55
|
+
The package will create or modify your `nuxt.config.ts`:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
export default defineNuxtConfig({
|
|
59
|
+
srcDir: 'app',
|
|
60
|
+
nitro: {
|
|
61
|
+
preset: 'node-server',
|
|
62
|
+
output: {
|
|
63
|
+
dir: '.edgeone',
|
|
64
|
+
publicDir: '.edgeone/assets',
|
|
65
|
+
serverDir: '.edgeone/server-handler',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
devtools: { enabled: true },
|
|
69
|
+
})
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 🏗️ Architecture
|
|
73
|
+
|
|
74
|
+
### Build Process
|
|
75
|
+
|
|
76
|
+
The deployment follows a three-phase approach:
|
|
77
|
+
|
|
78
|
+
1. **PreBuild Phase** (`onPreBuild`)
|
|
79
|
+
- Validates Nuxt version compatibility
|
|
80
|
+
- Configures Nitro build output
|
|
81
|
+
- Sets up EdgeOne-specific configurations
|
|
82
|
+
|
|
83
|
+
2. **Build Phase** (`onBuild`)
|
|
84
|
+
- Creates server handlers
|
|
85
|
+
- Generates route metadata for pages and API routes
|
|
86
|
+
- Patches Nitro handlers for EdgeOne compatibility
|
|
87
|
+
|
|
88
|
+
3. **PostBuild Phase** (`onPostBuild`)
|
|
89
|
+
- Restores original configurations
|
|
90
|
+
- Cleanup and optimization
|
|
91
|
+
|
|
92
|
+
### Caching Strategy
|
|
93
|
+
|
|
94
|
+
- **Memory Cache**: LRU cache for frequently accessed data
|
|
95
|
+
- **Regional Blobs**: Distributed storage for static assets
|
|
96
|
+
- **Tag Invalidation**: Smart cache invalidation based on content tags
|
|
97
|
+
- **Stale-While-Revalidate**: Background revalidation for optimal performance
|
|
98
|
+
|
|
99
|
+
## 📁 Project Structure
|
|
100
|
+
|
|
101
|
+
After deployment, your project will have:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
your-project/
|
|
105
|
+
├── .edgeone/
|
|
106
|
+
│ ├── assets/ # Static assets
|
|
107
|
+
│ ├── server-handler/ # Server-side code
|
|
108
|
+
│ │ ├── chunks/ # Nitro chunks
|
|
109
|
+
│ │ ├── handler.js # EdgeOne handler
|
|
110
|
+
│ │ └── index.mjs # Server entry point
|
|
111
|
+
│ └── dist/ # Runtime modules
|
|
112
|
+
├── app/ # Your Nuxt app (if using srcDir)
|
|
113
|
+
├── nuxt.config.ts # Auto-generated/modified config
|
|
114
|
+
└── package.json
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## ⚙️ Configuration
|
|
118
|
+
|
|
119
|
+
### Advanced Options
|
|
120
|
+
|
|
121
|
+
You can customize the deployment behavior:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Custom build options
|
|
125
|
+
const buildOptions = {
|
|
126
|
+
cwd: process.cwd(),
|
|
127
|
+
env: {
|
|
128
|
+
...process.env,
|
|
129
|
+
USE_REGIONAL_BLOBS: 'true',
|
|
130
|
+
NITRO_PORT: '9000'
|
|
131
|
+
},
|
|
132
|
+
meta: {
|
|
133
|
+
// Custom metadata
|
|
134
|
+
},
|
|
135
|
+
functions: {
|
|
136
|
+
// Function-specific settings
|
|
137
|
+
},
|
|
138
|
+
constants: {
|
|
139
|
+
PUBLISH_DIR: 'dist'
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Environment Variables
|
|
145
|
+
|
|
146
|
+
- `USE_REGIONAL_BLOBS`: Enable regional blob storage (default: true)
|
|
147
|
+
- `NITRO_PORT`: Development server port (default: 9000)
|
|
148
|
+
- `NITRO_PUBLIC_DIR`: Static assets directory
|
|
149
|
+
|
|
150
|
+
## 🎯 Monorepo Support
|
|
151
|
+
|
|
152
|
+
For monorepo projects, the package automatically detects the structure and uses optimized templates:
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// Automatic detection of monorepo structure
|
|
156
|
+
// Uses nuxt-handler-monorepo.tmpl.js for complex setups
|
|
157
|
+
// Handles working directory changes and path resolution
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## 🔧 Development
|
|
161
|
+
|
|
162
|
+
### Local Testing
|
|
163
|
+
|
|
164
|
+
The package includes a development server for local testing:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
# Start development server
|
|
168
|
+
npm run start
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Your Nuxt app will be available at `http://localhost:9000`
|
|
172
|
+
|
|
173
|
+
### Build Scripts
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"scripts": {
|
|
178
|
+
"build": "node ./tools/build.js",
|
|
179
|
+
"build:watch": "node ./tools/build.js --watch",
|
|
180
|
+
"start": "node dist/index.js",
|
|
181
|
+
"test": "ts-node src/test.ts"
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## 📊 Performance Features
|
|
187
|
+
|
|
188
|
+
- **Static Asset Optimization**: 1-year cache headers for static files
|
|
189
|
+
- **Lazy Loading**: Nitro app initialization on first request
|
|
190
|
+
- **OpenTelemetry Tracing**: Built-in performance monitoring
|
|
191
|
+
- **Error Handling**: Graceful fallbacks and error recovery
|
|
192
|
+
- **Memory Management**: Efficient memory usage with LRU caching
|
|
193
|
+
|
|
194
|
+
## 🚨 Compatibility
|
|
195
|
+
|
|
196
|
+
### Supported Features
|
|
197
|
+
- ✅ Nuxt 3+
|
|
198
|
+
- ✅ Server-Side Rendering (SSR)
|
|
199
|
+
- ✅ Static Site Generation (SSG)
|
|
200
|
+
- ✅ API Routes
|
|
201
|
+
- ✅ Middleware
|
|
202
|
+
- ✅ Plugins
|
|
203
|
+
- ✅ Monorepo structures
|
|
204
|
+
|
|
205
|
+
### Known Limitations
|
|
206
|
+
- ❌ `@nuxt/image` module (under development)
|
|
207
|
+
- ⚠️ Nuxt versions below 3 (compatibility in progress)
|
|
208
|
+
|
|
209
|
+
## 🛠️ Troubleshooting
|
|
210
|
+
|
|
211
|
+
### Common Issues
|
|
212
|
+
|
|
213
|
+
1. **Build Fails**: Ensure Nuxt version is 3+
|
|
214
|
+
2. **Module Conflicts**: Check for unsupported modules like `@nuxt/image`
|
|
215
|
+
3. **Path Issues**: Verify your project structure matches expected layout
|
|
216
|
+
|
|
217
|
+
### Debug Mode
|
|
218
|
+
|
|
219
|
+
Enable detailed logging:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
DEBUG=edgeone:* npm run build
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## 📝 API Reference
|
|
226
|
+
|
|
227
|
+
### Core Functions
|
|
228
|
+
|
|
229
|
+
#### `onPreBuild(options: BuildOptions)`
|
|
230
|
+
Prepares the project for EdgeOne deployment.
|
|
231
|
+
|
|
232
|
+
#### `onBuild(options: BuildOptions)`
|
|
233
|
+
Executes the main build process.
|
|
234
|
+
|
|
235
|
+
#### `onPostBuild(options: BuildOptions)`
|
|
236
|
+
Cleanup and finalization.
|
|
237
|
+
|
|
238
|
+
### Types
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
interface BuildOptions {
|
|
242
|
+
cwd: string
|
|
243
|
+
env: any
|
|
244
|
+
meta: any
|
|
245
|
+
functions: any
|
|
246
|
+
constants: {
|
|
247
|
+
PUBLISH_DIR: string
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## 📄 License
|
|
253
|
+
|
|
254
|
+
ISC License - see [LICENSE](LICENSE) file for details.
|
|
255
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
var require = await (async () => {
|
|
3
|
+
var { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
RUN_CONFIG_FILE,
|
|
9
|
+
copyNuxtServerCode,
|
|
10
|
+
verifyNuxtHandlerDirStructure
|
|
11
|
+
} from "../../esm-chunks/chunk-TP3RAVPL.js";
|
|
12
|
+
import "../../esm-chunks/chunk-V2LFVP3C.js";
|
|
13
|
+
import "../../esm-chunks/chunk-6BT4RYQJ.js";
|
|
14
|
+
export {
|
|
15
|
+
RUN_CONFIG_FILE,
|
|
16
|
+
copyNuxtServerCode,
|
|
17
|
+
verifyNuxtHandlerDirStructure
|
|
18
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
var require = await (async () => {
|
|
3
|
+
var { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
addNitroBuildOutputConfig,
|
|
9
|
+
resetNitroConfig
|
|
10
|
+
} from "../../esm-chunks/chunk-7RNB5RB6.js";
|
|
11
|
+
import "../../esm-chunks/chunk-5JK44IEA.js";
|
|
12
|
+
import "../../esm-chunks/chunk-V2LFVP3C.js";
|
|
13
|
+
import "../../esm-chunks/chunk-6BT4RYQJ.js";
|
|
14
|
+
export {
|
|
15
|
+
addNitroBuildOutputConfig,
|
|
16
|
+
resetNitroConfig
|
|
17
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
var require = await (async () => {
|
|
3
|
+
var { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createServerHandler,
|
|
9
|
+
patchNitroHandler
|
|
10
|
+
} from "../../esm-chunks/chunk-L23O2KDO.js";
|
|
11
|
+
import "../../esm-chunks/chunk-TP3RAVPL.js";
|
|
12
|
+
import "../../esm-chunks/chunk-V2LFVP3C.js";
|
|
13
|
+
import "../../esm-chunks/chunk-6BT4RYQJ.js";
|
|
14
|
+
export {
|
|
15
|
+
createServerHandler,
|
|
16
|
+
patchNitroHandler
|
|
17
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
var require = await (async () => {
|
|
3
|
+
var { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
PluginContext,
|
|
9
|
+
SERVER_HANDLER_NAME
|
|
10
|
+
} from "../esm-chunks/chunk-Y3YAV6NZ.js";
|
|
11
|
+
import "../esm-chunks/chunk-5JK44IEA.js";
|
|
12
|
+
import "../esm-chunks/chunk-6BT4RYQJ.js";
|
|
13
|
+
export {
|
|
14
|
+
PluginContext,
|
|
15
|
+
SERVER_HANDLER_NAME
|
|
16
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
var require = await (async () => {
|
|
3
|
+
var { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createNuxtApiRoutesMeta,
|
|
9
|
+
createNuxtPagesRouteMeta,
|
|
10
|
+
createNuxtRoutesMeta
|
|
11
|
+
} from "../esm-chunks/chunk-UFRAZNP3.js";
|
|
12
|
+
import "../esm-chunks/chunk-5JK44IEA.js";
|
|
13
|
+
import "../esm-chunks/chunk-6BT4RYQJ.js";
|
|
14
|
+
export {
|
|
15
|
+
createNuxtApiRoutesMeta,
|
|
16
|
+
createNuxtPagesRouteMeta,
|
|
17
|
+
createNuxtRoutesMeta
|
|
18
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { resolve, dirname } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
4
|
+
import { extname } from 'path';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// Static assets directory
|
|
10
|
+
const ASSET_DIR = resolve(__dirname, '../assets');
|
|
11
|
+
|
|
12
|
+
// MIME type mapping
|
|
13
|
+
const MIME_TYPES = {
|
|
14
|
+
'.html': 'text/html; charset=utf-8',
|
|
15
|
+
'.js': 'application/javascript',
|
|
16
|
+
'.css': 'text/css',
|
|
17
|
+
'.json': 'application/json',
|
|
18
|
+
'.png': 'image/png',
|
|
19
|
+
'.jpg': 'image/jpeg',
|
|
20
|
+
'.jpeg': 'image/jpeg',
|
|
21
|
+
'.gif': 'image/gif',
|
|
22
|
+
'.svg': 'image/svg+xml',
|
|
23
|
+
'.ico': 'image/x-icon',
|
|
24
|
+
'.txt': 'text/plain',
|
|
25
|
+
'.xml': 'application/xml'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get the MIME type of a file
|
|
30
|
+
*/
|
|
31
|
+
function getMimeType(filePath) {
|
|
32
|
+
const ext = extname(filePath).toLowerCase();
|
|
33
|
+
return MIME_TYPES[ext] || 'application/octet-stream';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handle static file requests
|
|
38
|
+
*/
|
|
39
|
+
function handleStaticFile(url) {
|
|
40
|
+
try {
|
|
41
|
+
// Remove query parameters
|
|
42
|
+
let cleanUrl = url.split('?')[0];
|
|
43
|
+
|
|
44
|
+
// Handle IPX image processing paths from @nuxt/image
|
|
45
|
+
// Convert /_ipx/s_800x600/hero.png to /hero.png
|
|
46
|
+
// Also handles nested paths: /_ipx/s_800x600/images/hero.png -> /images/hero.png
|
|
47
|
+
if (cleanUrl.startsWith('/_ipx/')) {
|
|
48
|
+
let newUrl = '';
|
|
49
|
+
|
|
50
|
+
// Remove /_ipx/ prefix
|
|
51
|
+
const ipxPath = cleanUrl.slice(6); // Remove '/_ipx/'
|
|
52
|
+
|
|
53
|
+
// IPX format: /_ipx/[params]/[original_path]
|
|
54
|
+
// Parameters are typically the first segment(s) and contain underscores/commas
|
|
55
|
+
// The original file path starts after the params
|
|
56
|
+
const pathSegments = ipxPath.split('/').filter(s => s); // Remove empty segments
|
|
57
|
+
|
|
58
|
+
if (pathSegments.length === 0) {
|
|
59
|
+
// Empty path after /_ipx/, skip
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check each segment for file extension
|
|
64
|
+
for (let i = 0; i < pathSegments.length; i++) {
|
|
65
|
+
const segment = pathSegments[i];
|
|
66
|
+
if(segment.startsWith('s_') || segment.startsWith('w_') || segment.startsWith('h_') || segment.startsWith('q_') || segment.startsWith('f_') || segment.startsWith('c_') || segment.startsWith('bg_') || segment.startsWith('blur_') || segment.startsWith('_')) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
// Check if segment ends with a known image extension
|
|
70
|
+
newUrl += '/' + segment;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if(newUrl.startsWith('/http')) newUrl = newUrl.slice(1);
|
|
74
|
+
|
|
75
|
+
if(newUrl.includes('http:/') && !newUrl.includes('http://')) {
|
|
76
|
+
newUrl = newUrl.replace('http:/', 'http://');
|
|
77
|
+
} else if(newUrl.includes('https:/') && !newUrl.includes('https://')) {
|
|
78
|
+
newUrl = newUrl.replace('https:/', 'https://');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
statusCode: 302,
|
|
83
|
+
headers: {
|
|
84
|
+
'from-server': 'true',
|
|
85
|
+
location: newUrl
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 本地调试寻找文件路径
|
|
91
|
+
const possiblePaths = [];
|
|
92
|
+
|
|
93
|
+
// Direct file path
|
|
94
|
+
const directPath = resolve(ASSET_DIR, cleanUrl.startsWith('/') ? cleanUrl.slice(1) : cleanUrl);
|
|
95
|
+
possiblePaths.push(directPath);
|
|
96
|
+
|
|
97
|
+
// Try each possible path
|
|
98
|
+
for (const filePath of possiblePaths) {
|
|
99
|
+
// Security check: ensure file is within asset directory
|
|
100
|
+
if (!filePath.startsWith(ASSET_DIR)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (existsSync(filePath) && statSync(filePath).isFile()) {
|
|
105
|
+
const content = readFileSync(filePath);
|
|
106
|
+
const mimeType = getMimeType(filePath);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
statusCode: 200,
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': mimeType,
|
|
112
|
+
'Content-Length': content.length.toString(),
|
|
113
|
+
'Cache-Control': 'public, max-age=31536000' // 1 year cache
|
|
114
|
+
},
|
|
115
|
+
body: content
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error('Static file error:', error);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Lazy load Nitro application
|
|
128
|
+
*/
|
|
129
|
+
let nitroApp = null;
|
|
130
|
+
async function getNitroApp() {
|
|
131
|
+
if (!nitroApp) {
|
|
132
|
+
// Set environment variables to prevent automatic server startup
|
|
133
|
+
process.env.NITRO_PORT = '';
|
|
134
|
+
process.env.PORT = '';
|
|
135
|
+
|
|
136
|
+
// Set correct static assets path
|
|
137
|
+
process.env.NITRO_PUBLIC_DIR = ASSET_DIR;
|
|
138
|
+
|
|
139
|
+
const nitroModule = await (async () => {
|
|
140
|
+
try {
|
|
141
|
+
return await import('./chunks/nitro/nitro.mjs')
|
|
142
|
+
} catch {
|
|
143
|
+
return await import('./chunks/_/nitro.mjs')
|
|
144
|
+
}
|
|
145
|
+
})()
|
|
146
|
+
|
|
147
|
+
const { {{USE_NITRO_APP_SYMBOL}}: useNitroApp } = nitroModule
|
|
148
|
+
nitroApp = useNitroApp();
|
|
149
|
+
}
|
|
150
|
+
return nitroApp;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function getBody(req) {
|
|
154
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
155
|
+
const chunks = [];
|
|
156
|
+
for await (const chunk of req) {
|
|
157
|
+
chunks.push(chunk);
|
|
158
|
+
}
|
|
159
|
+
const body = Buffer.concat(chunks).toString();
|
|
160
|
+
return body;
|
|
161
|
+
}
|
|
162
|
+
return '';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* EdgeOne function handler
|
|
167
|
+
*/
|
|
168
|
+
export default async function handler(req, res, context) {
|
|
169
|
+
try {
|
|
170
|
+
const url = req.url || '/';
|
|
171
|
+
const method = req.method || 'GET';
|
|
172
|
+
const headers = req.headers || new Headers();
|
|
173
|
+
const body = await getBody(req);
|
|
174
|
+
|
|
175
|
+
// First try to handle static assets
|
|
176
|
+
if (method === 'GET') {
|
|
177
|
+
const staticResponse = handleStaticFile(url);
|
|
178
|
+
if (staticResponse) {
|
|
179
|
+
return new Response(staticResponse.body, {
|
|
180
|
+
status: staticResponse.statusCode,
|
|
181
|
+
headers: staticResponse.headers
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Handle dynamic requests
|
|
187
|
+
const app = await getNitroApp();
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const response = await app.localCall({
|
|
191
|
+
url,
|
|
192
|
+
method,
|
|
193
|
+
headers,
|
|
194
|
+
body
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// 正确处理 headers,特别是多个 set-cookie 的情况
|
|
198
|
+
const responseHeaders = new Headers();
|
|
199
|
+
|
|
200
|
+
// 如果 response.headers 是 Headers 对象,直接复制
|
|
201
|
+
if (response.headers instanceof Headers) {
|
|
202
|
+
response.headers.forEach((value, key) => {
|
|
203
|
+
responseHeaders.append(key, value);
|
|
204
|
+
});
|
|
205
|
+
} else {
|
|
206
|
+
// 如果是普通对象,需要特殊处理 set-cookie 数组
|
|
207
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
208
|
+
const lowerKey = key.toLowerCase();
|
|
209
|
+
|
|
210
|
+
// set-cookie 是特殊 header,可以有多个值
|
|
211
|
+
if (lowerKey === 'set-cookie' && Array.isArray(value)) {
|
|
212
|
+
// 为每个 cookie 单独添加
|
|
213
|
+
value.forEach(cookie => {
|
|
214
|
+
responseHeaders.append('Set-Cookie', cookie);
|
|
215
|
+
});
|
|
216
|
+
} else {
|
|
217
|
+
// 其他 header 直接设置
|
|
218
|
+
responseHeaders.set(key, Array.isArray(value) ? value.join(', ') : value);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// console.log('responseHeaders.getSetCookie() --->', responseHeaders.getSetCookie());
|
|
224
|
+
return new Response(response.body, {
|
|
225
|
+
status: response.status || response.statusCode,
|
|
226
|
+
headers: responseHeaders
|
|
227
|
+
});
|
|
228
|
+
} catch (nitroError) {
|
|
229
|
+
// Handle Nitro static file read errors (prerender files not found)
|
|
230
|
+
// Check error and its cause property (H3Error may wrap actual error in cause)
|
|
231
|
+
const actualError = nitroError?.cause || nitroError;
|
|
232
|
+
const errorPath = actualError?.path || nitroError?.path;
|
|
233
|
+
const errorCode = actualError?.code || nitroError?.code;
|
|
234
|
+
|
|
235
|
+
// If error is due to prerender static file not found, try dynamic rendering
|
|
236
|
+
if (errorCode === 'ENOENT' &&
|
|
237
|
+
errorPath &&
|
|
238
|
+
(errorPath.includes('/assets/') || errorPath.includes('assets/')) &&
|
|
239
|
+
(errorPath.includes('/index.html') || errorPath.includes('index.html'))) {
|
|
240
|
+
console.warn(`Prerender file not found: ${errorPath}, falling back to dynamic rendering for ${url}`);
|
|
241
|
+
|
|
242
|
+
// If static file handling has been tried but file not found, should perform dynamic rendering
|
|
243
|
+
// Nitro should be able to handle dynamic routes, but if it still tries to read static files,
|
|
244
|
+
// it may be due to configuration issues. We throw an error directly to let user know to build or check configuration
|
|
245
|
+
throw new Error(`Prerender route ${url} not found. Make sure to run build first or configure routeRules correctly. Original error: ${actualError?.message || nitroError?.message}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Other errors are thrown directly
|
|
249
|
+
throw nitroError;
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return new Response(`Server Error: ${error.message}`, {
|
|
253
|
+
status: 500,
|
|
254
|
+
headers: {
|
|
255
|
+
'Content-Type': 'text/plain'
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|