Skip to content

Commit

Permalink
feat(plugin-webpack): Allow each entrypoints to specify `nodeIntegrat…
Browse files Browse the repository at this point in the history
…ion` (#2867)
  • Loading branch information
chetbox authored Jul 26, 2022
1 parent 4f79132 commit 1f45e2c
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 75 deletions.
18 changes: 16 additions & 2 deletions packages/plugin/webpack/src/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ export interface WebpackPluginEntryPoint {
* 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:
*
* * When `nodeIntegration` is true, the `target` is `electron-renderer`.
* * When `nodeIntegration` is false, the `target` is `web`.
*
* Unfortunately, we cannot derive the value from the main process code as it can be a
* dynamically generated value at runtime, and Webpack processes at build-time.
*
* Defaults to `false` (as it is disabled by default in Electron \>= 5) or the value set
* for all entries.
*/
nodeIntegration?: boolean;
}

export interface WebpackPreloadEntryPoint {
Expand Down Expand Up @@ -66,8 +80,8 @@ export interface WebpackPluginRendererConfig {
*/
jsonStats?: boolean;
/**
* Adjusts the Webpack config for the renderer based on whether `nodeIntegration` for the
* `BrowserWindow` is enabled. Namely, for Webpack's `target` option:
* Adjusts the Webpack config for all renderer entries based on whether `nodeIntegration`
* for the `BrowserWindow` is enabled. Namely, for Webpack's `target` option:
*
* * When `nodeIntegration` is true, the `target` is `electron-renderer`.
* * When `nodeIntegration` is false, the `target` is `web`.
Expand Down
82 changes: 42 additions & 40 deletions packages/plugin/webpack/src/WebpackConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ export default class WebpackConfigGenerator {
return this.isProd ? 'source-map' : 'eval-source-map';
}

get rendererTarget(): string {
return this.pluginConfig.renderer.nodeIntegration ? 'electron-renderer' : 'web';
rendererTarget(entryPoint: WebpackPluginEntryPoint): string {
return entryPoint.nodeIntegration ?? this.pluginConfig.renderer.nodeIntegration ? 'electron-renderer' : 'web';
}

rendererEntryPoint(entryPoint: WebpackPluginEntryPoint, inRendererDir: boolean, basename: string): string {
Expand Down Expand Up @@ -181,46 +181,48 @@ export default class WebpackConfigGenerator {
);
}

async getRendererConfig(entryPoints: WebpackPluginEntryPoint[]): Promise<Configuration> {
async getRendererConfig(entryPoints: WebpackPluginEntryPoint[]): Promise<Configuration[]> {
const rendererConfig = await this.resolveConfig(this.pluginConfig.renderer.config);
const entry: webpack.Entry = {};
for (const entryPoint of entryPoints) {
const prefixedEntries = entryPoint.prefixedEntries || [];
entry[entryPoint.name] = prefixedEntries.concat([entryPoint.js]);
}

const defines = this.getDefines(false);
const plugins = entryPoints
.filter((entryPoint) => Boolean(entryPoint.html))
.map(
(entryPoint) =>
new HtmlWebpackPlugin({
title: entryPoint.name,
template: entryPoint.html,
filename: `${entryPoint.name}/index.html`,
chunks: [entryPoint.name].concat(entryPoint.additionalChunks || []),
}) as WebpackPluginInstance
)
.concat([new webpack.DefinePlugin(defines), new AssetRelocatorPatch(this.isProd, !!this.pluginConfig.renderer.nodeIntegration)]);
return webpackMerge(
{
entry,
devtool: this.rendererSourceMapOption,
target: this.rendererTarget,
mode: this.mode,
output: {
path: path.resolve(this.webpackDir, 'renderer'),
filename: '[name]/index.js',
globalObject: 'self',
...(this.isProd ? {} : { publicPath: '/' }),
},
node: {
__dirname: false,
__filename: false,

return entryPoints.map((entryPoint) => {
const config = webpackMerge(
{
entry: {
[entryPoint.name]: (entryPoint.prefixedEntries || []).concat([entryPoint.js]),
},
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: [
...(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),
],
},
plugins,
},
rendererConfig || {}
);
rendererConfig || {}
);

return config;
});
}
}
21 changes: 13 additions & 8 deletions packages/plugin/webpack/src/WebpackPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,23 @@ export default class WebpackPlugin extends PluginBase<WebpackPluginConfig> {
if (options.exit) process.exit();
};

async writeJSONStats(type: string, stats: webpack.Stats | undefined, statsOptions: WebpackToJsonOptions): Promise<void> {
async writeJSONStats(type: string, stats: webpack.Stats | undefined, statsOptions: WebpackToJsonOptions, suffix: string): Promise<void> {
if (!stats) return;
d(`Writing JSON stats for ${type} config`);
const jsonStats = stats.toJson(statsOptions);
const jsonStatsFilename = path.resolve(this.baseDir, type, 'stats.json');
const jsonStatsFilename = path.resolve(this.baseDir, type, `stats-${suffix}.json`);
await fs.writeJson(jsonStatsFilename, jsonStats, { spaces: 2 });
}

// eslint-disable-next-line max-len
private runWebpack = async (options: Configuration, isRenderer = false): Promise<webpack.Stats | undefined> =>
private runWebpack = async (options: Configuration[], isRenderer = false): Promise<webpack.MultiStats | undefined> =>
new Promise((resolve, reject) => {
webpack(options).run(async (err, stats) => {
if (isRenderer && this.config.renderer.jsonStats) {
await this.writeJSONStats('renderer', stats, options.stats as WebpackToJsonOptions);
for (const [index, entryStats] of (stats?.stats ?? []).entries()) {
const name = this.config.renderer.entryPoints[index].name;
await this.writeJSONStats('renderer', entryStats, options[index].stats as WebpackToJsonOptions, name);
}
}
if (err) {
return reject(err);
Expand Down Expand Up @@ -266,7 +269,7 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
);
}
if (this.config.jsonStats) {
await this.writeJSONStats('main', stats, mainConfig.stats as WebpackToJsonOptions);
await this.writeJSONStats('main', stats, mainConfig.stats as WebpackToJsonOptions, 'main');
}

if (err) return onceReject(err);
Expand Down Expand Up @@ -298,7 +301,7 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
await asyncOra(`Compiling Renderer Preload: ${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.getPreloadRendererConfig(entryPoint, entryPoint.preload!)]
);

if (stats?.hasErrors()) {
Expand All @@ -315,8 +318,10 @@ the generated files). Instead, it is ${JSON.stringify(pj.main)}`);
const pluginLogs = new ElectronForgeLoggingPlugin(tab);

const config = await this.configGenerator.getRendererConfig(this.config.renderer.entryPoints);
if (!config.plugins) config.plugins = [];
config.plugins.push(pluginLogs);
for (const entryConfig of config) {
if (!entryConfig.plugins) entryConfig.plugins = [];
entryConfig.plugins.push(pluginLogs);
}

const compiler = webpack(config);
const webpackDevServer = new WebpackDevServer(this.devServerOptions(), compiler);
Expand Down
12 changes: 9 additions & 3 deletions packages/plugin/webpack/test/AssetRelocatorPatch_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ describe('AssetRelocatorPatch', () => {

it('builds renderer', async () => {
const rendererConfig = await generator.getRendererConfig(config.renderer.entryPoints);
await asyncWebpack(rendererConfig);
for (const rendererEntryConfig of rendererConfig) {
await asyncWebpack(rendererEntryConfig);
}

await expectOutputFileToHaveTheCorrectNativeModulePath({
outDir: rendererOut,
Expand Down Expand Up @@ -216,7 +218,9 @@ describe('AssetRelocatorPatch', () => {

it('builds renderer', async () => {
const rendererConfig = await generator.getRendererConfig(config.renderer.entryPoints);
await asyncWebpack(rendererConfig);
for (const rendererEntryConfig of rendererConfig) {
await asyncWebpack(rendererEntryConfig);
}

await expectOutputFileToHaveTheCorrectNativeModulePath({
outDir: rendererOut,
Expand All @@ -239,7 +243,9 @@ describe('AssetRelocatorPatch', () => {
generator = new WebpackConfigGenerator(config, appPath, true, 3000);

const rendererConfig = await generator.getRendererConfig(config.renderer.entryPoints);
await asyncWebpack(rendererConfig);
for (const rendererEntryConfig of rendererConfig) {
await asyncWebpack(rendererEntryConfig);
}

await expectOutputFileToHaveTheCorrectNativeModulePath({
outDir: rendererOut,
Expand Down
57 changes: 36 additions & 21 deletions packages/plugin/webpack/test/WebpackConfig_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,30 +29,45 @@ describe('WebpackConfigGenerator', () => {
describe('rendererTarget', () => {
it('is web if undefined', () => {
const config = {
renderer: {},
renderer: {
entryPoints: [{ name: 'foo', js: 'foo/index.js' }],
},
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, '/', false, 3000);
expect(generator.rendererTarget).to.equal('web');
expect(generator.rendererTarget(config.renderer.entryPoints[0])).to.equal('web');
});

it('is web if false', () => {
const config = {
renderer: {
entryPoints: [{ name: 'foo', js: 'foo/index.js' }],
nodeIntegration: false,
},
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, '/', false, 3000);
expect(generator.rendererTarget).to.equal('web');
expect(generator.rendererTarget(config.renderer.entryPoints[0])).to.equal('web');
});

it('is electron-renderer if true', () => {
const config = {
renderer: {
entryPoints: [{ name: 'foo', js: 'foo/index.js' }],
nodeIntegration: true,
},
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, '/', false, 3000);
expect(generator.rendererTarget(config.renderer.entryPoints[0])).to.equal('electron-renderer');
});

it('is web if entry nodeIntegration is false', () => {
const config = {
renderer: {
entryPoints: [{ name: 'foo', js: 'foo/index.js', nodeIntegration: false }],
nodeIntegration: true,
},
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, '/', false, 3000);
expect(generator.rendererTarget).to.equal('electron-renderer');
expect(generator.rendererTarget(config.renderer.entryPoints[0])).to.equal('web');
});
});

Expand Down Expand Up @@ -411,7 +426,7 @@ describe('WebpackConfigGenerator', () => {
const rendererWebpackConfig = await generator.getRendererConfig(config.renderer.entryPoints);
// Our preload config plugins is an empty list while our renderer config plugins has a member
expect(preloadWebpackConfig.name).to.equal('preload');
expect(rendererWebpackConfig.name).to.equal('renderer');
expect(rendererWebpackConfig[0].name).to.equal('renderer');
});
});

Expand All @@ -430,20 +445,20 @@ describe('WebpackConfigGenerator', () => {
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000);
const webpackConfig = await generator.getRendererConfig(config.renderer.entryPoints);
expect(webpackConfig.target).to.deep.equal('electron-renderer');
expect(webpackConfig.mode).to.equal('development');
expect(webpackConfig.entry).to.deep.equal({
expect(webpackConfig[0].target).to.deep.equal('electron-renderer');
expect(webpackConfig[0].mode).to.equal('development');
expect(webpackConfig[0].entry).to.deep.equal({
main: ['rendererScript.js'],
});
expect(webpackConfig.output).to.deep.equal({
expect(webpackConfig[0].output).to.deep.equal({
path: path.join(mockProjectDir, '.webpack', 'renderer'),
filename: '[name]/index.js',
globalObject: 'self',
publicPath: '/',
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(webpackConfig.plugins!.length).to.equal(2);
expect(hasAssetRelocatorPatchPlugin(webpackConfig.plugins)).to.equal(true);
expect(webpackConfig[0].plugins!.length).to.equal(2);
expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).to.equal(true);
});

it('generates a development config with an HTML endpoint', async () => {
Expand All @@ -460,12 +475,12 @@ describe('WebpackConfigGenerator', () => {
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, mockProjectDir, false, 3000);
const webpackConfig = await generator.getRendererConfig(config.renderer.entryPoints);
expect(webpackConfig.entry).to.deep.equal({
expect(webpackConfig[0].entry).to.deep.equal({
main: ['rendererScript.js'],
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(webpackConfig.plugins!.length).to.equal(3);
expect(hasAssetRelocatorPatchPlugin(webpackConfig.plugins)).to.equal(true);
expect(webpackConfig[0].plugins!.length).to.equal(3);
expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).to.equal(true);
});

it('generates a production config', async () => {
Expand All @@ -481,19 +496,19 @@ describe('WebpackConfigGenerator', () => {
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000);
const webpackConfig = await generator.getRendererConfig(config.renderer.entryPoints);
expect(webpackConfig.target).to.deep.equal('web');
expect(webpackConfig.mode).to.equal('production');
expect(webpackConfig.entry).to.deep.equal({
expect(webpackConfig[0].target).to.deep.equal('web');
expect(webpackConfig[0].mode).to.equal('production');
expect(webpackConfig[0].entry).to.deep.equal({
main: ['rendererScript.js'],
});
expect(webpackConfig.output).to.deep.equal({
expect(webpackConfig[0].output).to.deep.equal({
path: path.join(mockProjectDir, '.webpack', 'renderer'),
filename: '[name]/index.js',
globalObject: 'self',
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(webpackConfig.plugins!.length).to.equal(2);
expect(hasAssetRelocatorPatchPlugin(webpackConfig.plugins)).to.equal(true);
expect(webpackConfig[0].plugins!.length).to.equal(2);
expect(hasAssetRelocatorPatchPlugin(webpackConfig[0].plugins)).to.equal(true);
});

it('can override the renderer target', async () => {
Expand All @@ -512,7 +527,7 @@ describe('WebpackConfigGenerator', () => {
} as WebpackPluginConfig;
const generator = new WebpackConfigGenerator(config, mockProjectDir, true, 3000);
const webpackConfig = await generator.getRendererConfig(config.renderer.entryPoints);
expect(webpackConfig.target).to.equal('web');
expect(webpackConfig[0].target).to.equal('web');
});

it('generates a config from function', async () => {
Expand Down
3 changes: 2 additions & 1 deletion typings/webpack-dev-server/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/55300#issuecomment-904636931
declare module 'webpack-dev-server' {
import { Server } from 'http';
import { Compiler } from 'webpack';
import { Compiler, MultiCompiler } from 'webpack';
class WebpackDevServer {
constructor(options: {}, compiler: Compiler);
constructor(options: {}, compiler: MultiCompiler);
server: Server;
start(): Promise<void>;
close(): void;
Expand Down

0 comments on commit 1f45e2c

Please sign in to comment.