diff --git a/src/extensions/example-extension/example-extension.ts b/src/extensions/example-extension/example-extension.ts index 9d5f2eab5e..7a59c8cd8d 100644 --- a/src/extensions/example-extension/example-extension.ts +++ b/src/extensions/example-extension/example-extension.ts @@ -1,4 +1,5 @@ -import { LensExtension } from "@lens"; // todo: handle runtime import +// import { LensExtension } from "@lens"; // fixme: provide runtime import +import { LensExtension } from "../extension"; export default class ExampleExtension extends LensExtension { async init(): Promise { diff --git a/src/extensions/extension-store.ts b/src/extensions/extension-store.ts index 3ed7249248..9a9d8b85c3 100644 --- a/src/extensions/extension-store.ts +++ b/src/extensions/extension-store.ts @@ -21,6 +21,12 @@ export interface ExtensionModel { updateUrl?: string; } +export interface InstalledExtension { + manifestPath: string; + manifest: ExtensionManifest; + LensExtension: new (model: T, manifest?: ExtensionManifest) => LensExtension; +} + export class ExtensionStore extends BaseStore { private constructor() { super({ @@ -31,7 +37,7 @@ export class ExtensionStore extends BaseStore { @observable version: ExtensionVersion = "0.0.0"; @observable extensions = observable.map(); @observable removed = observable.map(); - @observable installed = observable.set([], { equals: comparer.shallow }); + @observable installed = observable.map([], { equals: comparer.shallow }); get folderPath(): string { if (isDevelopment) { @@ -45,31 +51,41 @@ export class ExtensionStore extends BaseStore { return super.load(); } - async loadInstalledExtensions() { - const extensions = await this.loadExtensions(this.folderPath); - this.installed.replace(extensions); + getExtensionByManifest(manifestPath: string): InstalledExtension { + let manifestJson: ExtensionManifest; + let mainJs: string; + try { + manifestJson = __non_webpack_require__(manifestPath); // eslint-disable-line + mainJs = path.resolve(path.dirname(manifestPath), manifestJson.main); // fixme: compile *.ts on the fly + const LensExtension = __non_webpack_require__(mainJs).default; // eslint-disable-line + return { + manifestPath: manifestPath, + manifest: manifestJson, + LensExtension: LensExtension, + } + } catch (err) { + logger.error(`[EXTENSION-STORE]: can't load extension at ${manifestPath}: ${err}`, { manifestJson, mainJs }); + } } - async loadExtensions(basePath: string): Promise { - const paths = await fs.readdir(basePath); + @action + async loadInstalledExtensions() { + const extensions = await this.loadExtensions(this.folderPath); + this.installed.replace(extensions.map(ext => [ext.manifestPath, ext])); + } + + async loadExtensions(folderPath: string): Promise { + const paths = await fs.readdir(folderPath); const manifestsLoading = paths.map(fileName => { - const absPath = path.resolve(basePath, fileName); + const absPath = path.resolve(folderPath, fileName); const manifestPath = path.resolve(absPath, "package.json"); - return new Promise(async resolve => { - try { - const manifestJson = await fs.readJson(manifestPath); - resolve({ - ...manifestJson, - manifestPath: manifestPath, - }); - } catch (err) { - resolve(null); - } - }) + return fs.access(manifestPath, fs.constants.F_OK) + .then(() => this.getExtensionByManifest(manifestPath)) + .catch(() => null) }); let extensions = await Promise.all(manifestsLoading); extensions = extensions.filter(v => !!v); // filter out files and invalid folders (without manifest.json) - logger.info(`[EXTENSION-STORE]: loaded ${extensions.length} extensions`, { basePath, extensions }); + logger.info(`[EXTENSION-STORE]: ${extensions.length} extensions loaded`, { folderPath, extensions }); return extensions; } @@ -98,9 +114,19 @@ export class ExtensionStore extends BaseStore { } }) currentExtensions.forEach(model => { + const manifest = this.installed.get(model.manifestPath); + if (!manifest) { + logger.error(`[EXTENSION-STORE]: can't load extension manifest at ${model.manifestPath}`, { model }) + return; + } const extension = this.getById(model.id) if (!extension) { - this.extensions.set(model.id, new LensExtension(model)); + try { + const { LensExtension, manifest: manifestJson } = manifest; + this.extensions.set(model.id, new LensExtension(model, manifestJson)); + } catch (err) { + logger.error(`[EXTENSION-STORE]: init extension failed: ${err}`, { model, manifest }) + } } else { extension.importModel(model); } diff --git a/src/extensions/extension.ts b/src/extensions/extension.ts index e06554881f..66612a66a6 100644 --- a/src/extensions/extension.ts +++ b/src/extensions/extension.ts @@ -8,7 +8,7 @@ import logger from "../main/logger"; // * Lazy load/unload extension (js/ts?) (from sources: local folder, npm_modules/@lens/some_plugin, etc.) // * figure out how to expose lens external apis to extension: // - opt1: import {someApi} from "@lens" => replaced to import from "$PATH/build/Lens.js" on the fly ? -// - opt2: eval with injected exposed apis / contents.executeJavaScript / script[src] / etc. ? +// - opt2: dynamic require() / contents.executeJavaScript / etc. ? export type ExtensionId = string; export type ExtensionVersion = string | number; @@ -28,14 +28,14 @@ export class LensExtension implements ExtensionModel { whenReady = when(() => this.isReady); - constructor(model: ExtensionModel) { - this.importModel(model); + constructor(model: ExtensionModel, manifest?: ExtensionManifest) { + this.importModel(model, manifest); } @action - async importModel({ enabled, manifestPath, ...model }: ExtensionModel) { + async importModel({ enabled, manifestPath, ...model }: ExtensionModel, manifest?: ExtensionManifest) { try { - this.manifest = await readJsonSync(manifestPath, { throws: true }) + this.manifest = manifest || await readJsonSync(manifestPath, { throws: true }) this.manifestPath = manifestPath; this.isEnabled = enabled; Object.assign(this, model); diff --git a/webpack.renderer.ts b/webpack.renderer.ts index b6c35535ce..d51aad89d3 100755 --- a/webpack.renderer.ts +++ b/webpack.renderer.ts @@ -22,6 +22,7 @@ export default function (): webpack.Configuration { path: buildDir, filename: '[name].js', chunkFilename: 'chunks/[name].js', + libraryTarget: "commonjs2", }, resolve: { extensions: [