1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

extensions-api -- loading extension modules -- part 1

Signed-off-by: Roman <ixrock@gmail.com>
This commit is contained in:
Roman 2020-09-02 13:24:30 +03:00
parent 1e08b01423
commit b2414542b8
6 changed files with 47 additions and 18 deletions

1
.gitignore vendored
View File

@ -9,4 +9,5 @@ static/build/**
binaries/client/ binaries/client/
binaries/server/ binaries/server/
src/extensions/**/*.js src/extensions/**/*.js
src/extensions/**/*.d.ts
locales/**/**.js locales/**/**.js

View File

@ -19,6 +19,7 @@
"compile:main": "webpack --config webpack.main.ts", "compile:main": "webpack --config webpack.main.ts",
"compile:renderer": "webpack --config webpack.renderer.ts", "compile:renderer": "webpack --config webpack.renderer.ts",
"compile:i18n": "lingui compile", "compile:i18n": "lingui compile",
"compile-extensions": "tsc --project src/extensions --watch",
"build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens", "build:linux": "yarn compile && electron-builder --linux --dir -c.productName=Lens",
"build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens", "build:mac": "yarn compile && electron-builder --mac --dir -c.productName=Lens",
"build:win": "yarn compile && electron-builder --win --dir -c.productName=Lens", "build:win": "yarn compile && electron-builder --win --dir -c.productName=Lens",

View File

@ -1,8 +1,12 @@
import { LensExtension } from "@lens"; // fixme: provide runtime import // import { LensExtension } from "@lens"; // fixme: provide runtime import
export default class ExampleExtension extends LensExtension { export default class ExampleExtension /*extends LensExtension*/ {
async init(): Promise<any> { async init(): Promise<any> {
console.log('Example extension: init') console.log('Example extension: init')
return super.init(); // return super.init();
} }
} }
export const someData = {
title: "it works"
}

View File

@ -4,7 +4,6 @@ import { action, comparer, observable, toJS } from "mobx";
import { BaseStore } from "../common/base-store"; import { BaseStore } from "../common/base-store";
import { ExtensionId, ExtensionManifest, ExtensionVersion, LensExtension } from "./extension"; import { ExtensionId, ExtensionManifest, ExtensionVersion, LensExtension } from "./extension";
import { isDevelopment } from "../common/vars"; import { isDevelopment } from "../common/vars";
import logger from "../main/logger";
export interface ExtensionStoreModel { export interface ExtensionStoreModel {
version: ExtensionVersion; version: ExtensionVersion;
@ -24,7 +23,10 @@ export interface ExtensionModel {
export interface InstalledExtension<T extends ExtensionModel = any> { export interface InstalledExtension<T extends ExtensionModel = any> {
manifestPath: string; manifestPath: string;
manifest: ExtensionManifest; manifest: ExtensionManifest;
LensExtension: new (model: T, manifest?: ExtensionManifest) => LensExtension; extensionModule: {
[name: string]: any;
default: new (model: T, manifest?: ExtensionManifest) => LensExtension
}
} }
export class ExtensionStore extends BaseStore<ExtensionStoreModel> { export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
@ -56,15 +58,16 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
let mainJs: string; let mainJs: string;
try { try {
manifestJson = __non_webpack_require__(manifestPath); // eslint-disable-line manifestJson = __non_webpack_require__(manifestPath); // eslint-disable-line
mainJs = path.resolve(path.dirname(manifestPath), manifestJson.main); // todo: compile *.ts on the fly? mainJs = path.resolve(path.dirname(manifestPath), manifestJson.main);
const LensExtension = __non_webpack_require__(mainJs).default; // eslint-disable-line mainJs = mainJs.replace(/\.ts$/i, ".js"); // todo: compile *.ts on the fly?
const extensionModule = __non_webpack_require__(mainJs); // eslint-disable-line
return { return {
manifestPath: manifestPath, manifestPath: manifestPath,
manifest: manifestJson, manifest: manifestJson,
LensExtension: LensExtension, extensionModule: extensionModule,
} }
} catch (err) { } catch (err) {
logger.error(`[EXTENSION-STORE]: can't load extension at ${manifestPath}: ${err}`, { manifestJson, mainJs }); console.error(`[EXTENSION-STORE]: can't load extension at ${manifestPath}: ${err}`, { manifestJson, mainJs });
} }
} }
@ -85,7 +88,7 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
}); });
let extensions = await Promise.all(manifestsLoading); let extensions = await Promise.all(manifestsLoading);
extensions = extensions.filter(v => !!v); // filter out files and invalid folders (without manifest.json) extensions = extensions.filter(v => !!v); // filter out files and invalid folders (without manifest.json)
logger.info(`[EXTENSION-STORE]: ${extensions.length} extensions loaded`, { folderPath, extensions }); console.info(`[EXTENSION-STORE]: ${extensions.length} extensions loaded`, { folderPath, extensions });
return extensions; return extensions;
} }
@ -116,16 +119,17 @@ export class ExtensionStore extends BaseStore<ExtensionStoreModel> {
currentExtensions.forEach(model => { currentExtensions.forEach(model => {
const manifest = this.installed.get(model.manifestPath); const manifest = this.installed.get(model.manifestPath);
if (!manifest) { if (!manifest) {
logger.error(`[EXTENSION-STORE]: can't load extension manifest at ${model.manifestPath}`, { model }) console.error(`[EXTENSION-STORE]: can't load extension manifest at ${model.manifestPath}`, { model })
return; return;
} }
const extension = this.getById(model.id) const extension = this.getById(model.id)
if (!extension) { if (!extension) {
try { try {
const { LensExtension, manifest: manifestJson } = manifest; const { manifest: manifestJson, extensionModule } = manifest;
const LensExtension = extensionModule.default;
this.extensions.set(model.id, new LensExtension(model, manifestJson)); this.extensions.set(model.id, new LensExtension(model, manifestJson));
} catch (err) { } catch (err) {
logger.error(`[EXTENSION-STORE]: init extension failed: ${err}`, { model, manifest }) console.error(`[EXTENSION-STORE]: init extension failed: ${err}`, { model, manifest })
} }
} else { } else {
extension.importModel(model); extension.importModel(model);

View File

@ -1,14 +1,13 @@
import type { ExtensionModel } from "./extension-store";
import { readJsonSync } from "fs-extra"; import { readJsonSync } from "fs-extra";
import { action, observable, when } from "mobx"; import { action, observable, when } from "mobx";
import { ExtensionModel } from "./extension-store";
import extensionManifest from "./example-extension/package.json" import extensionManifest from "./example-extension/package.json"
import logger from "../main/logger"; import logger from "../main/logger";
// TODO: extensions api // TODO: extensions api
// * Lazy load/unload extension (js/ts?) (from sources: local folder, npm_modules/@lens/some_plugin, etc.) // - figure out how to expose/inject lens apis to extension:
// * figure out how to expose lens external apis to extension: // -- replace import "@lens" to real path to "build/Lens.js" or maybe "build/Lens-extensions.api.js"
// - opt1: import {someApi} from "@lens" => replaced to import from "$PATH/build/Lens.js" on the fly ? // -- load extension via NodeJS.require() / webContents.executeJavaScript()
// - opt2: dynamic require() / contents.executeJavaScript / etc. ?
export type ExtensionId = string; export type ExtensionId = string;
export type ExtensionVersion = string | number; export type ExtensionVersion = string | number;

View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"jsx": "react",
"target": "ES2017",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"module": "CommonJS",
"moduleResolution": "Node",
"noImplicitAny": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"declaration": true
},
"files": [
"./example-extension/example-extension.ts"
]
}