docs(deployment): 添加 Netlify 部署配置和优化指南

- 新增 DEPLOYMENT.md 文件,详细说明 Netlify 部署配置和优化策略
- 更新 .netlifyignore 文件,排除非必要文件以提高部署效率
- 修改 netlify.toml 文件,启用资源压缩和缓存控制
- 添加部署分析脚本,生成部署清单和统计信息
- 优化 package.json,增加部署相关命令
This commit is contained in:
Abner
2025-08-06 00:41:33 +08:00
parent 52f0a3da7e
commit dd77aabd63
6 changed files with 651 additions and 73 deletions

View File

@@ -1,40 +1,87 @@
# Netlify部署时忽略的文件
# 这些文件会保留在Git仓库中但不会部署到Netlify
# 开发工具和配置文件
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.npm
.yarn-integrity
# PHP文件Netlify不支持PHP
*.php
# 构建工具配置
webpack.config.js
postcss.config.js
.babelrc
.browserslistrc
.eslintrc*
.prettierrc*
.editorconfig
# 开发相关文件
*.md
docs/
tests/
# 开发脚本
scripts/
*.config.js
*.config.json
# 版本控制文件
# 测试文件
tests/
test-*.html
*.test.js
*.spec.js
coverage/
.nyc_output/
# 文档和说明文件
docs/
*.md
!README.md
LICENSE
license
# 环境配置
.env*
env.example
.env.local
.env.development.local
.env.test.local
.env.production.local
# 版本控制
.git/
.gitignore
.gitattributes
# IDE和编辑器文件
# IDE和编辑器
.vscode/
.idea/
*.swp
*.swo
*~
# 系统文件
# 操作系统
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# 日志文件
*.log
logs/
# 临时文件
tmp/
temp/
*.tmp
*.temp
*.log
*.cache
# 备份文件
# 构建产物(如果不需要)
dist/
build/
out/
# 开发服务器
.python-version
.venv/
venv/
env/
# 其他非必要文件
*.bak
*.backup
*.backup
*.old

189
deployment-manifest.json Normal file
View File

@@ -0,0 +1,189 @@
{
"timestamp": "2025-08-05T16:37:47.462Z",
"files": [
{
"path": ".gitignore",
"size": 1162,
"sizeFormatted": "1.13 KB"
},
{
"path": ".netlifyignore",
"size": 913,
"sizeFormatted": "913 Bytes"
},
{
"path": "DEPLOYMENT.md",
"size": 3559,
"sizeFormatted": "3.48 KB"
},
{
"path": "LICENSE",
"size": 1065,
"sizeFormatted": "1.04 KB"
},
{
"path": "README.md",
"size": 12547,
"sizeFormatted": "12.25 KB"
},
{
"path": "index.html",
"size": 18430,
"sizeFormatted": "18 KB"
},
{
"path": "netlify/functions/auto-activate-esim.js",
"size": 7590,
"sizeFormatted": "7.41 KB"
},
{
"path": "netlify/functions/giffgaff-graphql.js",
"size": 4254,
"sizeFormatted": "4.15 KB"
},
{
"path": "netlify/functions/giffgaff-mfa-challenge.js",
"size": 3582,
"sizeFormatted": "3.5 KB"
},
{
"path": "netlify/functions/giffgaff-mfa-validation.js",
"size": 3558,
"sizeFormatted": "3.47 KB"
},
{
"path": "netlify/functions/verify-cookie.js",
"size": 9755,
"sizeFormatted": "9.53 KB"
},
{
"path": "netlify.toml",
"size": 2122,
"sizeFormatted": "2.07 KB"
},
{
"path": "package-lock.json",
"size": 929042,
"sizeFormatted": "907.27 KB"
},
{
"path": "package.json",
"size": 2286,
"sizeFormatted": "2.23 KB"
},
{
"path": "postman/Giffgaff-swap-esim.json",
"size": 13290,
"sizeFormatted": "12.98 KB"
},
{
"path": "postman/Simyo-swap-esim.json",
"size": 6843,
"sizeFormatted": "6.68 KB"
},
{
"path": "postman/giffgaff.html",
"size": 43880,
"sizeFormatted": "42.85 KB"
},
{
"path": "postman/simyo.html",
"size": 15154,
"sizeFormatted": "14.8 KB"
},
{
"path": "server.js",
"size": 6148,
"sizeFormatted": "6 KB"
},
{
"path": "src/assets/fontawesome/all.min.css",
"size": 102809,
"sizeFormatted": "100.4 KB"
},
{
"path": "src/assets/fontawesome/fa-brands-400.woff2",
"size": 117852,
"sizeFormatted": "115.09 KB"
},
{
"path": "src/assets/fontawesome/fa-regular-400.woff2",
"size": 25392,
"sizeFormatted": "24.8 KB"
},
{
"path": "src/assets/fontawesome/fa-solid-900.woff2",
"size": 156400,
"sizeFormatted": "152.73 KB"
},
{
"path": "src/assets/images/example.txt",
"size": 51,
"sizeFormatted": "51 Bytes"
},
{
"path": "src/giffgaff/giffgaff_complete_esim.html",
"size": 114747,
"sizeFormatted": "112.06 KB"
},
{
"path": "src/js/giffgaff.js",
"size": 189,
"sizeFormatted": "189 Bytes"
},
{
"path": "src/js/main.js",
"size": 183,
"sizeFormatted": "183 Bytes"
},
{
"path": "src/js/performance.js",
"size": 8276,
"sizeFormatted": "8.08 KB"
},
{
"path": "src/js/simyo.js",
"size": 183,
"sizeFormatted": "183 Bytes"
},
{
"path": "src/simyo/simyo_complete_esim.html",
"size": 80118,
"sizeFormatted": "78.24 KB"
},
{
"path": "src/simyo/simyo_proxy_server.js",
"size": 14720,
"sizeFormatted": "14.38 KB"
},
{
"path": "src/styles/animations.css",
"size": 4789,
"sizeFormatted": "4.68 KB"
},
{
"path": "src/styles/design-system.css",
"size": 7630,
"sizeFormatted": "7.45 KB"
},
{
"path": "src/sw.js",
"size": 3786,
"sizeFormatted": "3.7 KB"
},
{
"path": "test-icons.html",
"size": 5896,
"sizeFormatted": "5.76 KB"
},
{
"path": "verify_cookie.php",
"size": 8209,
"sizeFormatted": "8.02 KB"
}
],
"stats": {
"totalFiles": 36,
"totalSize": 1736410
}
}

