@edgeone/nuxt-pages 1.0.0
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 +275 -0
- package/dist/build/content/server.js +18 -0
- package/dist/build/content/static.js +17 -0
- package/dist/build/functions/server.js +19 -0
- package/dist/build/plugin-context.js +18 -0
- package/dist/build/routes.js +18 -0
- package/dist/build/templates/nuxt-handler-backup.js +305 -0
- package/dist/build/templates/nuxt-handler-monorepo.tmpl-ipx_backup.js +511 -0
- package/dist/build/templates/nuxt-handler-monorepo.tmpl.js +243 -0
- package/dist/build/templates/nuxt-handler.tmpl.js +212 -0
- package/dist/esm-chunks/chunk-5YBUNNZ4.js +81 -0
- package/dist/esm-chunks/chunk-6BT4RYQJ.js +43 -0
- package/dist/esm-chunks/chunk-6YERJDAJ.js +208 -0
- package/dist/esm-chunks/chunk-GX4Z7KQX.js +15065 -0
- package/dist/esm-chunks/chunk-HBXUWFGE.js +19 -0
- package/dist/esm-chunks/chunk-HY3HNABZ.js +87 -0
- package/dist/esm-chunks/chunk-KGYBHZC3.js +1467 -0
- package/dist/esm-chunks/chunk-MMMRMLH2.js +132 -0
- package/dist/esm-chunks/chunk-NJ4SUJNF.js +5635 -0
- package/dist/esm-chunks/chunk-QG7JLDXY.js +127 -0
- package/dist/esm-chunks/chunk-RPSYO4VM.js +562 -0
- package/dist/esm-chunks/chunk-UOPC2N5A.js +69 -0
- package/dist/esm-chunks/chunk-V2LFVP3C.js +838 -0
- package/dist/index.js +61 -0
- package/dist/run/config.js +17 -0
- package/dist/run/constants.js +17 -0
- package/dist/run/handlers/cache.cjs +1410 -0
- package/dist/run/handlers/nuxt-cache.cjs +200 -0
- package/dist/run/handlers/nuxt-server.js +156 -0
- package/dist/run/handlers/request-context.cjs +148 -0
- package/dist/run/handlers/server.js +77 -0
- package/dist/run/handlers/tags-handler.cjs +177 -0
- package/dist/run/handlers/tracer.cjs +1004 -0
- package/dist/run/handlers/use-cache-handler.js +220 -0
- package/dist/run/handlers/wait-until.cjs +123 -0
- package/dist/run/headers.js +17 -0
- package/dist/run/revalidate.js +34 -0
- package/dist/run/storage/regional-blob-store.cjs +64 -0
- package/dist/run/storage/request-scoped-in-memory-cache.cjs +1582 -0
- package/dist/run/storage/storage.cjs +191 -0
- package/dist/shared/blob-types.cjs +37 -0
- package/dist/shared/blobkey.js +25 -0
- package/dist/shared/cache-types.cjs +33 -0
- package/dist/shared/nuxt-cache-types.cjs +18 -0
- package/dist/types/options.js +6 -0
- package/dist/utils.js +25 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# EdgeOne Nuxt Deploy
|
|
2
|
+
|
|
3
|
+
A professional deployment package that seamlessly deploys your Nuxt 3 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.8.0 or higher
|
|
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 @tencent/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 '@tencent/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.8.0+
|
|
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.8.0 (compatibility in progress)
|
|
208
|
+
|
|
209
|
+
## 🛠️ Troubleshooting
|
|
210
|
+
|
|
211
|
+
### Common Issues
|
|
212
|
+
|
|
213
|
+
1. **Build Fails**: Ensure Nuxt version is 3.8.0 or higher
|
|
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
|
+
## 🤝 Contributing
|
|
253
|
+
|
|
254
|
+
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
|
|
255
|
+
|
|
256
|
+
## 📄 License
|
|
257
|
+
|
|
258
|
+
ISC License - see [LICENSE](LICENSE) file for details.
|
|
259
|
+
|
|
260
|
+
## 🔗 Links
|
|
261
|
+
|
|
262
|
+
- [GitHub Repository](https://github.com/q153877011/edgeone-nuxt-deploy)
|
|
263
|
+
- [Issues](https://github.com/q153877011/edgeone-nuxt-deploy/issues)
|
|
264
|
+
- [Tencent Cloud EdgeOne](https://cloud.tencent.com/product/eo)
|
|
265
|
+
- [Nuxt 3 Documentation](https://nuxt.com)
|
|
266
|
+
|
|
267
|
+
## 📞 Support
|
|
268
|
+
|
|
269
|
+
- 📧 Create an issue on GitHub
|
|
270
|
+
- 💬 Community discussions
|
|
271
|
+
- 📖 Check the documentation
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
Made with ❤️ by [Venzil](https://github.com/q153877011) for the Nuxt community.
|
|
@@ -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-NJ4SUJNF.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-MMMRMLH2.js";
|
|
11
|
+
import "../../esm-chunks/chunk-V2LFVP3C.js";
|
|
12
|
+
import "../../esm-chunks/chunk-GX4Z7KQX.js";
|
|
13
|
+
import "../../esm-chunks/chunk-6BT4RYQJ.js";
|
|
14
|
+
export {
|
|
15
|
+
addNitroBuildOutputConfig,
|
|
16
|
+
resetNitroConfig
|
|
17
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
var require = await (async () => {
|
|
3
|
+
var { createRequire } = await import("node:module");
|
|
4
|
+
return createRequire(import.meta.url);
|
|
5
|
+
})();
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
clearStaleServerHandlers,
|
|
9
|
+
createServerHandler,
|
|
10
|
+
patchNitroHandler
|
|
11
|
+
} from "../../esm-chunks/chunk-QG7JLDXY.js";
|
|
12
|
+
import "../../esm-chunks/chunk-NJ4SUJNF.js";
|
|
13
|
+
import "../../esm-chunks/chunk-V2LFVP3C.js";
|
|
14
|
+
import "../../esm-chunks/chunk-6BT4RYQJ.js";
|
|
15
|
+
export {
|
|
16
|
+
clearStaleServerHandlers,
|
|
17
|
+
createServerHandler,
|
|
18
|
+
patchNitroHandler
|
|
19
|
+
};
|
|
@@ -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
|
+
EDGE_HANDLER_NAME,
|
|
9
|
+
PluginContext,
|
|
10
|
+
SERVER_HANDLER_NAME
|
|
11
|
+
} from "../esm-chunks/chunk-RPSYO4VM.js";
|
|
12
|
+
import "../esm-chunks/chunk-GX4Z7KQX.js";
|
|
13
|
+
import "../esm-chunks/chunk-6BT4RYQJ.js";
|
|
14
|
+
export {
|
|
15
|
+
EDGE_HANDLER_NAME,
|
|
16
|
+
PluginContext,
|
|
17
|
+
SERVER_HANDLER_NAME
|
|
18
|
+
};
|
|
@@ -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
|
+
convertNuxtRoutePattern,
|
|
9
|
+
createNuxtApiRoutesMeta,
|
|
10
|
+
createNuxtPagesRouteMeta
|
|
11
|
+
} from "../esm-chunks/chunk-6YERJDAJ.js";
|
|
12
|
+
import "../esm-chunks/chunk-GX4Z7KQX.js";
|
|
13
|
+
import "../esm-chunks/chunk-6BT4RYQJ.js";
|
|
14
|
+
export {
|
|
15
|
+
convertNuxtRoutePattern,
|
|
16
|
+
createNuxtApiRoutesMeta,
|
|
17
|
+
createNuxtPagesRouteMeta
|
|
18
|
+
};
|
|
@@ -0,0 +1,305 @@
|
|
|
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 HTTP response
|
|
38
|
+
*/
|
|
39
|
+
async function handleResponse(response, req, res, context) {
|
|
40
|
+
if (!response) {
|
|
41
|
+
res.statusCode = 500;
|
|
42
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
43
|
+
res.end('Server Error');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Ensure response.headers is a Headers object
|
|
48
|
+
if (!(response.headers instanceof Headers)) {
|
|
49
|
+
response.headers = new Headers(response.headers || {});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Correctly iterate over Headers object (using entries() method)
|
|
53
|
+
for (const [key, value] of response.headers.entries()) {
|
|
54
|
+
res.setHeader(key, value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if Content-Type already exists (case-insensitive)
|
|
58
|
+
const hasContentType = response.headers.has('content-type');
|
|
59
|
+
|
|
60
|
+
// Only set default value if Content-Type is missing
|
|
61
|
+
if (!hasContentType) {
|
|
62
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
63
|
+
}
|
|
64
|
+
// try {
|
|
65
|
+
// res.setHeader('functions-request-id', context.headers['x-scf-request-id']);
|
|
66
|
+
// } catch (error) {
|
|
67
|
+
// console.error('Handle response error:', error);
|
|
68
|
+
// }
|
|
69
|
+
|
|
70
|
+
res.setHeader('from-server', 'true');
|
|
71
|
+
|
|
72
|
+
// Handle set-cookie header (special handling, as there may be multiple values)
|
|
73
|
+
if (response.headers.has('set-cookie')) {
|
|
74
|
+
const cookieArr = response.headers.getSetCookie();
|
|
75
|
+
res.setHeader('set-cookie', Array.isArray(cookieArr) ? cookieArr : [cookieArr]);
|
|
76
|
+
// headers['set-cookie'] = Array.isArray(cookieArr) ? cookieArr : [cookieArr];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
res.statusCode = response.status || response.statusCode || 200;
|
|
80
|
+
if(Buffer.isBuffer(response.body)) {
|
|
81
|
+
res.end(response.body);
|
|
82
|
+
} else if(response.body && typeof response.body === 'object' && typeof response.body.getReader === 'function') {
|
|
83
|
+
const reader = response.body.getReader();
|
|
84
|
+
const chunks = [];
|
|
85
|
+
while (true) {
|
|
86
|
+
const { done, value } = await reader.read();
|
|
87
|
+
if (done) break;
|
|
88
|
+
chunks.push(Buffer.from(value));
|
|
89
|
+
}
|
|
90
|
+
res.end(Buffer.concat(chunks));
|
|
91
|
+
} else {
|
|
92
|
+
res.end(response.body || response._data || '');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Lazy load Nitro application
|
|
98
|
+
*/
|
|
99
|
+
let nitroApp = null;
|
|
100
|
+
async function getNitroApp() {
|
|
101
|
+
if (!nitroApp) {
|
|
102
|
+
// Set environment variables to prevent automatic server startup
|
|
103
|
+
process.env.NITRO_PORT = '';
|
|
104
|
+
process.env.PORT = '';
|
|
105
|
+
|
|
106
|
+
// Set correct static assets path
|
|
107
|
+
process.env.NITRO_PUBLIC_DIR = ASSET_DIR;
|
|
108
|
+
|
|
109
|
+
const { {{USE_NITRO_APP_SYMBOL}}: useNitroApp } = await import('./chunks/nitro/nitro.mjs');
|
|
110
|
+
nitroApp = useNitroApp();
|
|
111
|
+
}
|
|
112
|
+
return nitroApp;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Handle HTTP response
|
|
117
|
+
*/
|
|
118
|
+
function handleResponse(response, event) {
|
|
119
|
+
if (!response) {
|
|
120
|
+
return {
|
|
121
|
+
statusCode: 500,
|
|
122
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
123
|
+
body: 'Internal Server Error'
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const headers = {};
|
|
128
|
+
|
|
129
|
+
// Ensure response.headers is a Headers object
|
|
130
|
+
if (!(response.headers instanceof Headers)) {
|
|
131
|
+
response.headers = new Headers(response.headers || {});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Correctly iterate over Headers object (using entries() method)
|
|
135
|
+
for (const [key, value] of response.headers.entries()) {
|
|
136
|
+
headers[key] = value;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check if Content-Type already exists (case-insensitive)
|
|
140
|
+
const hasContentType = response.headers.has('content-type');
|
|
141
|
+
|
|
142
|
+
// Only set default value if Content-Type is missing
|
|
143
|
+
if (!hasContentType) {
|
|
144
|
+
headers['Content-Type'] = 'text/html; charset=utf-8';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
headers['from-server'] = 'true';
|
|
148
|
+
headers['functions-request-id'] = event.headers['x-scf-request-id'];
|
|
149
|
+
|
|
150
|
+
// Handle set-cookie header (special handling, as there may be multiple values)
|
|
151
|
+
if (response.headers.has('set-cookie')) {
|
|
152
|
+
const cookieArr = response.headers.getSetCookie();
|
|
153
|
+
headers['set-cookie'] = Array.isArray(cookieArr) ? cookieArr : [cookieArr];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
statusCode: response.status || response.statusCode || 200,
|
|
158
|
+
headers,
|
|
159
|
+
body: response.body || response._data || ''
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* EdgeOne function handler
|
|
165
|
+
*/
|
|
166
|
+
export async function handler(event) {
|
|
167
|
+
try {
|
|
168
|
+
const url = event.path || '/';
|
|
169
|
+
const method = event.httpMethod || event.method || 'GET';
|
|
170
|
+
const headers = event.headers || {};
|
|
171
|
+
const body = event.body || '';
|
|
172
|
+
|
|
173
|
+
// First try to handle static assets
|
|
174
|
+
if (method === 'GET') {
|
|
175
|
+
const staticResponse = handleStaticFile(url);
|
|
176
|
+
if (staticResponse) {
|
|
177
|
+
return staticResponse;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Handle dynamic requests
|
|
182
|
+
const app = await getNitroApp();
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const response = await app.localCall({
|
|
186
|
+
url,
|
|
187
|
+
method,
|
|
188
|
+
headers,
|
|
189
|
+
body
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
return handleResponse(response, event);
|
|
193
|
+
} catch (nitroError) {
|
|
194
|
+
// Handle Nitro static file read errors (prerender files not found)
|
|
195
|
+
// Check error and its cause property (H3Error may wrap actual error in cause)
|
|
196
|
+
const actualError = nitroError?.cause || nitroError;
|
|
197
|
+
const errorPath = actualError?.path || nitroError?.path;
|
|
198
|
+
const errorCode = actualError?.code || nitroError?.code;
|
|
199
|
+
|
|
200
|
+
// If error is due to prerender static file not found, try dynamic rendering
|
|
201
|
+
if (errorCode === 'ENOENT' &&
|
|
202
|
+
errorPath &&
|
|
203
|
+
(errorPath.includes('/assets/') || errorPath.includes('assets/')) &&
|
|
204
|
+
(errorPath.includes('/index.html') || errorPath.includes('index.html'))) {
|
|
205
|
+
console.warn(`Prerender file not found: ${errorPath}, falling back to dynamic rendering for ${url}`);
|
|
206
|
+
|
|
207
|
+
// If static file handling has been tried but file not found, should perform dynamic rendering
|
|
208
|
+
// Nitro should be able to handle dynamic routes, but if it still tries to read static files,
|
|
209
|
+
// it may be due to configuration issues. We throw an error directly to let user know to build or check configuration
|
|
210
|
+
throw new Error(`Prerender route ${url} not found. Make sure to run build first or configure routeRules correctly. Original error: ${actualError?.message || nitroError?.message}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Other errors are thrown directly
|
|
214
|
+
throw nitroError;
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error('EdgeOne handler error:', error);
|
|
218
|
+
return {
|
|
219
|
+
statusCode: 500,
|
|
220
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
221
|
+
body: `Internal Server Error: ${error.message}`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
import('http').then(async (http) => {
|
|
227
|
+
const { createServer } = http;
|
|
228
|
+
// Dynamically import stream module to handle ReadableStream
|
|
229
|
+
await import('stream').then(({ Readable, pipeline }) => {
|
|
230
|
+
const server = createServer(async (req, res) => {
|
|
231
|
+
try {
|
|
232
|
+
const event = {
|
|
233
|
+
path: req.url,
|
|
234
|
+
httpMethod: req.method,
|
|
235
|
+
headers: req.headers,
|
|
236
|
+
body: ''
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') {
|
|
240
|
+
const chunks = [];
|
|
241
|
+
for await (const chunk of req) {
|
|
242
|
+
chunks.push(chunk);
|
|
243
|
+
}
|
|
244
|
+
event.body = Buffer.concat(chunks).toString();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const result = await handler(event);
|
|
248
|
+
|
|
249
|
+
res.statusCode = result.statusCode;
|
|
250
|
+
// Object.entries(result.headers).forEach(([key, value]) => {
|
|
251
|
+
// if(key === 'set-cookie') {
|
|
252
|
+
res.setHeader('set-cookie', Array.isArray(value) ? value[0].split(',') : value);
|
|
253
|
+
// } else {
|
|
254
|
+
// res.setHeader(key, value);
|
|
255
|
+
// }
|
|
256
|
+
// });
|
|
257
|
+
// debug message
|
|
258
|
+
// res.setHeader('functions-request-id', req.headers['x-scf-request-id']);
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
// Handle response body: support Buffer, string, and ReadableStream
|
|
262
|
+
if (Buffer.isBuffer(result.body)) {
|
|
263
|
+
res.end(result.body);
|
|
264
|
+
} else if (result.body && typeof result.body === 'object' && typeof result.body.getReader === 'function') {
|
|
265
|
+
// Detect ReadableStream (Web Streams API)
|
|
266
|
+
try {
|
|
267
|
+
const nodeStream = Readable.fromWeb(result.body);
|
|
268
|
+
nodeStream.pipe(res);
|
|
269
|
+
} catch (streamError) {
|
|
270
|
+
console.error('Stream conversion error:', streamError);
|
|
271
|
+
// If conversion fails, try to read the entire stream
|
|
272
|
+
const reader = result.body.getReader();
|
|
273
|
+
const chunks = [];
|
|
274
|
+
try {
|
|
275
|
+
while (true) {
|
|
276
|
+
const { done, value } = await reader.read();
|
|
277
|
+
if (done) break;
|
|
278
|
+
chunks.push(Buffer.from(value));
|
|
279
|
+
}
|
|
280
|
+
res.end(Buffer.concat(chunks));
|
|
281
|
+
} catch (readError) {
|
|
282
|
+
console.error('Stream read error:', readError);
|
|
283
|
+
res.end();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} else {
|
|
287
|
+
// Handle string or other types
|
|
288
|
+
res.end(result.body || '');
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.error('Local server error:', error);
|
|
292
|
+
res.statusCode = 500;
|
|
293
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
294
|
+
res.end(`Server Error: ${error.message}`);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
const port = process.env.DEV_PORT || 9000;
|
|
299
|
+
server.listen(port, () => {
|
|
300
|
+
console.log(`EdgeOne development server running at http://localhost:${port}`);
|
|
301
|
+
console.log(`Static assets served from: ${ASSET_DIR}`);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|