Skip to content

Commit 4a3d0f7

Browse files
authored
Adding Electron Support (#433)
1 parent 65dd5e9 commit 4a3d0f7

9 files changed

+651
-109
lines changed

package-lock.json

Lines changed: 459 additions & 67 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
"tslint": "5.18.0"
106106
},
107107
"dependencies": {
108-
"@dojo/webpack-contrib": "~7.0.3",
108+
"@dojo/webpack-contrib": "8.0.0-alpha.1",
109109
"@typescript-eslint/eslint-plugin": "2.34.0",
110110
"@typescript-eslint/parser": "2.34.0",
111111
"caniuse-lite": "1.0.30000973",
@@ -119,6 +119,7 @@
119119
"css-loader": "1.0.1",
120120
"css-url-relative-plugin": "1.0.0",
121121
"cssnano": "4.1.7",
122+
"electron": "10.1.1",
122123
"eslint": "7.0.0",
123124
"eslint-loader": "4.0.2",
124125
"eslint-plugin-jsdoc": "25.4.2",

src/base.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ export class InsertScriptPlugin {
202202
export default function webpackConfigFactory(args: any): webpack.Configuration {
203203
tsnode.register({ transpileOnly: true });
204204
const isLegacy = args.legacy;
205+
const base = args.target === 'electron' ? './' : args.base || '/';
205206
const experimental = args.experimental || {};
206207
const isExperimentalSpeed = !!experimental.speed && args.mode === 'dev';
207208
const isTest = args.mode === 'unit' || args.mode === 'functional' || args.mode === 'test';
@@ -544,7 +545,7 @@ export default function webpackConfigFactory(args: any): webpack.Configuration {
544545
files: watchExtraFiles
545546
}),
546547
new ManifestPlugin(),
547-
new CssUrlRelativePlugin({ root: args.base || '/' })
548+
new CssUrlRelativePlugin({ root: base || '/' })
548549
]),
549550
module: {
550551
// `file` uses the pattern `loaderPath!filePath`, hence the regex test

src/dev.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function webpackConfig(args: any): webpack.Configuration {
2424
const experimental = args.experimental || {};
2525
const isExperimentalSpeed = !!experimental.speed;
2626
const singleBundle = args.singleBundle || isExperimentalSpeed;
27-
const base = args.base || '/';
27+
const base = args.target === 'electron' ? './' : args.base || '/';
2828

2929
const basePath = process.cwd();
3030
const config = baseConfigFactory(args);

src/dist.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ All rights reserved
3333

3434
function webpackConfig(args: any): webpack.Configuration {
3535
const basePath = process.cwd();
36-
const base = args.base || '/';
36+
const base = args.target === 'electron' ? './' : args.base || '/';
3737
const config = baseConfigFactory(args);
3838
const manifest: WebAppManifest = args.pwa && args.pwa.manifest;
3939
const { plugins, output } = config;

src/electron.config.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import ElectronPlugin from '@dojo/webpack-contrib/electron-plugin/ElectronPlugin';
2+
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
3+
4+
import * as webpack from 'webpack';
5+
import * as path from 'path';
6+
import * as fs from 'fs';
7+
8+
const basePath = process.cwd();
9+
const srcPath = path.join(basePath, 'src');
10+
const extensions = ['.ts', '.tsx', '.mjs', '.js'];
11+
const removeEmpty = (items: any[]) => items.filter((item) => item);
12+
const mainPath = path.join(srcPath, 'main.electron.ts');
13+
14+
function webpackConfig(args: any): webpack.Configuration {
15+
const experimental = args.experimental || {};
16+
const electron = args.electron || {};
17+
const isExperimentalSpeed = !!experimental.speed && args.mode === 'dev';
18+
const baseOutputPath = path.resolve('./output');
19+
const outputPath = path.join(baseOutputPath, args.mode);
20+
21+
return {
22+
name: 'electron',
23+
entry: {
24+
'main.electron': removeEmpty([
25+
'@dojo/webpack-contrib/electron-plugin/bootstrap',
26+
fs.existsSync(mainPath) ? mainPath : null
27+
])
28+
},
29+
resolveLoader: {
30+
modules: [path.resolve(__dirname, 'node_modules'), 'node_modules']
31+
},
32+
resolve: {
33+
modules: [basePath, path.join(basePath, 'node_modules')],
34+
extensions,
35+
plugins: [new TsconfigPathsPlugin({ configFile: path.join(basePath, 'tsconfig.json') })]
36+
},
37+
mode: args.mode === 'dev' ? 'development' : 'production',
38+
devtool: 'source-map',
39+
watchOptions: { ignored: /node_modules/ },
40+
target: 'electron-main',
41+
plugins: [
42+
new ElectronPlugin({
43+
electron: {
44+
browser: electron.browser || {},
45+
packaging: electron.packaging || {}
46+
},
47+
watch: !!args.watch,
48+
serve: !!args.serve,
49+
port: args.port,
50+
dist: args.mode !== 'dev'
51+
})
52+
],
53+
module: {
54+
rules: [
55+
{
56+
test: /\.(gif|png|jpe?g|svg|eot|ttf|woff|woff2|ico)$/i,
57+
loader: 'file-loader?hash=sha512&digest=hex&name=[name].[hash:base64:8].[ext]'
58+
},
59+
{
60+
test: /@dojo(\/|\\).*\.(js|mjs)$/,
61+
enforce: 'pre',
62+
loader: 'source-map-loader-cli',
63+
options: { includeModulePaths: true }
64+
},
65+
{
66+
include: srcPath,
67+
test: /\.ts(x)?$/,
68+
use: removeEmpty([
69+
{
70+
loader: 'ts-loader',
71+
options: {
72+
onlyCompileBundledFiles: true,
73+
instance: 'dojo',
74+
transpileOnly: isExperimentalSpeed,
75+
compilerOptions: {
76+
target: 'es2017',
77+
module: 'esnext',
78+
downlevelIteration: false
79+
}
80+
}
81+
}
82+
])
83+
}
84+
]
85+
},
86+
output: {
87+
chunkFilename: '[name].js',
88+
filename: '[name].js',
89+
path: outputPath
90+
}
91+
};
92+
}
93+
94+
export default webpackConfig;

src/main.ts

Lines changed: 49 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ import devConfigFactory from './dev.config';
1919
import unitConfigFactory from './unit.config';
2020
import functionalConfigFactory from './functional.config';
2121
import distConfigFactory from './dist.config';
22+
import electronConfigFactory from './electron.config';
2223
import logger from './logger';
2324
import { moveBuildOptions } from './util/eject';
2425
import { readFileSync } from 'fs';
2526

26-
export const mainEntry = 'main';
2727
const packageJsonPath = path.join(process.cwd(), 'package.json');
2828
const packageJson = fs.existsSync(packageJsonPath) ? require(packageJsonPath) : {};
2929
export const packageName = packageJson.name || '';
@@ -37,20 +37,19 @@ function getLibraryName(name: string) {
3737

3838
const libraryName = packageName ? getLibraryName(packageName) : 'main';
3939

40-
const fixMultipleWatchTrigger = require('webpack-mild-compile');
4140
const hotMiddleware = require('webpack-hot-middleware');
4241
const connectInject = require('connect-inject');
4342

4443
const testModes = ['test', 'unit', 'functional'];
4544

46-
function createCompiler(config: webpack.Configuration) {
47-
const compiler = webpack(config);
48-
fixMultipleWatchTrigger(compiler);
49-
return compiler;
45+
// for some reason the MultiCompiler type doesn't include hooks, even though they are clearly defined on the
46+
// object coming back.
47+
interface MultiCompilerWithHooks extends webpack.MultiCompiler {
48+
hooks: webpack.compilation.CompilerHooks;
5049
}
5150

52-
function createWatchCompiler(config: webpack.Configuration) {
53-
const compiler = createCompiler(config);
51+
function createWatchCompiler(configs: webpack.Configuration[]) {
52+
const compiler = webpack(configs) as MultiCompilerWithHooks;
5453
const spinner = ora('building').start();
5554
compiler.hooks.invalid.tap('@dojo/cli-build-app', () => {
5655
logUpdate('');
@@ -83,18 +82,18 @@ function serveStatic(
8382
}
8483
}
8584

86-
function build(config: webpack.Configuration, args: any) {
87-
const compiler = createCompiler(config);
85+
function build(configs: webpack.Configuration[], args: any) {
86+
const compiler = webpack(configs);
8887
const spinner = ora('building').start();
89-
return new Promise<webpack.Compiler>((resolve, reject) => {
88+
return new Promise<webpack.MultiCompiler>((resolve, reject) => {
9089
compiler.run((err, stats) => {
9190
spinner.stop();
9291
if (err) {
9392
reject(err);
9493
}
9594
if (stats) {
9695
const runningMessage = args.serve ? `Listening on port ${args.port}...` : '';
97-
const hasErrors = logger(stats.toJson({ warningsFilter }), config, runningMessage, args);
96+
const hasErrors = logger(stats.toJson({ warningsFilter }), configs, runningMessage, args);
9897
if (hasErrors) {
9998
reject({});
10099
return;
@@ -125,10 +124,15 @@ function buildNpmDependencies(): any {
125124
}
126125
}
127126

128-
async function fileWatch(config: webpack.Configuration, args: any, shouldResolve = false) {
129-
return new Promise<webpack.Compiler>((resolve, reject) => {
130-
const watchOptions = config.watchOptions as webpack.Compiler.WatchOptions;
131-
const compiler = createWatchCompiler(config);
127+
function fileWatch(configs: webpack.Configuration[], args: any, shouldResolve = false): Promise<webpack.MultiCompiler> {
128+
const [mainConfig] = configs;
129+
let compiler: webpack.MultiCompiler;
130+
131+
return new Promise<webpack.MultiCompiler>((resolve, reject) => {
132+
const watchOptions = mainConfig.watchOptions as webpack.Compiler.WatchOptions;
133+
134+
compiler = createWatchCompiler(configs);
135+
132136
compiler.watch(watchOptions, (err, stats) => {
133137
if (err) {
134138
reject(err);
@@ -139,7 +143,7 @@ async function fileWatch(config: webpack.Configuration, args: any, shouldResolve
139143
args.port
140144
}\nPlease note the serve option is not intended to be used to serve applications in production.`
141145
: 'watching...';
142-
logger(stats.toJson({ warningsFilter }), config, runningMessage, args);
146+
logger(stats.toJson({ warningsFilter }), configs, runningMessage, args);
143147
}
144148
if (shouldResolve) {
145149
resolve(compiler);
@@ -148,8 +152,9 @@ async function fileWatch(config: webpack.Configuration, args: any, shouldResolve
148152
});
149153
}
150154

151-
async function serve(config: webpack.Configuration, args: any) {
152-
const compiler = args.watch ? await fileWatch(config, args, true) : await build(config, args);
155+
async function serve(configs: webpack.Configuration[], args: any) {
156+
const [mainConfig] = configs;
157+
153158
let isHttps = false;
154159
const base = args.base || '/';
155160

@@ -162,19 +167,21 @@ async function serve(config: webpack.Configuration, args: any) {
162167
next();
163168
});
164169

165-
const outputDir = (config.output && config.output.path) || process.cwd();
170+
const compiler = args.watch ? await fileWatch(configs, args, true) : await build(configs, args);
171+
172+
const outputDir = (mainConfig.output && mainConfig.output.path) || process.cwd();
166173
let btrOptions = args['build-time-render'];
167174
if (btrOptions) {
168175
if (args.singleBundle || (args.experimental && !!args.experimental.speed)) {
169176
btrOptions = { ...btrOptions, sync: true };
170177
}
171-
const jsonpName = (config.output && config.output.jsonpFunction) || 'unknown';
178+
const jsonpName = (mainConfig.output && mainConfig.output.jsonpFunction) || 'unknown';
172179
const onDemandBtr = new OnDemandBtr({
173180
buildTimeRenderOptions: btrOptions,
174181
scope: libraryName,
175182
base,
176-
compiler,
177-
entries: config.entry ? Object.keys(config.entry) : [],
183+
compiler: compiler.compilers[0],
184+
entries: mainConfig.entry ? Object.keys(mainConfig.entry) : [],
178185
outputPath: outputDir,
179186
jsonpName
180187
});
@@ -295,6 +302,13 @@ const command: Command = {
295302
choices: ['dist', 'dev', 'test', 'unit', 'functional']
296303
});
297304

305+
options('target', {
306+
describe: 'the target',
307+
alias: 't',
308+
default: 'web',
309+
choices: ['web', 'electron']
310+
});
311+
298312
options('watch', {
299313
describe: 'watch for file changes',
300314
alias: 'w'
@@ -355,35 +369,39 @@ const command: Command = {
355369
},
356370
run(helper: Helper, args: any) {
357371
console.log = () => {};
358-
let config: webpack.Configuration;
372+
let configs: webpack.Configuration[] = [];
359373
args.experimental = args.experimental || {};
360374
args.base = url.resolve('/', args.base || '');
361375
if (!args.base.endsWith('/')) {
362376
args.base = `${args.base}/`;
363377
}
364378

365379
if (args.mode === 'dev') {
366-
config = devConfigFactory(args);
380+
configs.push(devConfigFactory(args));
367381
} else if (args.mode === 'unit' || args.mode === 'test') {
368-
config = unitConfigFactory(args);
382+
configs.push(unitConfigFactory(args));
369383
} else if (args.mode === 'functional') {
370-
config = functionalConfigFactory(args);
384+
configs.push(functionalConfigFactory(args));
371385
} else {
372-
config = distConfigFactory(args);
386+
configs.push(distConfigFactory(args));
387+
}
388+
389+
if (args.target === 'electron') {
390+
configs.push(electronConfigFactory(args));
373391
}
374392

375393
if (args.serve) {
376394
if (testModes.indexOf(args.mode) !== -1) {
377395
return Promise.reject(new Error(`Cannot use \`--serve\` with \`--mode=${args.mode}\``));
378396
}
379-
return serve(config, args);
397+
return serve(configs, args);
380398
}
381399

382400
if (args.watch) {
383-
return fileWatch(config, args);
401+
return fileWatch(configs, args);
384402
}
385403

386-
return build(config, args);
404+
return build(configs, args);
387405
},
388406
eject(helper: Helper): EjectOutput {
389407
return {

src/schema.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
"functional"
1010
]
1111
},
12+
"target": {
13+
"type": "string",
14+
"enum": [
15+
"web",
16+
"electron"
17+
]
18+
},
1219
"watch": {
1320
"type": "boolean"
1421
},
@@ -63,6 +70,13 @@
6370
}
6471
}
6572
},
73+
"electron": {
74+
"properties": {
75+
"browser": {
76+
"type": "object"
77+
}
78+
}
79+
},
6680
"build-time-render": {
6781
"additionalProperties": false,
6882
"properties": {
@@ -445,6 +459,12 @@
445459
"m": {
446460
"$ref": "#/definitions/mode"
447461
},
462+
"target": {
463+
"$ref": "#/definitions/target"
464+
},
465+
"t": {
466+
"$ref": "#/definitions/target"
467+
},
448468
"watch": {
449469
"$ref": "#/definitions/watch"
450470
},
@@ -522,6 +542,9 @@
522542
{ "$ref": "#/definitions/imageOptimizationOptions" },
523543
{ "$ref": "#/definitions/imageOptimizationFlag" }
524544
]
545+
},
546+
"electron": {
547+
"$ref": "#/definitions/electron"
525548
}
526549
}
527550
}

0 commit comments

Comments
 (0)