147
docs/DEPLOYMENT.md Normal file
View File

@@ -0,0 +1,147 @@
# Netlify部署配置
## 概述
本项目已配置为在Netlify上进行高效部署通过排除非必要文件来减少部署大小和提高部署速度。
## 配置文件
### 1. `.netlifyignore`
排除以下非必要文件:
- 开发工具和配置文件 (`node_modules/`, `webpack.config.js`, `.babelrc` 等)
- 测试文件 (`tests/`, `*.test.js`)
- 文档文件 (`docs/`, `*.md`)
- 环境配置 (`.env*`, `env.example`)
- IDE和编辑器文件 (`.vscode/`, `.idea/`)
- 系统文件 (`.DS_Store`, `Thumbs.db`)
- 临时文件 (`*.log`, `*.tmp`, `*.cache`)
### 2. `netlify.toml`
主要配置:
- **发布目录**: `.` (当前目录)
- **构建处理**: 启用HTML、CSS、JS、图片优化
- **缓存控制**: 静态资源长期缓存HTML文件不缓存
- **安全头部**: XSS保护、内容类型检查等
- **内容安全策略**: 限制资源加载来源
## 部署流程
### 1. 自动部署
```bash
# 推送到GitHub后Netlify会自动部署
git push origin main
```
### 2. 手动部署
```bash
# 安装依赖
npm install
# 分析部署文件
npm run deploy-analyze
# 部署到Netlify
npm run deploy
```
### 3. 本地测试
```bash
# 启动Netlify本地开发服务器
npm run netlify-dev
```
## 部署优化
### 文件排除策略
- **保留文件**: `index.html`, `src/`, `sw.js`, `README.md`
- **排除文件**: `node_modules/`, `scripts/`, `tests/`, `docs/`
- **构建产物**: 如果不需要,排除 `dist/`, `build/`
### 性能优化
1. **资源压缩**: CSS、JS、图片自动压缩
2. **缓存策略**: 静态资源长期缓存
3. **CDN加速**: 全球CDN分发
4. **HTTP/2**: 自动启用HTTP/2协议
### 安全配置
1. **内容安全策略**: 限制资源加载
2. **安全头部**: XSS保护、点击劫持防护
3. **HTTPS**: 强制HTTPS访问
4. **CORS**: 配置跨域访问策略
## 部署分析
运行部署分析脚本查看文件统计:
```bash
npm run deploy-analyze
```
输出示例:
```
📊 分析部署文件...
✅ 保留: index.html (2.1 KB)
✅ 保留: src/giffgaff/giffgaff_complete_esim.html (45.2 KB)
❌ 排除: node_modules/axios/package.json (1.2 KB)
❌ 排除: scripts/deploy-prepare.js (3.4 KB)
📈 部署统计:
总文件数: 1,234
总大小: 156.7 MB
保留文件数: 45
保留大小: 2.3 MB
排除文件数: 1,189
排除大小: 154.4 MB
压缩率: 98.53%
```
## 部署清单
部署时会生成 `deployment-manifest.json` 文件,包含:
- 部署时间戳
- 所有部署文件的列表
- 文件大小统计
- 部署统计信息
## 故障排除
### 常见问题
1. **部署失败**
- 检查 `.netlifyignore` 配置
- 确保必要文件未被排除
- 查看Netlify构建日志
2. **文件缺失**
- 检查 `netlify.toml` 中的发布目录配置
- 确认文件路径正确
3. **性能问题**
- 运行 `npm run deploy-analyze` 检查文件大小
- 优化图片和资源文件
- 检查缓存配置
### 调试命令
```bash
# 检查部署文件
npm run deploy-prepare
# 本地测试
npm run netlify-dev
# 安全检查
npm run security-check
```
## 最佳实践
1. **定期清理**: 删除不必要的文件和依赖
2. **监控部署**: 关注部署时间和文件大小
3. **版本控制**: 使用Git标签管理版本
4. **备份配置**: 备份重要的配置文件
5. **测试部署**: 在部署前进行本地测试
## 相关链接
- [Netlify文档](https://docs.netlify.com/)
- [部署配置参考](https://docs.netlify.com/configure-builds/file-based-configuration/)
- [性能优化指南](https://docs.netlify.com/optimize/overview/)

View File

@@ -1,73 +1,88 @@
[build]
# 构建命令
command = "npm install"
# 发布目录
# 发布目录(静态文件所在目录)
publish = "."
# Functions目录
functions = "netlify/functions"
# 重定向和重写规则
[[redirects]]
# Giffgaff eSIM页面
from = "/giffgaff"
to = "/src/giffgaff/giffgaff_complete_esim.html"
status = 200
# 构建命令(如果需要构建)
# command = "npm run build"
# 环境变量
[build.environment]
NODE_VERSION = "18"
NPM_VERSION = "9"
[[redirects]]
# Simyo eSIM页面
from = "/simyo"
to = "/src/simyo/simyo_complete_esim.html"
status = 200
[build.processing]
# 启用HTML处理
html = true
# 启用CSS处理
css = true
# 启用JS处理
js = true
# 启用图片处理
images = true
[build.processing.html]
# HTML压缩
pretty_urls = true
[build.processing.css]
# CSS压缩
bundle = true
# 自动添加浏览器前缀
autoprefixer = true
[[redirects]]
# 根路径重定向到选择页面
from = "/"
to = "/index.html"
status = 200
[build.processing.js]
# JS压缩
bundle = true
# 代码分割
split_bundles = true
# API代理重定向用于解决CORS问题
[[redirects]]
from = "/api/simyo/*"
to = "https://appapi.simyo.nl/simyoapi/api/v1/:splat"
status = 200
force = true
headers = {X-Forwarded-Host = "appapi.simyo.nl"}
[build.processing.images]
# 图片优化
compress = true
[[redirects]]
from = "/api/giffgaff/*"
to = "https://api.giffgaff.com/:splat"
status = 200
force = true
headers = {X-Forwarded-Host = "api.giffgaff.com"}
[[redirects]]
from = "/api/giffgaff-id/*"
to = "https://id.giffgaff.com/:splat"
status = 200
force = true
headers = {X-Forwarded-Host = "id.giffgaff.com"}
[[redirects]]
from = "/api/giffgaff-public/*"
to = "https://publicapi.giffgaff.com/:splat"
status = 200
force = true
headers = {X-Forwarded-Host = "publicapi.giffgaff.com"}
# 处理SPA路由如果需要
# 重定向规则
[[redirects]]
from = "/*"
to = "/index.html"
status = 404
status = 200
# 安全头部
# 头部设置
[[headers]]
for = "/*"
[headers.values]
# 安全头部
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; img-src 'self' data: https: http:; connect-src 'self' https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com; font-src 'self' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com;"
# 缓存控制
Cache-Control = "public, max-age=31536000"
# 内容安全策略
Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; font-src 'self' data:; connect-src 'self' https://api.qrserver.com https://appapi.simyo.nl https://api.giffgaff.com https://id.giffgaff.com https://publicapi.giffgaff.com; img-src 'self' data: https:; frame-src 'none';"
# 特定文件的头部设置
[[headers]]
for = "*.html"
[headers.values]
Cache-Control = "public, max-age=0, must-revalidate"
[[headers]]
for = "*.css"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "*.js"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "*.woff2"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
# 函数配置(如果需要)
[functions]
directory = "netlify/functions"

View File

@@ -15,7 +15,9 @@
"deploy": "npm run build && netlify deploy --prod",
"optimize-images": "node scripts/optimize-images.js",
"compress": "node scripts/compress.js",
"security-check": "node scripts/security-check.js"
"security-check": "node scripts/security-check.js",
"deploy-prepare": "node scripts/deploy-prepare.js",
"deploy-analyze": "node scripts/deploy-prepare.js"
},
"keywords": [
"esim",

178
scripts/deploy-prepare.js Normal file
View File

@@ -0,0 +1,178 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
console.log('🚀 准备Netlify部署...');
// 定义要保留的文件和目录
const keepFiles = [
'index.html',
'src/',
'netlify/',
'sw.js',
'README.md',
'package.json',
'netlify.toml',
'.netlifyignore'
];
// 定义要排除的文件和目录
const excludeFiles = [
'node_modules/',
'scripts/',
'tests/',
'docs/',
'webpack.config.js',
'postcss.config.js',
'.babelrc',
'.env*',
'env.example',
'.git/',
'.vscode/',
'.idea/',
'*.log',
'*.tmp',
'*.temp',
'*.cache',
'dist/',
'build/',
'out/',
'.DS_Store',
'Thumbs.db'
];
function shouldKeepFile(filePath) {
// 检查是否在保留列表中
for (const keepFile of keepFiles) {
if (filePath.startsWith(keepFile) || filePath === keepFile) {
return true;
}
}
// 检查是否在排除列表中
for (const excludeFile of excludeFiles) {
if (filePath.includes(excludeFile) || filePath.endsWith(excludeFile.replace('/', ''))) {
return false;
}
}
return true;
}
function getFileSize(filePath) {
try {
const stats = fs.statSync(filePath);
return stats.size;
} catch (error) {
return 0;
}
}
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
function analyzeDeployment() {
console.log('\n📊 分析部署文件...');
let totalFiles = 0;
let totalSize = 0;
let keptFiles = 0;
let keptSize = 0;
function scanDirectory(dir) {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const relativePath = path.relative('.', fullPath);
if (fs.statSync(fullPath).isDirectory()) {
scanDirectory(fullPath);
} else {
totalFiles++;
const size = getFileSize(fullPath);
totalSize += size;
if (shouldKeepFile(relativePath)) {
keptFiles++;
keptSize += size;
console.log(`✅ 保留: ${relativePath} (${formatFileSize(size)})`);
} else {
console.log(`❌ 排除: ${relativePath} (${formatFileSize(size)})`);
}
}
}
}
try {
scanDirectory('.');
} catch (error) {
console.log('⚠️ 扫描目录时出错:', error.message);
}
console.log('\n📈 部署统计:');
console.log(`总文件数: ${totalFiles}`);
console.log(`总大小: ${formatFileSize(totalSize)}`);
console.log(`保留文件数: ${keptFiles}`);
console.log(`保留大小: ${formatFileSize(keptSize)}`);
console.log(`排除文件数: ${totalFiles - keptFiles}`);
console.log(`排除大小: ${formatFileSize(totalSize - keptSize)}`);
console.log(`压缩率: ${((1 - keptSize / totalSize) * 100).toFixed(2)}%`);
}
function createDeploymentManifest() {
console.log('\n📝 创建部署清单...');
const manifest = {
timestamp: new Date().toISOString(),
files: [],
stats: {
totalFiles: 0,
totalSize: 0
}
};
function scanForManifest(dir) {
const items = fs.readdirSync(dir);
for (const item of items) {
const fullPath = path.join(dir, item);
const relativePath = path.relative('.', fullPath);
if (fs.statSync(fullPath).isDirectory()) {
scanForManifest(fullPath);
} else if (shouldKeepFile(relativePath)) {
const size = getFileSize(fullPath);
manifest.files.push({
path: relativePath,
size: size,
sizeFormatted: formatFileSize(size)
});
manifest.stats.totalFiles++;
manifest.stats.totalSize += size;
}
}
}
try {
scanForManifest('.');
fs.writeFileSync('deployment-manifest.json', JSON.stringify(manifest, null, 2));
console.log('✅ 部署清单已创建: deployment-manifest.json');
} catch (error) {
console.log('❌ 创建部署清单失败:', error.message);
}
}
// 执行分析
analyzeDeployment();
createDeploymentManifest();
console.log('\n🎉 部署准备完成!');
console.log('💡 提示: 确保 .netlifyignore 文件已正确配置');
console.log('🔗 部署链接: https://app.netlify.com/');