mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-26 13:19:34 +07:00
🐛 fix(desktop): prevent duplicate IPC handler registration from dynamic imports (#11827)
* 🐛 fix(desktop): prevent duplicate IPC handler registration from dynamic imports Fix an issue where dynamic imports in file-loaders package would cause the debug package to be bundled into index.js, leading to side-effect pollution and duplicate electron-log IPC handler registration. - Add manualChunks config to isolate debug package into separate chunk - Add @napi-rs/canvas to native modules for proper externalization * ✨ feat(desktop): enhance afterPack hook and add native module copying
This commit is contained in:
@@ -4,7 +4,11 @@ import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { getAsarUnpackPatterns, getFilesPatterns } from './native-deps.config.mjs';
|
||||
import {
|
||||
copyNativeModules,
|
||||
getAsarUnpackPatterns,
|
||||
getFilesPatterns,
|
||||
} from './native-deps.config.mjs';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
@@ -86,30 +90,46 @@ const getIconFileName = () => {
|
||||
*/
|
||||
const config = {
|
||||
/**
|
||||
* AfterPack hook to copy pre-generated Liquid Glass Assets.car for macOS 26+
|
||||
* AfterPack hook for post-processing:
|
||||
* 1. Copy native modules to asar.unpacked (resolving pnpm symlinks)
|
||||
* 2. Copy Liquid Glass Assets.car for macOS 26+
|
||||
* 3. Remove unused Electron Framework localizations
|
||||
*
|
||||
* @see https://github.com/electron-userland/electron-builder/issues/9254
|
||||
* @see https://github.com/MultiboxLabs/flow-browser/pull/159
|
||||
* @see https://github.com/electron/packager/pull/1806
|
||||
*/
|
||||
afterPack: async (context) => {
|
||||
// Only process macOS builds
|
||||
if (!['darwin', 'mas'].includes(context.electronPlatformName)) {
|
||||
const isMac = ['darwin', 'mas'].includes(context.electronPlatformName);
|
||||
|
||||
// Determine resources path based on platform
|
||||
let resourcesPath;
|
||||
if (isMac) {
|
||||
resourcesPath = path.join(
|
||||
context.appOutDir,
|
||||
`${context.packager.appInfo.productFilename}.app`,
|
||||
'Contents',
|
||||
'Resources',
|
||||
);
|
||||
} else {
|
||||
// Windows and Linux: resources is directly in appOutDir
|
||||
resourcesPath = path.join(context.appOutDir, 'resources');
|
||||
}
|
||||
|
||||
// Copy native modules to asar.unpacked, resolving pnpm symlinks
|
||||
const unpackedNodeModules = path.join(resourcesPath, 'app.asar.unpacked', 'node_modules');
|
||||
await copyNativeModules(unpackedNodeModules);
|
||||
|
||||
// macOS-specific post-processing
|
||||
if (!isMac) {
|
||||
return;
|
||||
}
|
||||
|
||||
const iconFileName = getIconFileName();
|
||||
const assetsCarSource = path.join(__dirname, 'build', `${iconFileName}.Assets.car`);
|
||||
const resourcesPath = path.join(
|
||||
context.appOutDir,
|
||||
`${context.packager.appInfo.productFilename}.app`,
|
||||
'Contents',
|
||||
'Resources',
|
||||
);
|
||||
const assetsCarDest = path.join(resourcesPath, 'Assets.car');
|
||||
|
||||
// Remove unused Electron Framework localizations to reduce app size
|
||||
// Equivalent to:
|
||||
// ../../Frameworks/Electron Framework.framework/Versions/A/Resources/*.lproj
|
||||
const frameworkResourcePath = path.join(
|
||||
context.appOutDir,
|
||||
`${context.packager.appInfo.productFilename}.app`,
|
||||
@@ -155,7 +175,7 @@ const config = {
|
||||
appImage: {
|
||||
artifactName: '${productName}-${version}.${ext}',
|
||||
},
|
||||
asar: true,
|
||||
|
||||
// Native modules must be unpacked from asar to work correctly
|
||||
asarUnpack: getAsarUnpackPatterns(),
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
// Native modules must be externalized to work correctly
|
||||
external: getExternalDependencies(),
|
||||
output: {
|
||||
// Prevent debug package from being bundled into index.js to avoid side-effect pollution
|
||||
manualChunks(id) {
|
||||
if (id.includes('node_modules/debug')) {
|
||||
return 'vendor-debug';
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
sourcemap: isDev ? 'inline' : false,
|
||||
},
|
||||
@@ -25,7 +33,6 @@ export default defineConfig({
|
||||
'process.env.UPDATE_CHANNEL': JSON.stringify(process.env.UPDATE_CHANNEL),
|
||||
'process.env.UPDATE_SERVER_URL': JSON.stringify(process.env.UPDATE_SERVER_URL),
|
||||
},
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src/main'),
|
||||
|
||||
@@ -24,6 +24,7 @@ function getTargetPlatform() {
|
||||
return process.env.npm_config_platform || os.platform();
|
||||
}
|
||||
const isDarwin = getTargetPlatform() === 'darwin';
|
||||
|
||||
/**
|
||||
* List of native modules that need special handling
|
||||
* Only add the top-level native modules here - dependencies are resolved automatically
|
||||
@@ -33,8 +34,8 @@ const isDarwin = getTargetPlatform() === 'darwin';
|
||||
export const nativeModules = [
|
||||
// macOS-only native modules
|
||||
...(isDarwin ? ['node-mac-permissions'] : []),
|
||||
'@napi-rs/canvas',
|
||||
// Add more native modules here as needed
|
||||
// e.g., 'better-sqlite3', 'sharp', etc.
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -53,22 +54,32 @@ function resolveDependencies(
|
||||
return visited;
|
||||
}
|
||||
|
||||
// Always add the module name first (important for workspace dependencies
|
||||
// that may not be in local node_modules but are declared in nativeModules)
|
||||
visited.add(moduleName);
|
||||
|
||||
const packageJsonPath = path.join(nodeModulesPath, moduleName, 'package.json');
|
||||
|
||||
// Check if module exists
|
||||
// If module doesn't exist locally, still keep it in visited but skip dependency resolution
|
||||
if (!fs.existsSync(packageJsonPath)) {
|
||||
return visited;
|
||||
}
|
||||
|
||||
visited.add(moduleName);
|
||||
|
||||
try {
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
const dependencies = packageJson.dependencies || {};
|
||||
const optionalDependencies = packageJson.optionalDependencies || {};
|
||||
|
||||
// Resolve regular dependencies
|
||||
for (const dep of Object.keys(dependencies)) {
|
||||
resolveDependencies(dep, visited, nodeModulesPath);
|
||||
}
|
||||
|
||||
// Also resolve optional dependencies (important for native modules like @napi-rs/canvas
|
||||
// which have platform-specific binaries in optional deps)
|
||||
for (const dep of Object.keys(optionalDependencies)) {
|
||||
resolveDependencies(dep, visited, nodeModulesPath);
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors reading package.json
|
||||
}
|
||||
@@ -116,3 +127,79 @@ export function getAsarUnpackPatterns() {
|
||||
export function getExternalDependencies() {
|
||||
return getAllDependencies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy native modules to destination, resolving symlinks
|
||||
* This is used in afterPack hook to handle pnpm symlinks correctly
|
||||
* @param {string} destNodeModules - Destination node_modules path
|
||||
*/
|
||||
export async function copyNativeModules(destNodeModules) {
|
||||
const fsPromises = await import('node:fs/promises');
|
||||
const deps = getAllDependencies();
|
||||
const sourceNodeModules = path.join(__dirname, 'node_modules');
|
||||
|
||||
console.log(`📦 Copying ${deps.length} native modules to unpacked directory...`);
|
||||
|
||||
for (const dep of deps) {
|
||||
const sourcePath = path.join(sourceNodeModules, dep);
|
||||
const destPath = path.join(destNodeModules, dep);
|
||||
|
||||
try {
|
||||
// Check if source exists (might be a symlink)
|
||||
const stat = await fsPromises.lstat(sourcePath);
|
||||
|
||||
if (stat.isSymbolicLink()) {
|
||||
// Resolve the symlink to get the real path
|
||||
const realPath = await fsPromises.realpath(sourcePath);
|
||||
console.log(` 📎 ${dep} (symlink -> ${path.relative(sourceNodeModules, realPath)})`);
|
||||
|
||||
// Create destination directory
|
||||
await fsPromises.mkdir(path.dirname(destPath), { recursive: true });
|
||||
|
||||
// Copy the actual directory content (not the symlink)
|
||||
await copyDir(realPath, destPath);
|
||||
} else if (stat.isDirectory()) {
|
||||
console.log(` 📁 ${dep}`);
|
||||
await fsPromises.mkdir(path.dirname(destPath), { recursive: true });
|
||||
await copyDir(sourcePath, destPath);
|
||||
}
|
||||
} catch (err) {
|
||||
// Module might not exist (optional dependency for different platform)
|
||||
console.log(` ⏭️ ${dep} (skipped: ${err.code || err.message})`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Native modules copied successfully`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively copy a directory
|
||||
* @param {string} src - Source directory
|
||||
* @param {string} dest - Destination directory
|
||||
*/
|
||||
async function copyDir(src, dest) {
|
||||
const fsPromises = await import('node:fs/promises');
|
||||
|
||||
await fsPromises.mkdir(dest, { recursive: true });
|
||||
const entries = await fsPromises.readdir(src, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await copyDir(srcPath, destPath);
|
||||
} else if (entry.isSymbolicLink()) {
|
||||
// For symlinks within the module, resolve and copy the actual file
|
||||
const realPath = await fsPromises.realpath(srcPath);
|
||||
const realStat = await fsPromises.stat(realPath);
|
||||
if (realStat.isDirectory()) {
|
||||
await copyDir(realPath, destPath);
|
||||
} else {
|
||||
await fsPromises.copyFile(realPath, destPath);
|
||||
}
|
||||
} else {
|
||||
await fsPromises.copyFile(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
"pdfjs-dist": "5.4.530",
|
||||
"word-extractor": "^1.0.4",
|
||||
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
|
||||
"yauzl": "^3.2.0"
|
||||
"yauzl": "^3.2.0",
|
||||
"@napi-rs/canvas": "^0.1.70"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/canvas": "^0.1.70",
|
||||
"@types/concat-stream": "^2.0.3",
|
||||
"@types/yauzl": "^2.10.3",
|
||||
"typescript": "^5.9.3"
|
||||
@@ -44,4 +44,4 @@
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user