Skip to content

Commit

Permalink
fix(plugin-vite): package volume size to large (#3336)
Browse files Browse the repository at this point in the history
Co-authored-by: Black-Hole <github@bugs.cc>
  • Loading branch information
caoxiemeihao and BlackHole1 authored Jan 17, 2024
1 parent 46aed48 commit 2d244f0
Show file tree
Hide file tree
Showing 13 changed files with 542 additions and 167 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
"source-map-support": "^0.5.13",
"sudo-prompt": "^9.1.1",
"username": "^5.1.0",
"vite": "^4.1.1",
"vite": "^4.5.1",
"webpack": "^5.69.1",
"webpack-dev-server": "^4.0.0",
"webpack-merge": "^5.7.3",
Expand Down
7 changes: 4 additions & 3 deletions packages/plugin/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
"main": "dist/VitePlugin.js",
"typings": "dist/VitePlugin.d.ts",
"scripts": {
"test": "xvfb-maybe mocha --config ../../../.mocharc.js test/**/*_spec.ts"
"test": "xvfb-maybe mocha --config ../../../.mocharc.js test/**/*_spec.ts test/*_spec.ts"
},
"devDependencies": {
"@electron/packager": "^18.1.2",
"@malept/cross-spawn-promise": "^2.0.0",
"@types/node": "^18.0.3",
"chai": "^4.3.3",
"fs-extra": "^10.0.0",
"mocha": "^9.0.1",
"which": "^2.0.2",
"xvfb-maybe": "^0.2.1"
Expand All @@ -33,7 +33,8 @@
"@electron-forge/web-multi-logger": "7.2.0",
"chalk": "^4.0.0",
"debug": "^4.3.1",
"vite": "^4.1.1"
"fs-extra": "^10.0.0",
"vite": "^4.5.1"
},
"publishConfig": {
"access": "public"
Expand Down
1 change: 1 addition & 0 deletions packages/plugin/vite/src/ViteConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default class ViteConfigGenerator {
// 🚧 Multiple builds may conflict.
outDir: path.join(this.baseDir, 'build'),
watch: watch ? {} : undefined,
minify: this.isProd,
},
clearScreen: false,
define,
Expand Down
62 changes: 57 additions & 5 deletions packages/plugin/vite/src/VitePlugin.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import fs from 'node:fs/promises';
import { AddressInfo } from 'node:net';
import path from 'node:path';

import { namedHookWithTaskFn, PluginBase } from '@electron-forge/plugin-base';
import { ForgeMultiHookMap, StartResult } from '@electron-forge/shared-types';
import { ForgeMultiHookMap, ResolvedForgeConfig, StartResult } from '@electron-forge/shared-types';
import chalk from 'chalk';
import debug from 'debug';
import fs from 'fs-extra';
// eslint-disable-next-line node/no-extraneous-import
import { RollupWatcher } from 'rollup';
import { default as vite } from 'vite';

import { VitePluginConfig } from './Config';
import { getFlatDependencies } from './util/package';
import ViteConfigGenerator from './ViteConfig';

const d = debug('electron-forge:plugin:vite');
Expand Down Expand Up @@ -41,7 +43,7 @@ export default class VitePlugin extends PluginBase<VitePluginConfig> {
process.on('SIGINT' as NodeJS.Signals, (_signal) => this.exitHandler({ exit: true }));
};

private setDirectories(dir: string): void {
public setDirectories(dir: string): void {
this.projectDir = dir;
this.baseDir = path.join(dir, '.vite');
}
Expand All @@ -55,7 +57,7 @@ export default class VitePlugin extends PluginBase<VitePluginConfig> {
prePackage: [
namedHookWithTaskFn<'prePackage'>(async () => {
this.isProd = true;
await fs.rm(this.baseDir, { recursive: true, force: true });
await fs.remove(this.baseDir);

await Promise.all([this.build(), this.buildRenderer()]);
}, 'Building vite bundles'),
Expand All @@ -67,14 +69,64 @@ export default class VitePlugin extends PluginBase<VitePluginConfig> {
this.exitHandler({ cleanup: true, exit: true });
});
},
resolveForgeConfig: this.resolveForgeConfig,
packageAfterCopy: this.packageAfterCopy,
};
};

resolveForgeConfig = async (forgeConfig: ResolvedForgeConfig): Promise<ResolvedForgeConfig> => {
forgeConfig.packagerConfig ??= {};

if (forgeConfig.packagerConfig.ignore) {
if (typeof forgeConfig.packagerConfig.ignore !== 'function') {
console.error(
chalk.yellow(`You have set packagerConfig.ignore, the Electron Forge Vite plugin normally sets this automatically.
Your packaged app may be larger than expected if you dont ignore everything other than the '.vite' folder`)
);
}
return forgeConfig;
}

forgeConfig.packagerConfig.ignore = (file: string) => {
if (!file) return false;

// Always starts with `/`
// @see - https://github.com/electron/packager/blob/v18.1.3/src/copy-filter.ts#L89-L93
return !file.startsWith('/.vite');
};
return forgeConfig;
};

packageAfterCopy = async (_forgeConfig: ResolvedForgeConfig, buildPath: string): Promise<void> => {
const pj = await fs.readJson(path.resolve(this.projectDir, 'package.json'));
const flatDependencies = await getFlatDependencies(this.projectDir);

if (!pj.main?.includes('.vite/')) {
throw new Error(`Electron Forge is configured to use the Vite plugin. The plugin expects the
"main" entry point in "package.json" to be ".vite/*" (where the plugin outputs
the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
}

if (pj.config) {
delete pj.config.forge;
}

await fs.writeJson(path.resolve(buildPath, 'package.json'), pj, {
spaces: 2,
});

// Copy the dependencies in package.json
for (const dep of flatDependencies) {
await fs.copy(dep.src, path.resolve(buildPath, dep.dest));
}
};

startLogic = async (): Promise<StartResult> => {
if (VitePlugin.alreadyStarted) return false;
VitePlugin.alreadyStarted = true;

await fs.rm(this.baseDir, { recursive: true, force: true });
await fs.remove(this.baseDir);

return {
tasks: [
Expand Down
108 changes: 108 additions & 0 deletions packages/plugin/vite/src/util/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import path from 'node:path';

import fs from 'fs-extra';

export interface Dependency {
name: string;
path: SourceAndDestination;
dependencies: Dependency[];
}

export interface SourceAndDestination {
src: string;
dest: string;
}

function isRootPath(dir: string) {
// *unix or Windows root path
return dir === '/' || /^[a-zA-Z]:\\$/i.test(dir);
}

export async function isDirectory(p: string): Promise<boolean> {
try {
const stat = await fs.promises.stat(p);
return stat.isDirectory();
} catch {
return false;
}
}

export async function lookupNodeModulesPaths(root: string, paths: string[] = []): Promise<string[]> {
if (!root) return paths;
if (!path.isAbsolute(root)) return paths;

const p = path.join(root, 'node_modules');

if (await isDirectory(p)) {
paths = paths.concat(p);
}
root = path.join(root, '..');

return isRootPath(root) ? paths : await lookupNodeModulesPaths(root, paths);
}

export async function resolveDependencies(root: string) {
const rootDependencies = Object.keys((await fs.readJson(path.join(root, 'package.json'))).dependencies || {});
const resolve = async (prePath: string, dependencies: string[], collected: Map<string, Dependency> = new Map()) =>
await Promise.all(
dependencies.map(async (name) => {
let curPath = prePath,
depPath = null,
packageJson = null;
while (!packageJson && !isRootPath(curPath)) {
const allNodeModules = await lookupNodeModulesPaths(curPath);

for (const nodeModules of allNodeModules) {
depPath = path.join(nodeModules, name);
if (await fs.pathExists(depPath)) break;
}

if (depPath) {
try {
packageJson = await fs.readJson(path.join(depPath, 'package.json'));
} catch (err) {
// lookup node_modules
curPath = path.join(curPath, '..');
if (curPath.length < root.length) {
console.error(`not found 'node_modules' in root path: ${root}`);
throw err;
}
}
}
}

if (!depPath || !packageJson) {
throw new Error(`find dependencies error in: ${curPath}`);
}

const result: Dependency = {
name,
path: {
src: depPath,
dest: path.relative(root, depPath),
},
dependencies: [],
};
const shouldResolveDeps = !collected.has(depPath);
collected.set(depPath, result);
if (shouldResolveDeps) {
result.dependencies = await resolve(depPath, Object.keys(packageJson.dependencies || {}), collected);
}
return result;
})
);
return resolve(root, rootDependencies);
}

export async function getFlatDependencies(root = process.cwd()) {
const depsTree = await resolveDependencies(root);
const depsFlat = new Map<string, SourceAndDestination>();

const flatten = (dep: Dependency) => {
depsFlat.set(dep.path.src, dep.path); // dedup
dep.dependencies.forEach(flatten);
};
depsTree.forEach(flatten);

return [...depsFlat.values()];
}
1 change: 1 addition & 0 deletions packages/plugin/vite/test/ViteConfig_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('ViteConfigGenerator', () => {
emptyOutDir: false,
outDir: path.join('.vite', 'build'),
watch: undefined,
minify: true, // this.isProd === true
},
clearScreen: false,
define: {},
Expand Down
124 changes: 124 additions & 0 deletions packages/plugin/vite/test/VitePlugin_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import * as os from 'os';
import * as path from 'path';

import { ResolvedForgeConfig } from '@electron-forge/shared-types';
import { IgnoreFunction } from '@electron/packager';
import { expect } from 'chai';
import * as fs from 'fs-extra';

import { VitePluginConfig } from '../src/Config';
import { VitePlugin } from '../src/VitePlugin';

describe('VitePlugin', () => {
const baseConfig: VitePluginConfig = {
build: [],
renderer: [],
};

const viteTestDir = path.resolve(os.tmpdir(), 'electron-forge-plugin-vite-test');

describe('packageAfterCopy', () => {
const packageJSONPath = path.join(viteTestDir, 'package.json');
const packagedPath = path.join(viteTestDir, 'packaged');
const packagedPackageJSONPath = path.join(packagedPath, 'package.json');
let plugin: VitePlugin;

before(async () => {
await fs.ensureDir(packagedPath);
plugin = new VitePlugin(baseConfig);
plugin.setDirectories(viteTestDir);
});

it('should remove config.forge from package.json', async () => {
const packageJSON = { main: './.vite/build/main.js', config: { forge: 'config.js' } };
await fs.writeJson(packageJSONPath, packageJSON);
await plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath);
expect(await fs.pathExists(packagedPackageJSONPath)).to.equal(true);
expect((await fs.readJson(packagedPackageJSONPath)).config).to.not.have.property('forge');
});

it('should succeed if there is no config.forge', async () => {
const packageJSON = { main: '.vite/build/main.js' };
await fs.writeJson(packageJSONPath, packageJSON);
await plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath);
expect(await fs.pathExists(packagedPackageJSONPath)).to.equal(true);
expect(await fs.readJson(packagedPackageJSONPath)).to.not.have.property('config');
});

it('should fail if there is no main key in package.json', async () => {
const packageJSON = {};
await fs.writeJson(packageJSONPath, packageJSON);
await expect(plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath)).to.eventually.be.rejectedWith(/entry point/);
});

it('should fail if main in package.json does not starts with .vite/', async () => {
const packageJSON = { main: 'src/main.js' };
await fs.writeJson(packageJSONPath, packageJSON);
await expect(plugin.packageAfterCopy({} as ResolvedForgeConfig, packagedPath)).to.eventually.be.rejectedWith(/entry point/);
});

after(async () => {
await fs.remove(viteTestDir);
});
});

describe('resolveForgeConfig', () => {
let plugin: VitePlugin;

before(() => {
plugin = new VitePlugin(baseConfig);
});

it('sets packagerConfig and packagerConfig.ignore if it does not exist', async () => {
const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig);
expect(config.packagerConfig).to.not.equal(undefined);
expect(config.packagerConfig.ignore).to.be.a('function');
});

describe('packagerConfig.ignore', () => {
it('does not overwrite an existing ignore value', async () => {
const config = await plugin.resolveForgeConfig({
packagerConfig: {
ignore: /test/,
},
} as ResolvedForgeConfig);

expect(config.packagerConfig.ignore).to.deep.equal(/test/);
});

it('ignores everything but files in .vite', async () => {
const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig);
const ignore = config.packagerConfig.ignore as IgnoreFunction;

expect(ignore('')).to.equal(false);
expect(ignore('/abc')).to.equal(true);
expect(ignore('/.vite')).to.equal(false);
expect(ignore('/.vite/foo')).to.equal(false);
});

it('ignores source map files by default', async () => {
const viteConfig = { ...baseConfig };
plugin = new VitePlugin(viteConfig);
const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig);
const ignore = config.packagerConfig.ignore as IgnoreFunction;

expect(ignore(path.posix.join('/.vite', 'build', 'main.js'))).to.equal(false);
expect(ignore(path.posix.join('/.vite', 'build', 'main.js.map'))).to.equal(false);
expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js'))).to.equal(false);
expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js.map'))).to.equal(false);
});

it('includes source map files when specified by config', async () => {
const viteConfig = { ...baseConfig, packageSourceMaps: true };
plugin = new VitePlugin(viteConfig);
const config = await plugin.resolveForgeConfig({} as ResolvedForgeConfig);
const ignore = config.packagerConfig.ignore as IgnoreFunction;

expect(ignore(path.posix.join('/.vite', 'build', 'main.js'))).to.equal(false);
expect(ignore(path.posix.join('/.vite', 'build', 'main.js.map'))).to.equal(false);
expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js'))).to.equal(false);
expect(ignore(path.posix.join('/.vite', 'renderer', 'main_window', 'assets', 'index.js.map'))).to.equal(false);
});
});
});
});
4 changes: 4 additions & 0 deletions packages/plugin/vite/test/fixture/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# lockfile
package-lock.json
pnpm-lock.yaml
yarn.lock
Loading

0 comments on commit 2d244f0

Please sign in to comment.