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

Adding Electron Support #433

Merged
merged 2 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
526 changes: 459 additions & 67 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"tslint": "5.18.0"
},
"dependencies": {
"@dojo/webpack-contrib": "~7.0.3",
"@dojo/webpack-contrib": "8.0.0-alpha.1",
"@typescript-eslint/eslint-plugin": "2.34.0",
"@typescript-eslint/parser": "2.34.0",
"caniuse-lite": "1.0.30000973",
Expand All @@ -119,6 +119,7 @@
"css-loader": "1.0.1",
"css-url-relative-plugin": "1.0.0",
"cssnano": "4.1.7",
"electron": "10.1.1",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding electron as a dep here so you dont need to install it separately on every app. In the future, we'll look into how to conditionally install deps like puppetteer and electron.

"eslint": "7.0.0",
"eslint-loader": "4.0.2",
"eslint-plugin-jsdoc": "25.4.2",
Expand Down
3 changes: 2 additions & 1 deletion src/base.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export class InsertScriptPlugin {
export default function webpackConfigFactory(args: any): webpack.Configuration {
tsnode.register({ transpileOnly: true });
const isLegacy = args.legacy;
const base = args.target === 'electron' ? './' : args.base || '/';
const experimental = args.experimental || {};
const isExperimentalSpeed = !!experimental.speed && args.mode === 'dev';
const isTest = args.mode === 'unit' || args.mode === 'functional' || args.mode === 'test';
Expand Down Expand Up @@ -544,7 +545,7 @@ export default function webpackConfigFactory(args: any): webpack.Configuration {
files: watchExtraFiles
}),
new ManifestPlugin(),
new CssUrlRelativePlugin({ root: args.base || '/' })
new CssUrlRelativePlugin({ root: base || '/' })
]),
module: {
// `file` uses the pattern `loaderPath!filePath`, hence the regex test
Expand Down
2 changes: 1 addition & 1 deletion src/dev.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function webpackConfig(args: any): webpack.Configuration {
const experimental = args.experimental || {};
const isExperimentalSpeed = !!experimental.speed;
const singleBundle = args.singleBundle || isExperimentalSpeed;
const base = args.base || '/';
const base = args.target === 'electron' ? './' : args.base || '/';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Electron target does not support a base URL


const basePath = process.cwd();
const config = baseConfigFactory(args);
Expand Down
2 changes: 1 addition & 1 deletion src/dist.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ All rights reserved

function webpackConfig(args: any): webpack.Configuration {
const basePath = process.cwd();
const base = args.base || '/';
const base = args.target === 'electron' ? './' : args.base || '/';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Electron target does not support a base URL

const config = baseConfigFactory(args);
const manifest: WebAppManifest = args.pwa && args.pwa.manifest;
const { plugins, output } = config;
Expand Down
94 changes: 94 additions & 0 deletions src/electron.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import ElectronPlugin from '@dojo/webpack-contrib/electron-plugin/ElectronPlugin';
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

import * as webpack from 'webpack';
import * as path from 'path';
import * as fs from 'fs';

const basePath = process.cwd();
const srcPath = path.join(basePath, 'src');
const extensions = ['.ts', '.tsx', '.mjs', '.js'];
const removeEmpty = (items: any[]) => items.filter((item) => item);
const mainPath = path.join(srcPath, 'main.electron.ts');

function webpackConfig(args: any): webpack.Configuration {
const experimental = args.experimental || {};
const electron = args.electron || {};
const isExperimentalSpeed = !!experimental.speed && args.mode === 'dev';
const baseOutputPath = path.resolve('./output');
const outputPath = path.join(baseOutputPath, args.mode);

return {
name: 'electron',
entry: {
'main.electron': removeEmpty([
'@dojo/webpack-contrib/electron-plugin/bootstrap',
fs.existsSync(mainPath) ? mainPath : null
])
},
resolveLoader: {
modules: [path.resolve(__dirname, 'node_modules'), 'node_modules']
},
resolve: {
modules: [basePath, path.join(basePath, 'node_modules')],
extensions,
plugins: [new TsconfigPathsPlugin({ configFile: path.join(basePath, 'tsconfig.json') })]
},
mode: args.mode === 'dev' ? 'development' : 'production',
devtool: 'source-map',
watchOptions: { ignored: /node_modules/ },
target: 'electron-main',
plugins: [
new ElectronPlugin({
electron: {
browser: electron.browser || {},
packaging: electron.packaging || {}
},
watch: !!args.watch,
serve: !!args.serve,
port: args.port,
dist: args.mode !== 'dev'
})
],
module: {
rules: [
{
test: /\.(gif|png|jpe?g|svg|eot|ttf|woff|woff2|ico)$/i,
loader: 'file-loader?hash=sha512&digest=hex&name=[name].[hash:base64:8].[ext]'
},
{
test: /@dojo(\/|\\).*\.(js|mjs)$/,
enforce: 'pre',
loader: 'source-map-loader-cli',
options: { includeModulePaths: true }
},
{
include: srcPath,
test: /\.ts(x)?$/,
use: removeEmpty([
{
loader: 'ts-loader',
options: {
onlyCompileBundledFiles: true,
instance: 'dojo',
transpileOnly: isExperimentalSpeed,
compilerOptions: {
target: 'es2017',
module: 'esnext',
downlevelIteration: false
}
}
}
])
}
]
},
output: {
chunkFilename: '[name].js',
filename: '[name].js',
path: outputPath
}
};
}

export default webpackConfig;
80 changes: 49 additions & 31 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import devConfigFactory from './dev.config';
import unitConfigFactory from './unit.config';
import functionalConfigFactory from './functional.config';
import distConfigFactory from './dist.config';
import electronConfigFactory from './electron.config';
import logger from './logger';
import { moveBuildOptions } from './util/eject';
import { readFileSync } from 'fs';

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

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

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

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

function createCompiler(config: webpack.Configuration) {
const compiler = webpack(config);
fixMultipleWatchTrigger(compiler);
return compiler;
// for some reason the MultiCompiler type doesn't include hooks, even though they are clearly defined on the
// object coming back.
interface MultiCompilerWithHooks extends webpack.MultiCompiler {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a better way to do this?

hooks: webpack.compilation.CompilerHooks;
}

function createWatchCompiler(config: webpack.Configuration) {
const compiler = createCompiler(config);
function createWatchCompiler(configs: webpack.Configuration[]) {
const compiler = webpack(configs) as MultiCompilerWithHooks;
const spinner = ora('building').start();
compiler.hooks.invalid.tap('@dojo/cli-build-app', () => {
logUpdate('');
Expand Down Expand Up @@ -83,18 +82,18 @@ function serveStatic(
}
}

function build(config: webpack.Configuration, args: any) {
const compiler = createCompiler(config);
function build(configs: webpack.Configuration[], args: any) {
const compiler = webpack(configs);
const spinner = ora('building').start();
return new Promise<webpack.Compiler>((resolve, reject) => {
return new Promise<webpack.MultiCompiler>((resolve, reject) => {
compiler.run((err, stats) => {
spinner.stop();
if (err) {
reject(err);
}
if (stats) {
const runningMessage = args.serve ? `Listening on port ${args.port}...` : '';
const hasErrors = logger(stats.toJson({ warningsFilter }), config, runningMessage, args);
const hasErrors = logger(stats.toJson({ warningsFilter }), configs, runningMessage, args);
if (hasErrors) {
reject({});
return;
Expand Down Expand Up @@ -125,10 +124,15 @@ function buildNpmDependencies(): any {
}
}

async function fileWatch(config: webpack.Configuration, args: any, shouldResolve = false) {
return new Promise<webpack.Compiler>((resolve, reject) => {
const watchOptions = config.watchOptions as webpack.Compiler.WatchOptions;
const compiler = createWatchCompiler(config);
function fileWatch(configs: webpack.Configuration[], args: any, shouldResolve = false): Promise<webpack.MultiCompiler> {
const [mainConfig] = configs;
let compiler: webpack.MultiCompiler;

return new Promise<webpack.MultiCompiler>((resolve, reject) => {
const watchOptions = mainConfig.watchOptions as webpack.Compiler.WatchOptions;

compiler = createWatchCompiler(configs);

compiler.watch(watchOptions, (err, stats) => {
if (err) {
reject(err);
Expand All @@ -139,7 +143,7 @@ async function fileWatch(config: webpack.Configuration, args: any, shouldResolve
args.port
}\nPlease note the serve option is not intended to be used to serve applications in production.`
: 'watching...';
logger(stats.toJson({ warningsFilter }), config, runningMessage, args);
logger(stats.toJson({ warningsFilter }), configs, runningMessage, args);
}
if (shouldResolve) {
resolve(compiler);
Expand All @@ -148,8 +152,9 @@ async function fileWatch(config: webpack.Configuration, args: any, shouldResolve
});
}

async function serve(config: webpack.Configuration, args: any) {
const compiler = args.watch ? await fileWatch(config, args, true) : await build(config, args);
async function serve(configs: webpack.Configuration[], args: any) {
const [mainConfig] = configs;

let isHttps = false;
const base = args.base || '/';

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

const outputDir = (config.output && config.output.path) || process.cwd();
const compiler = args.watch ? await fileWatch(configs, args, true) : await build(configs, args);

const outputDir = (mainConfig.output && mainConfig.output.path) || process.cwd();
let btrOptions = args['build-time-render'];
if (btrOptions) {
if (args.singleBundle || (args.experimental && !!args.experimental.speed)) {
btrOptions = { ...btrOptions, sync: true };
}
const jsonpName = (config.output && config.output.jsonpFunction) || 'unknown';
const jsonpName = (mainConfig.output && mainConfig.output.jsonpFunction) || 'unknown';
const onDemandBtr = new OnDemandBtr({
buildTimeRenderOptions: btrOptions,
scope: libraryName,
base,
compiler,
entries: config.entry ? Object.keys(config.entry) : [],
compiler: compiler.compilers[0],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to pass in electron compiler to BTR. Realistically, probably no reason to pass anything to BTR in this case...

entries: mainConfig.entry ? Object.keys(mainConfig.entry) : [],
outputPath: outputDir,
jsonpName
});
Expand Down Expand Up @@ -295,6 +302,13 @@ const command: Command = {
choices: ['dist', 'dev', 'test', 'unit', 'functional']
});

options('target', {
describe: 'the target',
alias: 't',
default: 'web',
choices: ['web', 'electron']
});

options('watch', {
describe: 'watch for file changes',
alias: 'w'
Expand Down Expand Up @@ -355,35 +369,39 @@ const command: Command = {
},
run(helper: Helper, args: any) {
console.log = () => {};
let config: webpack.Configuration;
let configs: webpack.Configuration[] = [];
args.experimental = args.experimental || {};
args.base = url.resolve('/', args.base || '');
if (!args.base.endsWith('/')) {
args.base = `${args.base}/`;
}

if (args.mode === 'dev') {
config = devConfigFactory(args);
configs.push(devConfigFactory(args));
} else if (args.mode === 'unit' || args.mode === 'test') {
config = unitConfigFactory(args);
configs.push(unitConfigFactory(args));
} else if (args.mode === 'functional') {
config = functionalConfigFactory(args);
configs.push(functionalConfigFactory(args));
} else {
config = distConfigFactory(args);
configs.push(distConfigFactory(args));
}

if (args.target === 'electron') {
configs.push(electronConfigFactory(args));
}

if (args.serve) {
if (testModes.indexOf(args.mode) !== -1) {
return Promise.reject(new Error(`Cannot use \`--serve\` with \`--mode=${args.mode}\``));
}
return serve(config, args);
return serve(configs, args);
}

if (args.watch) {
return fileWatch(config, args);
return fileWatch(configs, args);
}

return build(config, args);
return build(configs, args);
},
eject(helper: Helper): EjectOutput {
return {
Expand Down
23 changes: 23 additions & 0 deletions src/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
"functional"
]
},
"target": {
"type": "string",
"enum": [
"web",
"electron"
]
},
"watch": {
"type": "boolean"
},
Expand Down Expand Up @@ -63,6 +70,13 @@
}
}
},
"electron": {
"properties": {
"browser": {
"type": "object"
}
}
},
"build-time-render": {
"additionalProperties": false,
"properties": {
Expand Down Expand Up @@ -445,6 +459,12 @@
"m": {
"$ref": "#/definitions/mode"
},
"target": {
"$ref": "#/definitions/target"
},
"t": {
"$ref": "#/definitions/target"
},
"watch": {
"$ref": "#/definitions/watch"
},
Expand Down Expand Up @@ -522,6 +542,9 @@
{ "$ref": "#/definitions/imageOptimizationOptions" },
{ "$ref": "#/definitions/imageOptimizationFlag" }
]
},
"electron": {
"$ref": "#/definitions/electron"
}
}
}
Loading