dify
This commit is contained in:
38
dify/web/scripts/README.md
Normal file
38
dify/web/scripts/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Production Build Optimization Scripts
|
||||
|
||||
## optimize-standalone.js
|
||||
|
||||
This script removes unnecessary development dependencies from the Next.js standalone build output to reduce the production Docker image size.
|
||||
|
||||
### What it does
|
||||
|
||||
The script specifically targets and removes `jest-worker` packages that are bundled with Next.js but not needed in production. These packages are included because:
|
||||
|
||||
1. Next.js includes jest-worker in its compiled dependencies
|
||||
1. terser-webpack-plugin (used by Next.js for minification) depends on jest-worker
|
||||
1. pnpm's dependency resolution creates symlinks to jest-worker in various locations
|
||||
|
||||
### Usage
|
||||
|
||||
The script is automatically run during Docker builds via the `build:docker` npm script:
|
||||
|
||||
```bash
|
||||
# Docker build (removes jest-worker after build)
|
||||
pnpm build:docker
|
||||
```
|
||||
|
||||
To run the optimization manually:
|
||||
|
||||
```bash
|
||||
node scripts/optimize-standalone.js
|
||||
```
|
||||
|
||||
### What gets removed
|
||||
|
||||
- `node_modules/.pnpm/next@*/node_modules/next/dist/compiled/jest-worker`
|
||||
- `node_modules/.pnpm/terser-webpack-plugin@*/node_modules/jest-worker` (symlinks)
|
||||
- `node_modules/.pnpm/jest-worker@*` (actual packages)
|
||||
|
||||
### Impact
|
||||
|
||||
Removing jest-worker saves approximately 36KB per instance from the production image. While this may seem small, it helps ensure production images only contain necessary runtime dependencies.
|
||||
115
dify/web/scripts/copy-and-start.mjs
Normal file
115
dify/web/scripts/copy-and-start.mjs
Normal file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* This script copies static files to the target directory and starts the server.
|
||||
* It is intended to be used as a replacement for `next start`.
|
||||
*/
|
||||
|
||||
import { cp, mkdir, stat } from 'node:fs/promises'
|
||||
import { spawn } from 'node:child_process'
|
||||
import path from 'node:path'
|
||||
|
||||
// Configuration for directories to copy
|
||||
const DIRS_TO_COPY = [
|
||||
{
|
||||
src: path.join('.next', 'static'),
|
||||
dest: path.join('.next', 'standalone', '.next', 'static'),
|
||||
},
|
||||
{
|
||||
src: 'public',
|
||||
dest: path.join('.next', 'standalone', 'public'),
|
||||
},
|
||||
]
|
||||
|
||||
// Path to the server script
|
||||
const SERVER_SCRIPT_PATH = path.join('.next', 'standalone', 'server.js')
|
||||
|
||||
// Function to check if a path exists
|
||||
const pathExists = async (path) => {
|
||||
try {
|
||||
console.debug(`Checking if path exists: ${path}`)
|
||||
await stat(path)
|
||||
console.debug(`Path exists: ${path}`)
|
||||
return true
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.warn(`Path does not exist: ${path}`)
|
||||
return false
|
||||
}
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Function to recursively copy directories
|
||||
const copyDir = async (src, dest) => {
|
||||
console.debug(`Copying directory from ${src} to ${dest}`)
|
||||
await cp(src, dest, { recursive: true })
|
||||
console.info(`Successfully copied ${src} to ${dest}`)
|
||||
}
|
||||
|
||||
// Process each directory copy operation
|
||||
const copyAllDirs = async () => {
|
||||
console.debug('Starting directory copy operations')
|
||||
for (const { src, dest } of DIRS_TO_COPY) {
|
||||
try {
|
||||
// Instead of pre-creating destination directory, we ensure parent directory exists
|
||||
const destParent = path.dirname(dest)
|
||||
console.debug(`Ensuring destination parent directory exists: ${destParent}`)
|
||||
await mkdir(destParent, { recursive: true })
|
||||
if (await pathExists(src)) {
|
||||
await copyDir(src, dest)
|
||||
}
|
||||
else {
|
||||
console.error(`Error: ${src} directory does not exist. This is a required build artifact.`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Error processing ${src}:`, err.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
console.debug('Finished directory copy operations')
|
||||
}
|
||||
|
||||
// Run copy operations and start server
|
||||
const main = async () => {
|
||||
console.debug('Starting copy-and-start script')
|
||||
await copyAllDirs()
|
||||
|
||||
// Start server
|
||||
const port = process.env.npm_config_port || process.env.PORT || '3000'
|
||||
const host = process.env.npm_config_host || process.env.HOSTNAME || '0.0.0.0'
|
||||
|
||||
console.info(`Starting server on ${host}:${port}`)
|
||||
console.debug(`Server script path: ${SERVER_SCRIPT_PATH}`)
|
||||
console.debug(`Environment variables - PORT: ${port}, HOSTNAME: ${host}`)
|
||||
|
||||
const server = spawn(
|
||||
process.execPath,
|
||||
[SERVER_SCRIPT_PATH],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: port,
|
||||
HOSTNAME: host,
|
||||
},
|
||||
stdio: 'inherit',
|
||||
},
|
||||
)
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.error('Failed to start server:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
server.on('exit', (code) => {
|
||||
console.debug(`Server exited with code: ${code}`)
|
||||
process.exit(code || 0)
|
||||
})
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Unexpected error:', err)
|
||||
process.exit(1)
|
||||
})
|
||||
51
dify/web/scripts/generate-icons.js
Normal file
51
dify/web/scripts/generate-icons.js
Normal file
@@ -0,0 +1,51 @@
|
||||
const sharp = require('sharp');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const sizes = [
|
||||
{ size: 192, name: 'icon-192x192.png' },
|
||||
{ size: 256, name: 'icon-256x256.png' },
|
||||
{ size: 384, name: 'icon-384x384.png' },
|
||||
{ size: 512, name: 'icon-512x512.png' },
|
||||
{ size: 96, name: 'icon-96x96.png' },
|
||||
{ size: 72, name: 'icon-72x72.png' },
|
||||
{ size: 128, name: 'icon-128x128.png' },
|
||||
{ size: 144, name: 'icon-144x144.png' },
|
||||
{ size: 152, name: 'icon-152x152.png' },
|
||||
];
|
||||
|
||||
const inputPath = path.join(__dirname, '../public/icon.svg');
|
||||
const outputDir = path.join(__dirname, '../public');
|
||||
|
||||
// Generate icons
|
||||
async function generateIcons() {
|
||||
try {
|
||||
console.log('Generating PWA icons...');
|
||||
|
||||
for (const { size, name } of sizes) {
|
||||
const outputPath = path.join(outputDir, name);
|
||||
|
||||
await sharp(inputPath)
|
||||
.resize(size, size)
|
||||
.png()
|
||||
.toFile(outputPath);
|
||||
|
||||
console.log(`✓ Generated ${name} (${size}x${size})`);
|
||||
}
|
||||
|
||||
// Generate apple-touch-icon
|
||||
await sharp(inputPath)
|
||||
.resize(180, 180)
|
||||
.png()
|
||||
.toFile(path.join(outputDir, 'apple-touch-icon.png'));
|
||||
|
||||
console.log('✓ Generated apple-touch-icon.png (180x180)');
|
||||
|
||||
console.log('\n✅ All icons generated successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error generating icons:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
generateIcons();
|
||||
149
dify/web/scripts/optimize-standalone.js
Normal file
149
dify/web/scripts/optimize-standalone.js
Normal file
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Script to optimize Next.js standalone output for production
|
||||
* Removes unnecessary files like jest-worker that are bundled with Next.js
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
console.log('🔧 Optimizing standalone output...');
|
||||
|
||||
const standaloneDir = path.join(__dirname, '..', '.next', 'standalone');
|
||||
|
||||
// Check if standalone directory exists
|
||||
if (!fs.existsSync(standaloneDir)) {
|
||||
console.error('❌ Standalone directory not found. Please run "next build" first.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// List of paths to remove (relative to standalone directory)
|
||||
const pathsToRemove = [
|
||||
// Remove jest-worker from Next.js compiled dependencies
|
||||
'node_modules/.pnpm/next@*/node_modules/next/dist/compiled/jest-worker',
|
||||
// Remove jest-worker symlinks from terser-webpack-plugin
|
||||
'node_modules/.pnpm/terser-webpack-plugin@*/node_modules/jest-worker',
|
||||
// Remove actual jest-worker packages (directories only, not symlinks)
|
||||
'node_modules/.pnpm/jest-worker@*',
|
||||
];
|
||||
|
||||
// Function to safely remove a path
|
||||
function removePath(basePath, relativePath) {
|
||||
const fullPath = path.join(basePath, relativePath);
|
||||
|
||||
// Handle wildcard patterns
|
||||
if (relativePath.includes('*')) {
|
||||
const parts = relativePath.split('/');
|
||||
let currentPath = basePath;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
const part = parts[i];
|
||||
if (part.includes('*')) {
|
||||
// Find matching directories
|
||||
if (fs.existsSync(currentPath)) {
|
||||
const entries = fs.readdirSync(currentPath);
|
||||
|
||||
// replace '*' with '.*'
|
||||
const regexPattern = part.replace(/\*/g, '.*');
|
||||
|
||||
const regex = new RegExp(`^${regexPattern}$`);
|
||||
|
||||
for (const entry of entries) {
|
||||
if (regex.test(entry)) {
|
||||
const remainingPath = parts.slice(i + 1).join('/');
|
||||
const matchedPath = path.join(currentPath, entry, remainingPath);
|
||||
|
||||
try {
|
||||
// Use lstatSync to check if path exists (works for both files and symlinks)
|
||||
const stats = fs.lstatSync(matchedPath);
|
||||
|
||||
if (stats.isSymbolicLink()) {
|
||||
// Remove symlink
|
||||
fs.unlinkSync(matchedPath);
|
||||
console.log(`✅ Removed symlink: ${path.relative(basePath, matchedPath)}`);
|
||||
} else {
|
||||
// Remove directory/file
|
||||
fs.rmSync(matchedPath, { recursive: true, force: true });
|
||||
console.log(`✅ Removed: ${path.relative(basePath, matchedPath)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently ignore ENOENT (path not found) errors
|
||||
if (error.code !== 'ENOENT') {
|
||||
console.error(`❌ Failed to remove ${matchedPath}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
currentPath = path.join(currentPath, part);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Direct path removal
|
||||
if (fs.existsSync(fullPath)) {
|
||||
try {
|
||||
fs.rmSync(fullPath, { recursive: true, force: true });
|
||||
console.log(`✅ Removed: ${relativePath}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Failed to remove ${fullPath}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unnecessary paths
|
||||
console.log('🗑️ Removing unnecessary files...');
|
||||
for (const pathToRemove of pathsToRemove) {
|
||||
removePath(standaloneDir, pathToRemove);
|
||||
}
|
||||
|
||||
// Calculate size reduction
|
||||
console.log('\n📊 Optimization complete!');
|
||||
|
||||
// Optional: Display the size of remaining jest-related files (if any)
|
||||
const checkForJest = (dir) => {
|
||||
const jestFiles = [];
|
||||
|
||||
function walk(currentPath) {
|
||||
if (!fs.existsSync(currentPath)) return;
|
||||
|
||||
try {
|
||||
const entries = fs.readdirSync(currentPath);
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(currentPath, entry);
|
||||
|
||||
try {
|
||||
const stat = fs.lstatSync(fullPath); // Use lstatSync to handle symlinks
|
||||
|
||||
if (stat.isDirectory() && !stat.isSymbolicLink()) {
|
||||
// Skip node_modules subdirectories to avoid deep traversal
|
||||
if (entry === 'node_modules' && currentPath !== standaloneDir) {
|
||||
continue;
|
||||
}
|
||||
walk(fullPath);
|
||||
} else if (stat.isFile() && entry.includes('jest')) {
|
||||
jestFiles.push(path.relative(standaloneDir, fullPath));
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip files that can't be accessed
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Skip directories that can't be read
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
walk(dir);
|
||||
return jestFiles;
|
||||
};
|
||||
|
||||
const remainingJestFiles = checkForJest(standaloneDir);
|
||||
if (remainingJestFiles.length > 0) {
|
||||
console.log('\n⚠️ Warning: Some jest-related files still remain:');
|
||||
remainingJestFiles.forEach(file => console.log(` - ${file}`));
|
||||
} else {
|
||||
console.log('\n✨ No jest-related files found in standalone output!');
|
||||
}
|
||||
Reference in New Issue
Block a user