Skip to content

Commit

Permalink
[plugin] remove raw package.json model from metadata
Browse files Browse the repository at this point in the history
It accumulates to a lot of data with many plugins and can block the connection leading to slow start up time and unresponsive UI.

Signed-off-by: Anton Kosiakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Sep 25, 2019
1 parent e9acc49 commit 016f6d6
Show file tree
Hide file tree
Showing 17 changed files with 167 additions and 106 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ Breaking changes:
- [core][monaco][plugin] reload plugins on reconnection [6159](https://github.com/eclipse-theia/theia/pull/6159)
- Extenders should implement `Disposable` for plugin main services to handle reconnection properly.
- Many APIs are refactored to return `Disposable`.
- [plugin] don't block web socket with many plugins [6252](https://github.com/eclipse-theia/theia/pull/6252)
- frontend plugins don't have access to raw package.json model anymore
- backend plugins don't have access to `PluginModel.contributes` anymore, read them from raw package.json model instead
- `PluginManagerExt.$init` does not start plugins anymore, but only initialize the manager RPC services, call `$start` to start plugins

Misc:

Expand Down
3 changes: 2 additions & 1 deletion packages/plugin-ext-vscode/src/node/plugin-vscode-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ export const doInitialization: BackendInitializationFn = (apiFactory: PluginAPIF
vscode.commands.registerCommand = function (command: theia.CommandDescription | string, handler?: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any): any {
// use of the ID when registering commands
if (typeof command === 'string') {
const commands = plugin.model.contributes && plugin.model.contributes.commands;
const rawCommands = plugin.rawModel.contributes && plugin.rawModel.contributes.commands;
const commands = rawCommands ? Array.isArray(rawCommands) ? rawCommands : [rawCommands] : undefined;
if (handler && commands && commands.some(item => item.command === command)) {
return vscode.commands.registerHandler(command, handler, thisArg);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/plugin-ext-vscode/src/node/scanner-vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca
plugin.name = plugin.name.substr(built_prefix.length);
}
const result: PluginModel = {
packagePath: plugin.packagePath,
// see id definition: https://github.com/microsoft/vscode/blob/15916055fe0cb9411a5f36119b3b012458fe0a1d/src/vs/platform/extensions/common/extensions.ts#L167-L169
id: `${plugin.publisher.toLowerCase()}.${plugin.name.toLowerCase()}`,
name: plugin.name,
Expand All @@ -50,9 +51,11 @@ export class VsCodePluginScanner extends TheiaPluginScanner implements PluginSca
}
};
if (options.contributions) {
result.activationEvents = plugin.activationEvents;
result.contributes = this.readContributions(plugin);
}
if (options.dependencies) {
result.rawExtensionDependencies = plugin.extensionDependencies;
result.extensionDependencies = this.getDeployableDependencies(plugin.extensionDependencies || []);
}
return result;
Expand Down
1 change: 1 addition & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export const emptyPlugin: Plugin = {
type: 'empty',
version: 'empty'
},
packagePath: 'empty',
entryPoint: {

}
Expand Down
12 changes: 9 additions & 3 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,17 +385,24 @@ export interface PluginModel {
frontend?: string;
backend?: string;
};
packagePath: string;

// frontend contributions info -- start
contributes?: PluginContribution;
activationEvents?: string[];
// frontend contributions info -- end

// backend dependencies info -- start
/**
* The deployable form of extensionDependencies from package.json,
* i.e. not `publisher.name`, but `vscode:extension/publisher.name`.
* The deployable form of `rawExtensionDependencies`,
* e.g. not `publisher.name`, but `vscode:extension/publisher.name`.
*/
extensionDependencies?: string[];
/**
* The extension dependencies as they defined in plugin package.json,
* e.g. in `publisher.name` form.
*/
rawExtensionDependencies?: string[];
// backend dependencies info -- end
}

Expand Down Expand Up @@ -596,7 +603,6 @@ export interface ExtensionContext {

export interface PluginMetadata {
host: string;
source: PluginPackage;
model: PluginModel;
lifecycle: PluginLifecycle;
}
Expand Down
5 changes: 3 additions & 2 deletions packages/plugin-ext/src/hosted/browser/hosted-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ export class HostedPluginSupport {
};
// remove contributions info in order to reduce size of JSON-RPC messages
delete metadata.model.contributes;
delete metadata.model.activationEvents;
return metadata;
});
thenable.push((async () => {
Expand Down Expand Up @@ -488,13 +489,13 @@ export class HostedPluginSupport {
}

protected async activateByWorkspaceContains(manager: PluginManagerExt, plugin: PluginMetadata): Promise<void> {
if (!plugin.source.activationEvents) {
if (!plugin.model.activationEvents) {
return;
}
const paths: string[] = [];
const includePatterns: string[] = [];
// should be aligned with https://github.com/microsoft/vscode/blob/da5fb7d5b865aa522abc7e82c10b746834b98639/src/vs/workbench/api/node/extHostExtensionService.ts#L460-L469
for (const activationEvent of plugin.source.activationEvents) {
for (const activationEvent of plugin.model.activationEvents) {
if (/^workspaceContains:/.test(activationEvent)) {
const fileNameOrGlob = activationEvent.substr('workspaceContains:'.length);
if (fileNameOrGlob.indexOf('*') >= 0 || fileNameOrGlob.indexOf('?') >= 0) {
Expand Down
24 changes: 19 additions & 5 deletions packages/plugin-ext/src/hosted/browser/worker/worker-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { RPCProtocolImpl } from '../../../common/rpc-protocol';
import { PluginManagerExtImpl } from '../../../plugin/plugin-manager';
import { MAIN_RPC_CONTEXT, Plugin, emptyPlugin } from '../../../common/plugin-api-rpc';
import { createAPIFactory } from '../../../plugin/plugin-context';
import { getPluginId, PluginMetadata } from '../../../common/plugin-protocol';
import { getPluginId, PluginMetadata, PluginPackage, PluginModel } from '../../../common/plugin-protocol';
import * as theia from '@theia/plugin';
import { PreferenceRegistryExtImpl } from '../../../plugin/preference-registry';
import { ExtPluginApi } from '../../../common/plugin-ext-api-contribution';
Expand Down Expand Up @@ -58,6 +58,20 @@ const preferenceRegistryExt = new PreferenceRegistryExtImpl(rpc, workspaceExt);
const debugExt = createDebugExtStub(rpc);
const clipboardExt = new ClipboardExt(rpc);

function stubPluginPackage(pluginModel: PluginModel): PluginPackage {
return {
name: pluginModel.name,
displayName: pluginModel.displayName,
engines: {
[pluginModel.engine.type]: pluginModel.engine.version
},
packagePath: pluginModel.packagePath,
publisher: pluginModel.publisher,
version: pluginModel.version,
description: ''
};
}

const pluginManager = new PluginManagerExtImpl({
// tslint:disable-next-line:no-any
loadPlugin(plugin: Plugin): any {
Expand Down Expand Up @@ -90,10 +104,10 @@ const pluginManager = new PluginManagerExtImpl({
}
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.frontend!,
pluginFolder: plg.source.packagePath,
pluginFolder: pluginModel.packagePath,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel: plg.source
rawModel: stubPluginPackage(pluginModel)
};
result.push(plugin);
const apiImpl = apiFactory(plugin);
Expand All @@ -102,10 +116,10 @@ const pluginManager = new PluginManagerExtImpl({
} else {
foreign.push({
pluginPath: pluginModel.entryPoint.backend!,
pluginFolder: plg.source.packagePath,
pluginFolder: pluginModel.packagePath,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel: plg.source
rawModel: stubPluginPackage(pluginModel)
});
}
}
Expand Down
1 change: 0 additions & 1 deletion packages/plugin-ext/src/hosted/node/metadata-scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export class MetadataScanner {
const scanner = this.getScanner(plugin);
return {
host: 'main',
source: plugin,
model: scanner.getModel(plugin, options),
lifecycle: scanner.getLifecycle(plugin)
};
Expand Down
61 changes: 34 additions & 27 deletions packages/plugin-ext/src/hosted/node/plugin-host-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { WorkspaceExtImpl } from '../../plugin/workspace';
import { MessageRegistryExt } from '../../plugin/message-registry';
import { EnvNodeExtImpl } from '../../plugin/node/env-node-ext';
import { ClipboardExt } from '../../plugin/clipboard-ext';
import { loadManifest } from './plugin-manifest-loader';

/**
* Handle the RPC calls.
Expand Down Expand Up @@ -128,40 +129,46 @@ export class PluginHostRPC {
console.error(e);
}
},
init(raw: PluginMetadata[]): [Plugin[], Plugin[]] {
async init(raw: PluginMetadata[]): Promise<[Plugin[], Plugin[]]> {
console.log('PLUGIN_HOST(' + process.pid + '): PluginManagerExtImpl/init()');
const result: Plugin[] = [];
const foreign: Plugin[] = [];
for (const plg of raw) {
const pluginModel = plg.model;
const pluginLifecycle = plg.lifecycle;

if (pluginModel.entryPoint!.frontend) {
foreign.push({
pluginPath: pluginModel.entryPoint.frontend!,
pluginFolder: plg.source.packagePath,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel: plg.source
});
} else {
let backendInitPath = pluginLifecycle.backendInitPath;
// if no init path, try to init as regular Theia plugin
if (!backendInitPath) {
backendInitPath = __dirname + '/scanners/backend-init-theia.js';
}
try {
const pluginModel = plg.model;
const pluginLifecycle = plg.lifecycle;

const rawModel = await loadManifest(pluginModel.packagePath);
rawModel.packagePath = pluginModel.packagePath;
if (pluginModel.entryPoint!.frontend) {
foreign.push({
pluginPath: pluginModel.entryPoint.frontend!,
pluginFolder: pluginModel.packagePath,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel
});
} else {
let backendInitPath = pluginLifecycle.backendInitPath;
// if no init path, try to init as regular Theia plugin
if (!backendInitPath) {
backendInitPath = __dirname + '/scanners/backend-init-theia.js';
}

const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.backend!,
pluginFolder: plg.source.packagePath,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel: plg.source
};
const plugin: Plugin = {
pluginPath: pluginModel.entryPoint.backend!,
pluginFolder: pluginModel.packagePath,
model: pluginModel,
lifecycle: pluginLifecycle,
rawModel
};

self.initContext(backendInitPath, plugin);
self.initContext(backendInitPath, plugin);

result.push(plugin);
result.push(plugin);
}
} catch (e) {
console.error(`Failed to initialize ${plg.model.id} plugin.`, e);
}
}
return [result, foreign];
Expand Down
71 changes: 71 additions & 0 deletions packages/plugin-ext/src/hosted/node/plugin-manifest-loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/********************************************************************************
* Copyright (C) 2018 Red Hat, Inc. and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

// tslint:disable:no-any

import * as path from 'path';
import * as fs from 'fs-extra';

const NLS_REGEX = /^%([\w\d.-]+)%$/i;

export async function loadManifest(pluginPath: string): Promise<any> {
const [manifest, translations] = await Promise.all([
fs.readJson(path.join(pluginPath, 'package.json')),
loadTranslations(pluginPath)
]);
return manifest && translations && Object.keys(translations).length ?
localize(manifest, translations) :
manifest;
}

async function loadTranslations(pluginPath: string): Promise<any> {
try {
return await fs.readJson(path.join(pluginPath, 'package.nls.json'));
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
}
return {};
}
}

function localize(value: any, translations: {
[key: string]: string
}): any {
if (typeof value === 'string') {
const match = NLS_REGEX.exec(value);
return match && translations[match[1]] || value;
}
if (Array.isArray(value)) {
const result = [];
for (const item of value) {
result.push(localize(item, translations));
}
return result;
}
if (value === null) {
return value;
}
if (typeof value === 'object') {
const result: { [key: string]: any } = {};
// tslint:disable-next-line:forin
for (const propertyName in value) {
result[propertyName] = localize(value[propertyName], translations);
}
return result;
}
return value;
}
Loading

0 comments on commit 016f6d6

Please sign in to comment.