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

New @theia/example-hybrid application #2340

Closed
wants to merge 1 commit into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,12 @@ export class ApplicationPackageManager {

async copy(): Promise<void> {
await fs.ensureDir(this.pck.lib());
await fs.copy(this.pck.frontend('index.html'), this.pck.lib('index.html'));
if (this.pck.isHybrid()) {
await fs.copy(this.pck.frontend('electron', 'index.html'), this.pck.lib('electron', 'index.html'));
await fs.copy(this.pck.frontend('browser', 'index.html'), this.pck.lib('browser', 'index.html'));
} else {
await fs.copy(this.pck.frontend('index.html'), this.pck.lib('index.html'));
}
}

async build(args: string[] = []): Promise<void> {
Expand All @@ -73,8 +78,14 @@ export class ApplicationPackageManager {
async start(args: string[] = []): Promise<void> {
if (this.pck.isElectron()) {
return this.startElectron(args);

} else if (this.pck.isBrowser()) {
return this.startBrowser(args);

} else if (this.pck.isHybrid()) {
return this.startHybrid(args);
}
return this.startBrowser(args);
throw new Error(`Unknown target: '${this.pck.target}'`);
}

async startElectron(args: string[]): Promise<void> {
Expand All @@ -99,4 +110,8 @@ export class ApplicationPackageManager {
this.__process.fork(this.pck.backend('main.js'), mainArgs, options);
}

async startHybrid(args: string[]): Promise<void> {
return this.startBrowser(args);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ export abstract class AbstractGenerator {
return os.EOL + lines.join(os.EOL);
}

get package(): ApplicationPackage {
return this.pck;
}

normalized(string: string): string {
return string.replace(/\W/, '');
}

protected ifBrowser(value: string, defaultValue: string = '') {
return this.pck.ifBrowser(value, defaultValue);
}
Expand All @@ -67,6 +75,10 @@ export abstract class AbstractGenerator {
return this.pck.ifElectron(value, defaultValue);
}

protected ifHybrid(value: string, defaultValue: string = '') {
return this.pck.ifHybrid(value, defaultValue);
}

protected async write(path: string, content: string): Promise<void> {
await fs.ensureFile(path);
await fs.writeFile(path, content);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,32 @@ export class BackendGenerator extends AbstractGenerator {
await this.write(this.pck.backend('main.js'), this.compileMain(backendModules));
}

protected compileExpressStatic(segment: string = ''): string {
return `express.static(path.join(__dirname, '../../lib${segment}'), {
index: 'index.html'
})`;
}

protected compileMiddleware(backendModules: Map<string, string>): string {
return this.pck.isHybrid() ? `
const electron = ${this.compileExpressStatic('/electron')};
const browser = ${this.compileExpressStatic('/browser')};

application.use('*', (request, ...args) => {
const userAgent = request.headers['user-agent'] || 'unknown';
const isElectron = /electron/ig.test(userAgent);
request.url = request.baseUrl || request.url;
return (isElectron ?
electron : browser)(request, ...args);
});
` : `
application.use(${this.compileExpressStatic()});
`;
}

protected compileServer(backendModules: Map<string, string>): string {
return `// @ts-check
return `\
// @ts-check
require('reflect-metadata');
const path = require('path');
const express = require('express');
Expand Down Expand Up @@ -54,9 +78,7 @@ function start(port, host) {
const cliManager = container.get(CliManager);
return cliManager.initializeCli().then(function () {
const application = container.get(BackendApplication);
application.use(express.static(path.join(__dirname, '../../lib'), {
index: 'index.html'
}));
${this.compileMiddleware(backendModules)}
return application.start(port, host);
});
}
Expand All @@ -72,7 +94,8 @@ module.exports = (port, host) => Promise.resolve()${this.compileBackendModuleImp
}

protected compileMain(backendModules: Map<string, string>): string {
return `// @ts-check
return `\
// @ts-check
const serverPath = require('path').resolve(__dirname, 'server');
const address = require('@theia/core/lib/node/cluster/main').default(serverPath);
address.then(function (address) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,25 @@ export class FrontendGenerator extends AbstractGenerator {

async generate(): Promise<void> {
const frontendModules = this.pck.targetFrontendModules;
await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules));
await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules));

if (this.package.isHybrid()) {
await this.write(this.pck.frontend('browser', 'index.html'), this.compileIndexHtml(this.package.frontendModules));
await this.write(this.pck.frontend('electron', 'index.html'), this.compileIndexHtml(this.package.frontendElectronModules));
await this.write(this.pck.frontend('browser', 'index.js'), this.compileIndexJs(this.package.frontendModules));
await this.write(this.pck.frontend('electron', 'index.js'), this.compileIndexJs(this.package.frontendElectronModules));
} else {
await this.write(this.pck.frontend('index.html'), this.compileIndexHtml(frontendModules));
await this.write(this.pck.frontend('index.js'), this.compileIndexJs(frontendModules));
}

if (this.pck.isElectron()) {
await this.write(this.pck.frontend('electron-main.js'), this.compileElectronMain());
}
}

protected compileIndexHtml(frontendModules: Map<string, string>): string {
return `<!DOCTYPE html>
return `\
<!DOCTYPE html>
<html>

<head>${this.compileIndexHead(frontendModules)}
Expand All @@ -48,8 +58,10 @@ export class FrontendGenerator extends AbstractGenerator {
}

protected compileIndexJs(frontendModules: Map<string, string>): string {
return `// @ts-check
${this.ifBrowser("require('es6-promise/auto');")}
return `\
// @ts-check
${this.ifBrowser(`require('es6-promise/auto');
`)}\
require('reflect-metadata');
const { Container } = require('inversify');
const { FrontendApplication } = require('@theia/core/lib/browser');
Expand Down Expand Up @@ -90,8 +102,8 @@ module.exports = Promise.resolve()${this.compileFrontendModuleImports(frontendMo
}

protected compileElectronMain(): string {
return `// @ts-check

return `\
// @ts-check
// Workaround for https://github.com/electron/electron/issues/9225. Chrome has an issue where
// in certain locales (e.g. PL), image metrics are wrongly computed. We explicitly set the
// LC_NUMERIC to prevent this from happening (selects the numeric formatting category of the
Expand All @@ -105,18 +117,45 @@ const { join } = require('path');
const { isMaster } = require('cluster');
const { fork } = require('child_process');
const { app, BrowserWindow, ipcMain } = require('electron');
const EventEmitter = require('events');
const fileSchemeTester = /^file:/;

const localUriEvent = new EventEmitter();
let localUri = undefined;
const windows = [];

function setLocalUri(uri) {
localUriEvent.emit('update', localUri = uri);
}
function resolveLocalUriFromPort(port) {
setLocalUri('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port);
}

function createNewWindow(theUrl) {
const newWindow = new BrowserWindow({ width: 1024, height: 728, show: !!theUrl });
const config = {
width: 1024,
height: 728,
show: !!theUrl
};

// Converts 'localhost' to the running local backend endpoint
if (localUri && theUrl === 'localhost') {
theUrl = localUri;
}

if (!!theUrl && !fileSchemeTester.test(theUrl)) {
config.webPreferences = {
// nodeIntegration: false,
paul-marechal marked this conversation as resolved.
Show resolved Hide resolved
// contextIsolation: true,
};
};

const newWindow = new BrowserWindow(config);
if (windows.length === 0) {
newWindow.webContents.on('new-window', (event, url, frameName, disposition, options) => {
// If the first electron window isn't visible, then all other new windows will remain invisible.
// https://github.com/electron/electron/issues/3751
options.show = true;
options.width = 1024;
options.height = 728;
Object.assign(options, config);
});
}
windows.push(newWindow);
Expand Down Expand Up @@ -148,25 +187,29 @@ if (isMaster) {
});
app.on('ready', () => {
// Check whether we are in bundled application or development mode.
const devMode = process.defaultApp || /node_modules[\/]electron[\/]/.test(process.execPath);
const devMode = process.defaultApp || /node_modules[\\/]electron[\\/]/.test(process.execPath);

const mainWindow = createNewWindow();
const loadMainWindow = (port) => {
mainWindow.loadURL('file://' + join(__dirname, '../../lib/index.html') + '?port=' + port);
const loadMainWindow = (uri) => {
// mainWindow.loadURL(\`http://localhost:\${port}\`);
Copy link
Contributor

Choose a reason for hiding this comment

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

left-over code or example of mainWindow.loadURL() usage?

mainWindow.loadURL(uri);
};
localUriEvent.once('update', loadMainWindow);

const mainPath = join(__dirname, '..', 'backend', 'main');
// We need to distinguish between bundled application and development mode when starting the clusters.
// See: https://github.com/electron/electron/issues/6337#issuecomment-230183287
if (devMode) {
require(mainPath).then(address => {
loadMainWindow(address.port);
resolveLocalUriFromPort(address.port)
}).catch((error) => {
console.error(error);
app.exit(1);
});
} else {
const cp = fork(mainPath);
cp.on('message', (message) => {
loadMainWindow(message);
resolveLocalUriFromPort(message);
});
cp.on('error', (error) => {
console.error(error);
Expand Down
51 changes: 37 additions & 14 deletions dev-packages/application-manager/src/generator/webpack-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@

import * as paths from 'path';
import { AbstractGenerator } from './abstract-generator';
import { ApplicationProps } from '@theia/application-package';

export class WebpackGenerator extends AbstractGenerator {

async generate(): Promise<void> {
await this.write(this.configPath, this.compileWebpackConfig());
await this.write(this.configPath, this.compileWebpackConfigFile());
}

get configPath(): string {
Expand All @@ -31,7 +32,18 @@ export class WebpackGenerator extends AbstractGenerator {
return this.pck.resolveModulePath(moduleName, path).split(paths.sep).join('/');
}

protected compileWebpackConfig(): string {
protected compileExports(): string {
const exports = [];
if (this.package.isHybrid() || this.package.isBrowser()) {
exports.push(this.normalized('browser'));
}
if (this.package.isHybrid() || this.package.isElectron()) {
exports.push(this.normalized('electron'));
}
return `[${exports.join(', ')}]`;
}

protected compileWebpackConfigFile(): string {
return `// @ts-check
const path = require('path');
const webpack = require('webpack');
Expand All @@ -45,27 +57,38 @@ const { mode } = yargs.option('mode', {
choices: ["development", "production"],
default: "production"
}).argv;
const development = mode === 'development';${this.ifMonaco(() => `

const development = mode === 'development';
${this.ifMonaco(() => `
const monacoEditorCorePath = development ? '${this.resolve('@typefox/monaco-editor-core', 'dev/vs')}' : '${this.resolve('@typefox/monaco-editor-core', 'min/vs')}';
const monacoCssLanguagePath = '${this.resolve('monaco-css', 'release/min')}';
const monacoHtmlLanguagePath = '${this.resolve('monaco-html', 'release/min')}';`)}
const monacoHtmlLanguagePath = '${this.resolve('monaco-html', 'release/min')}';
`)}
${this.package.isHybrid() || this.package.isBrowser() ? this.compileWebpackConfigObjectFor('browser') : ''}\
${this.package.isHybrid() || this.package.isElectron() ? this.compileWebpackConfigObjectFor('electron') : ''}\

module.exports = {
entry: path.resolve(__dirname, 'src-gen/frontend/index.js'),
module.exports = ${this.compileExports()};
`;
}

protected compileWebpackConfigObjectFor(target: ApplicationProps.Target): string {
const normalizedTarget = this.normalized(target);
return `\
const ${normalizedTarget} = {
entry: path.resolve(__dirname, 'src-gen/frontend/${this.ifHybrid(normalizedTarget + '/')}index.js'),
output: {
filename: 'bundle.js',
path: outputPath
path: path.resolve(outputPath, '${this.ifHybrid(normalizedTarget)}')
},
target: '${this.ifBrowser('web', 'electron-renderer')}',
target: '${target === 'browser' ? 'web' : 'electron-renderer'}',
mode,
node: {${this.ifElectron(`
node: {${target === 'electron' ? `
__dirname: false,
__filename: false`, `
__filename: false`
: /* else */ `
fs: 'empty',
child_process: 'empty',
net: 'empty',
crypto: 'empty'`)}
crypto: 'empty'` }
},
module: {
rules: [
Expand Down Expand Up @@ -153,7 +176,7 @@ module.exports = {
stats: {
warnings: true
}
};`;
};
`;
}

}
7 changes: 5 additions & 2 deletions dev-packages/application-manager/src/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
paul-marechal marked this conversation as resolved.
Show resolved Hide resolved

import { ApplicationProps } from '@theia/application-package/lib/';
import fs = require('fs-extra');
import path = require('path');
import cp = require('child_process');

export function rebuild(target: 'electron' | 'browser', modules: string[]) {
export function rebuild(target: ApplicationProps.Target, modules: string[]) {
const nodeModulesPath = path.join(process.cwd(), 'node_modules');
const browserModulesPath = path.join(process.cwd(), '.browser_modules');
const modulesToProcess = modules || ['node-pty', 'vscode-nsfw', 'find-git-repositories'];
Expand Down Expand Up @@ -54,7 +55,8 @@ export function rebuild(target: 'electron' | 'browser', modules: string[]) {
fs.writeFile(packFile, packageText);
}, 100);
}
} else if (target === 'browser' && fs.existsSync(browserModulesPath)) {

} else if (/^(browser|hybrid)$/.test(target) && fs.existsSync(browserModulesPath)) {
for (const moduleName of fs.readdirSync(browserModulesPath)) {
console.log('Reverting ' + moduleName);
const src = path.join(browserModulesPath, moduleName);
Expand All @@ -63,6 +65,7 @@ export function rebuild(target: 'electron' | 'browser', modules: string[]) {
fs.copySync(src, dest);
}
fs.removeSync(browserModulesPath);

} else {
console.log('native node modules are already rebuilt for ' + target);
}
Expand Down
Loading