195 lines
5.8 KiB
JavaScript
195 lines
5.8 KiB
JavaScript
|
|
#!/usr/bin/env node
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 测试静态构建和404页面功能
|
||
|
|
*/
|
||
|
|
|
||
|
|
const fs = require('fs');
|
||
|
|
const path = require('path');
|
||
|
|
|
||
|
|
function testStaticBuild() {
|
||
|
|
console.log('🧪 测试静态构建...\n');
|
||
|
|
|
||
|
|
const checks = [
|
||
|
|
{
|
||
|
|
name: 'out目录存在',
|
||
|
|
test: () => fs.existsSync('out'),
|
||
|
|
required: true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'index.html存在',
|
||
|
|
test: () => fs.existsSync('out/index.html'),
|
||
|
|
required: true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: '404.html存在',
|
||
|
|
test: () => fs.existsSync('out/404.html'),
|
||
|
|
required: true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'public/404.html存在',
|
||
|
|
test: () => fs.existsSync('public/404.html'),
|
||
|
|
required: true
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'zh-CN目录存在',
|
||
|
|
test: () => fs.existsSync('out/zh-CN'),
|
||
|
|
required: false
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'en目录存在',
|
||
|
|
test: () => fs.existsSync('out/en'),
|
||
|
|
required: false
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'zh-TW目录存在',
|
||
|
|
test: () => fs.existsSync('out/zh-TW'),
|
||
|
|
required: false
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
let passed = 0;
|
||
|
|
let failed = 0;
|
||
|
|
|
||
|
|
checks.forEach(check => {
|
||
|
|
const result = check.test();
|
||
|
|
const icon = result ? '✅' : (check.required ? '❌' : '⚠️');
|
||
|
|
const status = result ? 'PASS' : (check.required ? 'FAIL' : 'SKIP');
|
||
|
|
|
||
|
|
console.log(`${icon} ${check.name}: ${status}`);
|
||
|
|
|
||
|
|
if (result) {
|
||
|
|
passed++;
|
||
|
|
} else if (check.required) {
|
||
|
|
failed++;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log(`\n📊 测试结果: ${passed} 通过, ${failed} 失败\n`);
|
||
|
|
|
||
|
|
if (failed > 0) {
|
||
|
|
console.log('❌ 静态构建测试失败!');
|
||
|
|
console.log('💡 建议运行: npm run build:static\n');
|
||
|
|
return false;
|
||
|
|
} else {
|
||
|
|
console.log('✅ 静态构建测试通过!\n');
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function test404Content() {
|
||
|
|
console.log('🔍 测试404页面内容...\n');
|
||
|
|
|
||
|
|
const files = ['public/404.html', 'out/404.html'];
|
||
|
|
|
||
|
|
files.forEach(file => {
|
||
|
|
if (fs.existsSync(file)) {
|
||
|
|
const content = fs.readFileSync(file, 'utf8');
|
||
|
|
|
||
|
|
const checks = [
|
||
|
|
{ name: '包含DOCTYPE', test: content.includes('<!DOCTYPE html>') },
|
||
|
|
{ name: '包含404标题', test: content.includes('404') },
|
||
|
|
{ name: '包含语言检测', test: content.includes('detectLanguage') },
|
||
|
|
{ name: '包含样式', test: content.includes('<style>') },
|
||
|
|
{ name: '包含JavaScript', test: content.includes('<script>') },
|
||
|
|
{ name: '包含导航链接', test: content.includes('btn-primary') }
|
||
|
|
];
|
||
|
|
|
||
|
|
console.log(`📄 ${file}:`);
|
||
|
|
checks.forEach(check => {
|
||
|
|
const icon = check.test ? '✅' : '❌';
|
||
|
|
console.log(` ${icon} ${check.name}`);
|
||
|
|
});
|
||
|
|
console.log();
|
||
|
|
} else {
|
||
|
|
console.log(`❌ ${file} 不存在\n`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function checkHydrationIssues() {
|
||
|
|
console.log('🔍 检查潜在的hydration问题...\n');
|
||
|
|
|
||
|
|
const problematicPatterns = [
|
||
|
|
{
|
||
|
|
pattern: /new Date\(\)/g,
|
||
|
|
description: '使用 new Date() 可能导致服务器和客户端时间不匹配'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
pattern: /window\./g,
|
||
|
|
description: '直接使用 window 对象而没有检查 typeof window !== "undefined"'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
pattern: /document\./g,
|
||
|
|
description: '直接使用 document 对象而没有检查'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
pattern: /localStorage/g,
|
||
|
|
description: '使用 localStorage 可能导致hydration问题'
|
||
|
|
},
|
||
|
|
{
|
||
|
|
pattern: /sessionStorage/g,
|
||
|
|
description: '使用 sessionStorage 可能导致hydration问题'
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
const componentFiles = [
|
||
|
|
'app/components/Header.tsx',
|
||
|
|
'app/components/news/BackButton.tsx',
|
||
|
|
'app/components/news/ShareButton.tsx',
|
||
|
|
'app/components/news/NewsPageClient.tsx',
|
||
|
|
'app/components/news/NewsArticlePageClient.tsx'
|
||
|
|
];
|
||
|
|
|
||
|
|
let issuesFound = 0;
|
||
|
|
|
||
|
|
componentFiles.forEach(file => {
|
||
|
|
if (fs.existsSync(file)) {
|
||
|
|
const content = fs.readFileSync(file, 'utf8');
|
||
|
|
|
||
|
|
problematicPatterns.forEach(pattern => {
|
||
|
|
const matches = content.match(pattern.pattern);
|
||
|
|
if (matches) {
|
||
|
|
// 检查是否有适当的检查
|
||
|
|
const hasCheck = content.includes('typeof window !== "undefined"') ||
|
||
|
|
content.includes('typeof document !== "undefined"') ||
|
||
|
|
content.includes('useEffect');
|
||
|
|
|
||
|
|
if (!hasCheck) {
|
||
|
|
console.log(`⚠️ ${file}: ${pattern.description}`);
|
||
|
|
console.log(` 发现 ${matches.length} 个匹配项\n`);
|
||
|
|
issuesFound++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
if (issuesFound === 0) {
|
||
|
|
console.log('✅ 未发现明显的hydration问题\n');
|
||
|
|
} else {
|
||
|
|
console.log(`⚠️ 发现 ${issuesFound} 个潜在的hydration问题\n`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function main() {
|
||
|
|
console.log('🚀 静态构建测试工具\n');
|
||
|
|
console.log('=' .repeat(50));
|
||
|
|
|
||
|
|
const buildOk = testStaticBuild();
|
||
|
|
test404Content();
|
||
|
|
checkHydrationIssues();
|
||
|
|
|
||
|
|
console.log('=' .repeat(50));
|
||
|
|
|
||
|
|
if (buildOk) {
|
||
|
|
console.log('🎉 测试完成!可以运行 npm run preview 预览网站');
|
||
|
|
} else {
|
||
|
|
console.log('❌ 测试失败!请检查构建过程');
|
||
|
|
process.exit(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (require.main === module) {
|
||
|
|
main();
|
||
|
|
}
|