-
Notifications
You must be signed in to change notification settings - Fork 32
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 || '/'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 || '/'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 || ''; | ||
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(''); | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -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); | ||
|
@@ -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 || '/'; | ||
|
||
|
@@ -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], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
}); | ||
|
@@ -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' | ||
|
@@ -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 { | ||
|
There was a problem hiding this comment.
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.