Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(plugin-vite): package volume size to large #3336

Merged
merged 19 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
erikian marked this conversation as resolved.
Show resolved Hide resolved
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