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

feat(plugin-webpack): support standalone preload entry points #2950

Merged
merged 11 commits into from
Oct 24, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
Binary file not shown.
59 changes: 44 additions & 15 deletions packages/plugin/webpack/src/Config.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import { Configuration as RawWebpackConfiguration } from 'webpack';
import { ConfigurationFactory as WebpackConfigurationFactory } from './WebpackConfig';

export interface WebpackPluginEntryPoint {
export interface WebpackPluginEntryPointBase {
/**
* Relative or absolute path to the HTML template file for this entry point
*
* If this is a window, you MUST provide this. Only leave it unset for things
* like WebWorker scripts.
*/
html?: string;
/**
* Relative or absolute path to the main JS file for this entry point
* Type of renderer process to initialize
* Defaults to 'local-window' if not specified
*/
js: string;
type?: 'local-window' | 'preload-only' | 'no-window';
/**
* Human friendly name of your entry point
*/
Expand All @@ -28,11 +22,6 @@ export interface WebpackPluginEntryPoint {
* set up some custom chunking. E.g. CommonChunksPlugin
*/
additionalChunks?: string[];
/**
* Information about the preload script for this entry point, if you don't use
* preload scripts you don't need to set this.
*/
preload?: WebpackPreloadEntryPoint;
/**
* Override the Webpack config for this renderer based on whether `nodeIntegration` for
* the `BrowserWindow` is enabled. Namely, for Webpack's `target` option:
Expand All @@ -49,6 +38,42 @@ export interface WebpackPluginEntryPoint {
nodeIntegration?: boolean;
}

export interface WebpackPluginEntryPointLocalWindow extends WebpackPluginEntryPointBase {
/**
* Relative or absolute path to the HTML template file for this entry point
*
* If this is a window, you MUST provide this. Only leave it unset for things
* like WebWorker scripts.
*/
html: string;
/**
* Relative or absolute path to the main JS file for this entry point
*/
js: string;
/**
* Information about the preload script for this entry point, if you don't use
* preload scripts you don't need to set this.
*/
preload?: WebpackPreloadEntryPoint;
}

export interface WebpackPluginEntryPointPreloadOnly extends WebpackPluginEntryPointBase {
/**
* Information about the preload script for this entry point, if you don't use
* preload scripts you don't need to set this.
*/
preload: WebpackPreloadEntryPoint;
}

export interface WebpackPluginEntryPointNoWindow extends WebpackPluginEntryPointBase {
/**
* Relative or absolute path to the main JS file for this entry point
*/
js: string;
}

export type WebpackPluginEntryPoint = WebpackPluginEntryPointLocalWindow | WebpackPluginEntryPointNoWindow | WebpackPluginEntryPointPreloadOnly;

export interface WebpackPreloadEntryPoint {
/**
* Relative or absolute path to the preload JS file
Expand All @@ -67,6 +92,10 @@ export interface WebpackPreloadEntryPoint {
config?: WebpackConfiguration | string;
}

export interface StandaloneWebpackPreloadEntryPoint extends WebpackPreloadEntryPoint {
name: string;
}

export interface WebpackPluginRendererConfig {
/**
* The webpack config for your renderer process
Expand Down
132 changes: 90 additions & 42 deletions packages/plugin/webpack/src/WebpackConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import HtmlWebpackPlugin from 'html-webpack-plugin';
import path from 'path';
import webpack, { Configuration, WebpackPluginInstance } from 'webpack';
import { merge as webpackMerge } from 'webpack-merge';
import { WebpackPluginConfig, WebpackPluginEntryPoint, WebpackPreloadEntryPoint } from './Config';
import { WebpackPluginConfig, WebpackPluginEntryPoint, WebpackPluginEntryPointLocalWindow, WebpackPluginEntryPointPreloadOnly } from './Config';
import AssetRelocatorPatch from './util/AssetRelocatorPatch';
import processConfig from './util/processConfig';
import { isLocalWindow, isNoWindow, isPreloadOnly } from './util/rendererTypeUtils';

type EntryType = string | string[] | Record<string, string | string[]>;
type WebpackMode = 'production' | 'development';
Expand Down Expand Up @@ -87,15 +88,16 @@ export default class WebpackConfigGenerator {
}

getPreloadDefine(entryPoint: WebpackPluginEntryPoint): string {
if (entryPoint.preload) {
if (!isNoWindow(entryPoint)) {
if (this.isProd) {
return `require('path').resolve(__dirname, '../renderer', '${entryPoint.name}', 'preload.js')`;
}
return `'${path.resolve(this.webpackDir, 'renderer', entryPoint.name, 'preload.js').replace(/\\/g, '\\\\')}'`;
} else {
// If this entry-point has no configured preload script just map this constant to `undefined`
// so that any code using it still works. This makes quick-start / docs simpler.
return 'undefined';
}
// If this entry-point has no configured preload script just map this constant to `undefined`
// so that any code using it still works. This makes quick-start / docs simpler.
return 'undefined';
}

getDefines(inRendererDir = true): Record<string, string> {
Expand All @@ -105,7 +107,7 @@ export default class WebpackConfigGenerator {
}
for (const entryPoint of this.pluginConfig.renderer.entryPoints) {
const entryKey = this.toEnvironmentVariable(entryPoint);
if (entryPoint.html) {
if (isLocalWindow(entryPoint)) {
defines[entryKey] = this.rendererEntryPoint(entryPoint, inRendererDir, 'index.html');
} else {
defines[entryKey] = this.rendererEntryPoint(entryPoint, inRendererDir, 'index.js');
Expand All @@ -116,6 +118,7 @@ export default class WebpackConfigGenerator {
defines[preloadDefineKey] = this.getPreloadDefine(entryPoint);
defines[`process.env.${preloadDefineKey}`] = defines[preloadDefineKey];
}

return defines;
}

Expand Down Expand Up @@ -158,17 +161,21 @@ export default class WebpackConfigGenerator {
);
}

async getPreloadRendererConfig(parentPoint: WebpackPluginEntryPoint, entryPoint: WebpackPreloadEntryPoint): Promise<Configuration> {
const rendererConfig = await this.resolveConfig(entryPoint.config || this.pluginConfig.renderer.config);
async getPreloadConfigForEntryPoint(entryPoint: WebpackPluginEntryPointLocalWindow | WebpackPluginEntryPointPreloadOnly): Promise<Configuration> {
if (!entryPoint.preload) {
return {};
}

const rendererConfig = await this.resolveConfig(entryPoint.preload.config || this.pluginConfig.renderer.config);
const prefixedEntries = entryPoint.prefixedEntries || [];

return webpackMerge(
{
devtool: this.rendererSourceMapOption,
mode: this.mode,
entry: prefixedEntries.concat([entryPoint.js]),
entry: prefixedEntries.concat([entryPoint.preload.js]),
output: {
path: path.resolve(this.webpackDir, 'renderer', parentPoint.name),
path: path.resolve(this.webpackDir, 'renderer', entryPoint.name),
filename: 'preload.js',
},
node: {
Expand All @@ -186,41 +193,82 @@ export default class WebpackConfigGenerator {
const defines = this.getDefines(false);

return entryPoints.map((entryPoint) => {
const config = webpackMerge(
{
entry: {
[entryPoint.name]: (entryPoint.prefixedEntries || []).concat([entryPoint.js]),
const baseConfig: webpack.Configuration = {
target: this.rendererTarget(entryPoint),
devtool: this.rendererSourceMapOption,
mode: this.mode,
output: {
path: path.resolve(this.webpackDir, 'renderer'),
filename: '[name]/index.js',
globalObject: 'self',
...(this.isProd ? {} : { publicPath: '/' }),
},
node: {
__dirname: false,
__filename: false,
},
plugins: [new webpack.DefinePlugin(defines), new AssetRelocatorPatch(this.isProd, !!this.pluginConfig.renderer.nodeIntegration)],
};

if (isLocalWindow(entryPoint)) {
return webpackMerge(
baseConfig,
{
entry: {
[entryPoint.name]: (entryPoint.prefixedEntries || []).concat([entryPoint.js]),
},
output: {
path: path.resolve(this.webpackDir, 'renderer'),
filename: '[name]/index.js',
globalObject: 'self',
...(this.isProd ? {} : { publicPath: '/' }),
},
plugins: [
new HtmlWebpackPlugin({
title: entryPoint.name,
template: entryPoint.html,
filename: `${entryPoint.name}/index.html`,
chunks: [entryPoint.name].concat(entryPoint.additionalChunks || []),
}) as WebpackPluginInstance,
],
},
target: this.rendererTarget(entryPoint),
devtool: this.rendererSourceMapOption,
mode: this.mode,
output: {
path: path.resolve(this.webpackDir, 'renderer'),
filename: '[name]/index.js',
globalObject: 'self',
...(this.isProd ? {} : { publicPath: '/' }),
rendererConfig || {}
);
} else if (isNoWindow(entryPoint)) {
return webpackMerge(
baseConfig,
{
entry: {
[entryPoint.name]: (entryPoint.prefixedEntries || []).concat([entryPoint.js]),
},
output: {
path: path.resolve(this.webpackDir, 'renderer'),
filename: '[name]/index.js',
globalObject: 'self',
...(this.isProd ? {} : { publicPath: '/' }),
},
},
node: {
__dirname: false,
__filename: false,
rendererConfig || {}
);
} else if (isPreloadOnly(entryPoint)) {
return webpackMerge(
baseConfig,
{
target: 'electron-preload',
entry: {
[entryPoint.name]: (entryPoint.prefixedEntries || []).concat([entryPoint.preload.js]),
},
output: {
path: path.resolve(this.webpackDir, 'renderer'),
filename: 'preload.js',
globalObject: 'self',
...(this.isProd ? {} : { publicPath: '/' }),
},
},
plugins: [
...(entryPoint.html
? [
new HtmlWebpackPlugin({
title: entryPoint.name,
template: entryPoint.html,
filename: `${entryPoint.name}/index.html`,
chunks: [entryPoint.name].concat(entryPoint.additionalChunks || []),
}) as WebpackPluginInstance,
]
: []),
new webpack.DefinePlugin(defines),
new AssetRelocatorPatch(this.isProd, !!this.pluginConfig.renderer.nodeIntegration),
],
},
rendererConfig || {}
);
rendererConfig || {}
);
}
const config = webpackMerge(baseConfig, rendererConfig || {});

return config;
});
Expand Down
26 changes: 14 additions & 12 deletions packages/plugin/webpack/src/WebpackPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { WebpackPluginConfig } from './Config';
import ElectronForgeLoggingPlugin from './util/ElectronForgeLogging';
import once from './util/once';
import WebpackConfigGenerator from './WebpackConfig';
import { isLocalWindow, isPreloadOnly } from './util/rendererTypeUtils';

const d = debug('electron-forge:plugin:webpack');
const DEFAULT_PORT = 3000;
Expand Down Expand Up @@ -297,11 +298,11 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
});

for (const entryPoint of this.config.renderer.entryPoints) {
if (entryPoint.preload) {
await asyncOra(`Compiling Renderer Preload: ${entryPoint.name}`, async () => {
if ((isLocalWindow(entryPoint) && !!entryPoint.preload) || isPreloadOnly(entryPoint)) {
await asyncOra(`Compiling Renderer Preload: ${chalk.cyan(entryPoint.name)}`, async () => {
const stats = await this.runWebpack(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
[await this.configGenerator.getPreloadRendererConfig(entryPoint, entryPoint.preload!)]
[await this.configGenerator.getPreloadConfigForEntryPoint(entryPoint)]
);

if (stats?.hasErrors()) {
Expand All @@ -312,12 +313,17 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
}
};

launchDevServers = async (logger: Logger): Promise<void> => {
await asyncOra('Launch Dev Servers', async () => {
launchRendererDevServers = async (logger: Logger): Promise<void> => {
await asyncOra('Launching Dev Servers for Renderer Process Code', async () => {
const tab = logger.createTab('Renderers');
const pluginLogs = new ElectronForgeLoggingPlugin(tab);

const config = await this.configGenerator.getRendererConfig(this.config.renderer.entryPoints);

if (config.length === 0) {
return;
}

for (const entryConfig of config) {
if (!entryConfig.plugins) entryConfig.plugins = [];
entryConfig.plugins.push(pluginLogs);
Expand All @@ -331,12 +337,8 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);

await asyncOra('Compiling Preload Scripts', async () => {
for (const entryPoint of this.config.renderer.entryPoints) {
if (entryPoint.preload) {
const config = await this.configGenerator.getPreloadRendererConfig(
entryPoint,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
entryPoint.preload!
);
if ((isLocalWindow(entryPoint) && !!entryPoint.preload) || isPreloadOnly(entryPoint)) {
const config = await this.configGenerator.getPreloadConfigForEntryPoint(entryPoint);
await new Promise((resolve, reject) => {
const tab = logger.createTab(`${entryPoint.name} - Preload`);
const [onceResolve, onceReject] = once(resolve, reject);
Expand Down Expand Up @@ -395,7 +397,7 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
const logger = new Logger(this.loggerPort);
this.loggers.push(logger);
await this.compileMain(true, logger);
await this.launchDevServers(logger);
await this.launchRendererDevServers(logger);
await logger.start();
return false;
}
Expand Down
17 changes: 17 additions & 0 deletions packages/plugin/webpack/src/util/rendererTypeUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { WebpackPluginEntryPoint, WebpackPluginEntryPointLocalWindow, WebpackPluginEntryPointNoWindow, WebpackPluginEntryPointPreloadOnly } from '../Config';

/**
* Reusable type predicate functions to narrow down the type of the WebpackPluginEntryPoint
*/

export const isLocalWindow = (entry: WebpackPluginEntryPoint): entry is WebpackPluginEntryPointLocalWindow => {
return entry.type === 'local-window' || (entry.type === undefined && !!(entry as any).html);
};

export const isPreloadOnly = (entry: WebpackPluginEntryPoint): entry is WebpackPluginEntryPointPreloadOnly => {
return entry.type === 'preload-only' || (entry.type === undefined && !(entry as any).html && !(entry as any).js && !(entry as any).preload);
};

export const isNoWindow = (entry: WebpackPluginEntryPoint): entry is WebpackPluginEntryPointNoWindow => {
return entry.type === 'no-window' || (entry.type === undefined && !(entry as any).html && !!(entry as any).js);
};
Loading