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

Merge branch 'master' into fix/page-registry-get-extension-page-url

This commit is contained in:
Lauri Nevala 2020-12-02 12:25:31 +02:00
commit e389692af8
14 changed files with 263 additions and 163 deletions

View File

@ -4,6 +4,7 @@ module.exports = {
ignorePatterns: [ ignorePatterns: [
"**/node_modules/**/*", "**/node_modules/**/*",
"**/dist/**/*", "**/dist/**/*",
"**/static/**/*",
], ],
settings: { settings: {
react: { react: {

View File

@ -1,5 +1,5 @@
{ {
"name": "extension-example", "name": "example-extension",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,

View File

@ -37,7 +37,7 @@
"download:kubectl": "yarn run ts-node build/download_kubectl.ts", "download:kubectl": "yarn run ts-node build/download_kubectl.ts",
"download:helm": "yarn run ts-node build/download_helm.ts", "download:helm": "yarn run ts-node build/download_helm.ts",
"build:tray-icons": "yarn run ts-node build/build_tray_icon.ts", "build:tray-icons": "yarn run ts-node build/build_tray_icon.ts",
"lint": "yarn run eslint $@ --ext js,ts,tsx --max-warnings=0 src/ integration/ __mocks__/ build/ extensions/", "lint": "yarn run eslint $@ --ext js,ts,tsx --max-warnings=0 .",
"lint:fix": "yarn run lint --fix", "lint:fix": "yarn run lint --fix",
"mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest", "mkdocs-serve-local": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -it -p 8000:8000 -v ${PWD}:/docs mkdocs-serve-local:latest",
"verify-docs": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict", "verify-docs": "docker build -t mkdocs-serve-local:latest mkdocs/ && docker run --rm -v ${PWD}:/docs mkdocs-serve-local:latest build --strict",

View File

@ -9,7 +9,7 @@ jest.mock(
() => ({ () => ({
ipcRenderer: { ipcRenderer: {
invoke: jest.fn(async (channel: string) => { invoke: jest.fn(async (channel: string) => {
if (channel === "extensions:loaded") { if (channel === "extensions:main") {
return [ return [
[ [
manifestPath, manifestPath,
@ -44,7 +44,7 @@ jest.mock(
}), }),
on: jest.fn( on: jest.fn(
(channel: string, listener: (event: any, ...args: any[]) => void) => { (channel: string, listener: (event: any, ...args: any[]) => void) => {
if (channel === "extensions:loaded") { if (channel === "extensions:main") {
// First initialize with extensions 1 and 2 // First initialize with extensions 1 and 2
// and then broadcast event to remove extensioin 2 and add extension number 3 // and then broadcast event to remove extensioin 2 and add extension number 3
setTimeout(() => { setTimeout(() => {

View File

@ -1,5 +1,6 @@
import { app, ipcRenderer, remote } from "electron"; import { app, ipcRenderer, remote } from "electron";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { isEqual } from "lodash";
import { action, computed, observable, reaction, toJS, when } from "mobx"; import { action, computed, observable, reaction, toJS, when } from "mobx";
import path from "path"; import path from "path";
import { getHostedCluster } from "../common/cluster-store"; import { getHostedCluster } from "../common/cluster-store";
@ -25,7 +26,12 @@ const logModule = "[EXTENSIONS-LOADER]";
export class ExtensionLoader { export class ExtensionLoader {
protected extensions = observable.map<LensExtensionId, InstalledExtension>(); protected extensions = observable.map<LensExtensionId, InstalledExtension>();
protected instances = observable.map<LensExtensionId, LensExtension>(); protected instances = observable.map<LensExtensionId, LensExtension>();
protected readonly requestExtensionsChannel = "extensions:loaded";
// IPC channel to broadcast changes to extensions from main
protected static readonly extensionsMainChannel = "extensions:main";
// IPC channel to broadcast changes to extensions from renderer
protected static readonly extensionsRendererChannel = "extensions:renderer";
// emits event "remove" of type LensExtension when the extension is removed // emits event "remove" of type LensExtension when the extension is removed
private events = new EventEmitter(); private events = new EventEmitter();
@ -95,28 +101,27 @@ export class ExtensionLoader {
this.loadOnMain(); this.loadOnMain();
this.broadcastExtensions(); this.broadcastExtensions();
reaction(() => this.extensions.toJS(), () => { reaction(() => this.toJSON(), () => {
this.broadcastExtensions(); this.broadcastExtensions();
}); });
handleRequest(this.requestExtensionsChannel, () => { handleRequest(ExtensionLoader.extensionsMainChannel, () => {
return Array.from(this.toJSON()); return Array.from(this.toJSON());
}); });
subscribeToBroadcast(ExtensionLoader.extensionsRendererChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => {
this.syncExtensions(extensions);
});
} }
protected async initRenderer() { protected async initRenderer() {
const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => { const extensionListHandler = (extensions: [LensExtensionId, InstalledExtension][]) => {
this.isLoaded = true; this.isLoaded = true;
this.syncExtensions(extensions);
const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId); const receivedExtensionIds = extensions.map(([lensExtensionId]) => lensExtensionId);
// Add new extensions // Remove deleted extensions in renderer side only
extensions.forEach(([extId, ext]) => {
if (!this.extensions.has(extId)) {
this.extensions.set(extId, ext);
}
});
// Remove deleted extensions
this.extensions.forEach((_, lensExtensionId) => { this.extensions.forEach((_, lensExtensionId) => {
if (!receivedExtensionIds.includes(lensExtensionId)) { if (!receivedExtensionIds.includes(lensExtensionId)) {
this.removeExtension(lensExtensionId); this.removeExtension(lensExtensionId);
@ -124,14 +129,26 @@ export class ExtensionLoader {
}); });
}; };
requestMain(this.requestExtensionsChannel).then(extensionListHandler); reaction(() => this.toJSON(), () => {
subscribeToBroadcast(this.requestExtensionsChannel, (event, extensions: [LensExtensionId, InstalledExtension][]) => { this.broadcastExtensions(false);
});
requestMain(ExtensionLoader.extensionsMainChannel).then(extensionListHandler);
subscribeToBroadcast(ExtensionLoader.extensionsMainChannel, (_event, extensions: [LensExtensionId, InstalledExtension][]) => {
extensionListHandler(extensions); extensionListHandler(extensions);
}); });
} }
syncExtensions(extensions: [LensExtensionId, InstalledExtension][]) {
extensions.forEach(([lensExtensionId, extension]) => {
if (!isEqual(this.extensions.get(lensExtensionId), extension)) {
this.extensions.set(lensExtensionId, extension);
}
});
}
loadOnMain() { loadOnMain() {
logger.info(`${logModule}: load on main`); logger.debug(`${logModule}: load on main`);
this.autoInitExtensions(async (extension: LensMainExtension) => { this.autoInitExtensions(async (extension: LensMainExtension) => {
// Each .add returns a function to remove the item // Each .add returns a function to remove the item
const removeItems = [ const removeItems = [
@ -151,7 +168,7 @@ export class ExtensionLoader {
} }
loadOnClusterManagerRenderer() { loadOnClusterManagerRenderer() {
logger.info(`${logModule}: load on main renderer (cluster manager)`); logger.debug(`${logModule}: load on main renderer (cluster manager)`);
this.autoInitExtensions(async (extension: LensRendererExtension) => { this.autoInitExtensions(async (extension: LensRendererExtension) => {
const removeItems = [ const removeItems = [
registries.globalPageRegistry.add(extension.globalPages, extension), registries.globalPageRegistry.add(extension.globalPages, extension),
@ -174,7 +191,7 @@ export class ExtensionLoader {
} }
loadOnClusterRenderer() { loadOnClusterRenderer() {
logger.info(`${logModule}: load on cluster renderer (dashboard)`); logger.debug(`${logModule}: load on cluster renderer (dashboard)`);
const cluster = getHostedCluster(); const cluster = getHostedCluster();
this.autoInitExtensions(async (extension: LensRendererExtension) => { this.autoInitExtensions(async (extension: LensRendererExtension) => {
@ -204,26 +221,26 @@ export class ExtensionLoader {
protected autoInitExtensions(register: (ext: LensExtension) => Promise<Function[]>) { protected autoInitExtensions(register: (ext: LensExtension) => Promise<Function[]>) {
return reaction(() => this.toJSON(), installedExtensions => { return reaction(() => this.toJSON(), installedExtensions => {
for (const [extId, ext] of installedExtensions) { for (const [extId, extension] of installedExtensions) {
const alreadyInit = this.instances.has(extId); const alreadyInit = this.instances.has(extId);
if (ext.isEnabled && !alreadyInit) { if (extension.isEnabled && !alreadyInit) {
try { try {
const LensExtensionClass = this.requireExtension(ext); const LensExtensionClass = this.requireExtension(extension);
if (!LensExtensionClass) { if (!LensExtensionClass) {
continue; continue;
} }
const instance = new LensExtensionClass(ext); const instance = new LensExtensionClass(extension);
instance.whenEnabled(() => register(instance)); instance.whenEnabled(() => register(instance));
instance.enable(); instance.enable();
this.instances.set(extId, instance); this.instances.set(extId, instance);
} catch (err) { } catch (err) {
logger.error(`${logModule}: activation extension error`, { ext, err }); logger.error(`${logModule}: activation extension error`, { ext: extension, err });
} }
} else if (!ext.isEnabled && alreadyInit) { } else if (!extension.isEnabled && alreadyInit) {
this.removeInstance(extId); this.removeInstance(extId);
} }
} }
@ -262,8 +279,8 @@ export class ExtensionLoader {
}); });
} }
broadcastExtensions() { broadcastExtensions(main = true) {
broadcastMessage(this.requestExtensionsChannel, Array.from(this.toJSON())); broadcastMessage(main ? ExtensionLoader.extensionsMainChannel : ExtensionLoader.extensionsRendererChannel, Array.from(this.toJSON()));
} }
} }

View File

@ -45,17 +45,6 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
await extensionLoader.whenLoaded; await extensionLoader.whenLoaded;
await this.whenLoaded; await this.whenLoaded;
// apply state on changes from store
reaction(() => this.state.toJS(), extensionsState => {
extensionsState.forEach((state, extId) => {
const ext = extensionLoader.getExtension(extId);
if (ext && !ext.isBundled) {
ext.isEnabled = state.enabled;
}
});
});
// save state on change `extension.isEnabled` // save state on change `extension.isEnabled`
reaction(() => this.getState(extensionLoader), extensionsState => { reaction(() => this.getState(extensionLoader), extensionsState => {
this.state.merge(extensionsState); this.state.merge(extensionsState);
@ -65,7 +54,9 @@ export class ExtensionsStore extends BaseStore<LensExtensionsStoreModel> {
isEnabled(extId: LensExtensionId) { isEnabled(extId: LensExtensionId) {
const state = this.state.get(extId); const state = this.state.get(extId);
return state && state.enabled; // by default false // By default false, so that copied extensions are disabled by default.
// If user installs the extension from the UI, the Extensions component will specifically enable it.
return Boolean(state?.enabled);
} }
@action @action

View File

@ -62,6 +62,9 @@ export class Extensions extends React.Component {
@observable search = ""; @observable search = "";
@observable installPath = ""; @observable installPath = "";
// True if the preliminary install steps have started, but unpackExtension has not started yet
@observable startingInstall = false;
/** /**
* Extensions that were removed from extensions but are still in "uninstalling" state * Extensions that were removed from extensions but are still in "uninstalling" state
*/ */
@ -91,11 +94,20 @@ export class Extensions extends React.Component {
}); });
this.addedInstalling.forEach(({ id, displayName }) => { this.addedInstalling.forEach(({ id, displayName }) => {
const extension = this.extensions.find(extension => extension.id === id);
if (!extension) {
throw new Error("Extension not found");
}
Notifications.ok( Notifications.ok(
<p>Extension <b>{displayName}</b> successfully installed!</p> <p>Extension <b>{displayName}</b> successfully installed!</p>
); );
this.extensionState.delete(id); this.extensionState.delete(id);
this.installPath = ""; this.installPath = "";
// Enable installed extensions by default.
extension.isEnabled = true;
}); });
}) })
); );
@ -152,6 +164,8 @@ export class Extensions extends React.Component {
const { installPath } = this; const { installPath } = this;
if (!installPath) return; if (!installPath) return;
this.startingInstall = true;
const fileName = path.basename(installPath); const fileName = path.basename(installPath);
try { try {
@ -161,13 +175,14 @@ export class Extensions extends React.Component {
const { promise: filePromise } = downloadFile({ url: installPath, timeout: 60000 /*1m*/ }); const { promise: filePromise } = downloadFile({ url: installPath, timeout: 60000 /*1m*/ });
const data = await filePromise; const data = await filePromise;
this.requestInstall({ fileName, data }); await this.requestInstall({ fileName, data });
} }
// otherwise installing from system path // otherwise installing from system path
else if (InputValidators.isPath.validate(installPath)) { else if (InputValidators.isPath.validate(installPath)) {
this.requestInstall({ fileName, filePath: installPath }); await this.requestInstall({ fileName, filePath: installPath });
} }
} catch (error) { } catch (error) {
this.startingInstall = false;
Notifications.error( Notifications.error(
<p>Installation has failed: <b>{String(error)}</b></p> <p>Installation has failed: <b>{String(error)}</b></p>
); );
@ -186,11 +201,11 @@ export class Extensions extends React.Component {
}; };
async preloadExtensions(requests: InstallRequest[], { showError = true } = {}) { async preloadExtensions(requests: InstallRequest[], { showError = true } = {}) {
const preloadedRequests = requests.filter(req => req.data); const preloadedRequests = requests.filter(request => request.data);
await Promise.all( await Promise.all(
requests requests
.filter(req => !req.data && req.filePath) .filter(request => !request.data && request.filePath)
.map(async request => { .map(async request => {
try { try {
const data = await fse.readFile(request.filePath); const data = await fse.readFile(request.filePath);
@ -247,11 +262,11 @@ export class Extensions extends React.Component {
// copy files to temp // copy files to temp
await fse.ensureDir(this.getExtensionPackageTemp()); await fse.ensureDir(this.getExtensionPackageTemp());
requests.forEach(req => { for (const request of requests) {
const tempFile = this.getExtensionPackageTemp(req.fileName); const tempFile = this.getExtensionPackageTemp(request.fileName);
fse.writeFileSync(tempFile, req.data); await fse.writeFile(tempFile, request.data);
}); }
// validate packages // validate packages
await Promise.all( await Promise.all(
@ -289,15 +304,24 @@ export class Extensions extends React.Component {
const preloadedRequests = await this.preloadExtensions(requests); const preloadedRequests = await this.preloadExtensions(requests);
const validatedRequests = await this.createTempFilesAndValidate(preloadedRequests); const validatedRequests = await this.createTempFilesAndValidate(preloadedRequests);
validatedRequests.forEach(install => { // If there are no requests for installing, reset startingInstall state
if (validatedRequests.length === 0) {
this.startingInstall = false;
}
for (const install of validatedRequests) {
const { name, version, description } = install.manifest; const { name, version, description } = install.manifest;
const extensionFolder = this.getExtensionDestFolder(name); const extensionFolder = this.getExtensionDestFolder(name);
const folderExists = fse.existsSync(extensionFolder); const folderExists = await fse.pathExists(extensionFolder);
if (!folderExists) { if (!folderExists) {
// auto-install extension if not yet exists // auto-install extension if not yet exists
this.unpackExtension(install); this.unpackExtension(install);
} else { } else {
// If we show the confirmation dialog, we stop the install spinner until user clicks ok
// and the install continues
this.startingInstall = false;
// otherwise confirmation required (re-install / update) // otherwise confirmation required (re-install / update)
const removeNotification = Notifications.info( const removeNotification = Notifications.info(
<div className="InstallingExtensionNotification flex gaps align-center"> <div className="InstallingExtensionNotification flex gaps align-center">
@ -315,21 +339,23 @@ export class Extensions extends React.Component {
</div> </div>
); );
} }
}); }
} }
async unpackExtension({ fileName, tempFile, manifest: { name, version } }: InstallRequestValidated) { async unpackExtension({ fileName, tempFile, manifest: { name, version } }: InstallRequestValidated) {
const displayName = extensionDisplayName(name, version); const displayName = extensionDisplayName(name, version);
const extensionFolder = this.getExtensionDestFolder(name);
const unpackingTempFolder = path.join(path.dirname(tempFile), `${path.basename(tempFile)}-unpacked`);
const extensionId = path.join(extensionDiscovery.nodeModulesPath, name, "package.json"); const extensionId = path.join(extensionDiscovery.nodeModulesPath, name, "package.json");
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
this.extensionState.set(extensionId, { this.extensionState.set(extensionId, {
state: "installing", state: "installing",
displayName displayName
}); });
this.startingInstall = false;
const extensionFolder = this.getExtensionDestFolder(name);
const unpackingTempFolder = path.join(path.dirname(tempFile), `${path.basename(tempFile)}-unpacked`);
logger.info(`Unpacking extension ${displayName}`, { fileName, tempFile });
try { try {
// extract to temp folder first // extract to temp folder first
@ -455,7 +481,7 @@ export class Extensions extends React.Component {
* True if at least one extension is in installing state * True if at least one extension is in installing state
*/ */
@computed get isInstalling() { @computed get isInstalling() {
return [...this.extensionState.values()].some(extension => extension.state === "installing"); return this.startingInstall || [...this.extensionState.values()].some(extension => extension.state === "installing");
} }
render() { render() {

View File

@ -0,0 +1,52 @@
import React from "react";
import { render } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import { BottomBar } from "./bottom-bar";
jest.mock("../../../extensions/registries");
import { statusBarRegistry } from "../../../extensions/registries";
describe("<BottomBar />", () => {
it("renders w/o errors", () => {
const { container } = render(<BottomBar />);
expect(container).toBeInstanceOf(HTMLElement);
});
// some defensive testing
it("renders w/o errors when .getItems() returns edge cases", async () => {
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => undefined);
expect(() => render(<BottomBar />)).not.toThrow();
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => null);
expect(() => render(<BottomBar />)).not.toThrow();
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => []);
expect(() => render(<BottomBar />)).not.toThrow();
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => { return {};});
expect(() => render(<BottomBar />)).not.toThrow();
});
it("renders items [{item: React.ReactNode}] (4.0.0-rc.1)", async () => {
const testId = "testId";
const text = "heee";
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => [
{ item: <span data-testid={testId} >{text}</span> }
]);
const { getByTestId } = render(<BottomBar />);
expect(await getByTestId(testId)).toHaveTextContent(text);
});
it("renders items [{item: () => React.ReactNode}] (4.0.0-rc.1+)", async () => {
const testId = "testId";
const text = "heee";
statusBarRegistry.getItems = jest.fn().mockImplementationOnce(() => [
{ item: () => <span data-testid={testId} >{text}</span> }
]);
const { getByTestId } = render(<BottomBar />);
expect(await getByTestId(testId)).toHaveTextContent(text);
});
});

View File

@ -11,6 +11,8 @@ import { statusBarRegistry } from "../../../extensions/registries";
export class BottomBar extends React.Component { export class BottomBar extends React.Component {
render() { render() {
const { currentWorkspace } = workspaceStore; const { currentWorkspace } = workspaceStore;
// in case .getItems() returns undefined
const items = statusBarRegistry.getItems() ?? [];
return ( return (
<div className="BottomBar flex gaps"> <div className="BottomBar flex gaps">
@ -22,10 +24,17 @@ export class BottomBar extends React.Component {
htmlFor="current-workspace" htmlFor="current-workspace"
/> />
<div className="extensions box grow flex gaps justify-flex-end"> <div className="extensions box grow flex gaps justify-flex-end">
{statusBarRegistry.getItems().map(({ item }, index) => { {Array.isArray(items) && items.map(({ item }, index) => {
if (!item) return; if (!item) return;
return <div className="flex align-center gaps item" key={index}>{item}</div>; return (
<div
className="flex align-center gaps item"
key={index}
>
{typeof item === "function" ? item() : item}
</div>
);
})} })}
</div> </div>
</div> </div>

2
types/dom.d.ts vendored
View File

@ -1,4 +1,4 @@
export {} export {};
declare global { declare global {
interface Element { interface Element {

10
types/font-face.d.ts vendored
View File

@ -1,6 +1,6 @@
// https://www.w3.org/TR/css-font-loading/ // https://www.w3.org/TR/css-font-loading/
// https://developer.mozilla.org/en-US/docs/Web/API/FontFace // https://developer.mozilla.org/en-US/docs/Web/API/FontFace
export {} export {};
declare global { declare global {
const FontFace: FontFace; const FontFace: FontFace;
@ -10,11 +10,11 @@ declare global {
} }
type CSSOMString = string; type CSSOMString = string;
type FontFaceLoadStatus = 'unloaded' | 'loading' | 'loaded' | 'error'; type FontFaceLoadStatus = "unloaded" | "loading" | "loaded" | "error";
type FontFaceSetStatus = 'loading' | 'loaded'; type FontFaceSetStatus = "loading" | "loaded";
interface FontFace extends FontFaceDescriptors { class FontFace implements FontFaceDescriptors {
new(family: string, source: string | ArrayBuffer, descriptors?: FontFaceDescriptors): FontFace; constructor(family: string, source: string | ArrayBuffer, descriptors?: FontFaceDescriptors);
readonly status: FontFaceLoadStatus; readonly status: FontFaceLoadStatus;
readonly loaded: Promise<FontFace>; readonly loaded: Promise<FontFace>;
variationSettings: CSSOMString; variationSettings: CSSOMString;

View File

@ -1,87 +1,89 @@
import path from 'path'; import path from "path";
import webpack from "webpack"; import webpack from "webpack";
import { sassCommonVars } from "./src/common/vars"; import { sassCommonVars } from "./src/common/vars";
export default function (): webpack.Configuration { export default function (): webpack.Configuration {
const entry = "./src/extensions/extension-api.ts" const entry = "./src/extensions/extension-api.ts";
const outDir = "./src/extensions/npm/extensions/dist"; const outDir = "./src/extensions/npm/extensions/dist";
return { return {
// Compile for Electron for renderer process // Compile for Electron for renderer process
// see <https://webpack.js.org/configuration/target/> // see <https://webpack.js.org/configuration/target/>
target: "electron-renderer", target: "electron-renderer",
entry, entry,
output: { // this is the default mode, so we should make it explicit to silence the warning
filename: 'extension-api.js', mode: "production",
// need to be an absolute path output: {
path: path.resolve(__dirname, `${outDir}/src/extensions`), filename: "extension-api.js",
// can be use in commonjs environments // need to be an absolute path
// e.g. require('@k8slens/extensions') path: path.resolve(__dirname, `${outDir}/src/extensions`),
libraryTarget: "commonjs" // can be use in commonjs environments
// e.g. require('@k8slens/extensions')
libraryTarget: "commonjs"
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "ts-loader",
options: {
// !! ts-loader will use tsconfig.json at folder root
// !! changes in tsconfig.json may have side effects
// !! on '@k8slens/extensions' module
compilerOptions: {
declaration: true, // output .d.ts
sourceMap: false, // to override sourceMap: true in tsconfig.json
outDir // where the .d.ts should be located
}
}
}, },
module: { // for src/renderer/components/fonts/roboto-mono-nerd.ttf
rules: [ // in src/renderer/components/dock/terminal.ts 95:25-65
{ {
test: /\.tsx?$/, test: /\.(ttf|eot|woff2?)$/,
loader: 'ts-loader', use: {
options: { loader: "url-loader",
// !! ts-loader will use tsconfig.json at folder root options: {
// !! changes in tsconfig.json may have side effects name: "fonts/[name].[ext]"
// !! on '@k8slens/extensions' module }
compilerOptions: { }
declaration: true, // output .d.ts },
sourceMap: false, // to override sourceMap: true in tsconfig.json // for import scss files
outDir // where the .d.ts should be located {
} test: /\.s?css$/,
} use: [
// creates `style` nodes from JS strings
"style-loader",
// translates CSS into CommonJS
"css-loader",
{
loader: "sass-loader",
options: {
prependData: `@import "${path.basename(sassCommonVars)}";`,
sassOptions: {
includePaths: [
path.dirname(sassCommonVars)
]
}, },
// for src/renderer/components/fonts/roboto-mono-nerd.ttf }
// in src/renderer/components/dock/terminal.ts 95:25-65 },
{ ]
test: /\.(ttf|eot|woff2?)$/, }
use: { ]
loader: "url-loader", },
options: { resolve: {
name: "fonts/[name].[ext]" extensions: [".ts", ".tsx", ".js"]
} },
} plugins: [
}, // In ts-loader's README they said to output a built .d.ts file,
// for import scss files // you can set "declaration": true in tsconfig.extensions.json,
{ // and use the DeclarationBundlerPlugin in your webpack config... but
test: /\.s?css$/, // !! the DeclarationBundlerPlugin doesn't work anymore, author archived it.
use: [ // https://www.npmjs.com/package/declaration-bundler-webpack-plugin
// creates `style` nodes from JS strings // new DeclarationBundlerPlugin({
"style-loader", // moduleName: '@k8slens/extensions',
// translates CSS into CommonJS // out: 'extension-api.d.ts',
"css-loader", // })
{ ]
loader: "sass-loader", };
options: {
prependData: `@import "${path.basename(sassCommonVars)}";`,
sassOptions: {
includePaths: [
path.dirname(sassCommonVars)
]
},
}
},
]
}
]
},
resolve: {
extensions: ['.ts', '.tsx', '.js']
},
plugins: [
// In ts-loader's README they said to output a built .d.ts file,
// you can set "declaration": true in tsconfig.extensions.json,
// and use the DeclarationBundlerPlugin in your webpack config... but
// !! the DeclarationBundlerPlugin doesn't work anymore, author archived it.
// https://www.npmjs.com/package/declaration-bundler-webpack-plugin
// new DeclarationBundlerPlugin({
// moduleName: '@k8slens/extensions',
// out: 'extension-api.d.ts',
// })
]
};
} }

View File

@ -1,12 +1,13 @@
import path from "path"; import path from "path";
import webpack from "webpack"; import webpack from "webpack";
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin" import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars"; import { isDevelopment, isProduction, mainDir, buildDir } from "./src/common/vars";
import nodeExternals from "webpack-node-externals"; import nodeExternals from "webpack-node-externals";
import ProgressBarPlugin from "progress-bar-webpack-plugin"; import ProgressBarPlugin from "progress-bar-webpack-plugin";
import * as vars from "./src/common/vars";
export default function (): webpack.Configuration { export default function (): webpack.Configuration {
console.info('WEBPACK:main', require("./src/common/vars")) console.info("WEBPACK:main", vars);
return { return {
context: __dirname, context: __dirname,
target: "electron-main", target: "electron-main",
@ -21,7 +22,7 @@ export default function (): webpack.Configuration {
path: buildDir, path: buildDir,
}, },
resolve: { resolve: {
extensions: ['.json', '.js', '.ts'] extensions: [".json", ".js", ".ts"]
}, },
externals: [ externals: [
nodeExternals() nodeExternals()
@ -48,5 +49,5 @@ export default function (): webpack.Configuration {
new ProgressBarPlugin(), new ProgressBarPlugin(),
new ForkTsCheckerPlugin(), new ForkTsCheckerPlugin(),
].filter(Boolean) ].filter(Boolean)
} };
} }

View File

@ -4,17 +4,18 @@ import webpack from "webpack";
import HtmlWebpackPlugin from "html-webpack-plugin"; import HtmlWebpackPlugin from "html-webpack-plugin";
import MiniCssExtractPlugin from "mini-css-extract-plugin"; import MiniCssExtractPlugin from "mini-css-extract-plugin";
import TerserPlugin from "terser-webpack-plugin"; import TerserPlugin from "terser-webpack-plugin";
import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin" import ForkTsCheckerPlugin from "fork-ts-checker-webpack-plugin";
import ProgressBarPlugin from "progress-bar-webpack-plugin"; import ProgressBarPlugin from "progress-bar-webpack-plugin";
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin' import ReactRefreshWebpackPlugin from "@pmmmwh/react-refresh-webpack-plugin";
import * as vars from "./src/common/vars";
export default [ export default [
webpackLensRenderer webpackLensRenderer
] ];
export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration { export function webpackLensRenderer({ showVars = true } = {}): webpack.Configuration {
if (showVars) { if (showVars) {
console.info('WEBPACK:renderer', require("./src/common/vars")); console.info("WEBPACK:renderer", vars);
} }
return { return {
context: __dirname, context: __dirname,
@ -27,7 +28,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
hot: true, hot: true,
// to avoid cors errors when requests is from iframes // to avoid cors errors when requests is from iframes
disableHostCheck: true, disableHostCheck: true,
headers: { 'Access-Control-Allow-Origin': '*' }, headers: { "Access-Control-Allow-Origin": "*" },
}, },
name: "lens-app", name: "lens-app",
mode: isProduction ? "production" : "development", mode: isProduction ? "production" : "development",
@ -39,10 +40,10 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
libraryTarget: "global", libraryTarget: "global",
library: "", library: "",
globalObject: "this", globalObject: "this",
publicPath: publicPath, publicPath,
path: buildDir, path: buildDir,
filename: '[name].js', filename: "[name].js",
chunkFilename: 'chunks/[name].js', chunkFilename: "chunks/[name].js",
}, },
stats: { stats: {
warningsFilter: [ warningsFilter: [
@ -51,8 +52,8 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
}, },
resolve: { resolve: {
extensions: [ extensions: [
'.js', '.jsx', '.json', ".js", ".jsx", ".json",
'.ts', '.tsx', ".ts", ".tsx",
] ]
}, },
optimization: { optimization: {
@ -91,7 +92,7 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
}], }],
], ],
plugins: [ plugins: [
isDevelopment && require.resolve('react-refresh/babel'), isDevelopment && require.resolve("react-refresh/babel"),
].filter(Boolean), ].filter(Boolean),
} }
}, },
@ -190,5 +191,5 @@ export function webpackLensRenderer({ showVars = true } = {}): webpack.Configura
isDevelopment && new ReactRefreshWebpackPlugin(), isDevelopment && new ReactRefreshWebpackPlugin(),
].filter(Boolean), ].filter(Boolean),
} };
} }