Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@gondel/plugin-react

Package Overview
Dependencies
Maintainers
4
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@gondel/plugin-react - npm Package Compare versions

Comparing version 1.1.2 to 1.2.0

11

CHANGELOG.md

@@ -6,2 +6,13 @@ # Change Log

# [1.2.0](https://github.com/namics/gondel/compare/v1.1.2...v1.2.0) (2020-02-19)
### Features
* **react plugin:** provide a factory function to connect Gondel and React ([c6ac867](https://github.com/namics/gondel/commit/c6ac867ad9841f09d90dda18a9fbb77fb83f6dce))
## [1.1.2](https://github.com/namics/gondel/compare/v1.1.1...v1.1.2) (2020-01-16)

@@ -8,0 +19,0 @@

6

dist/AppWrapper.d.ts

@@ -1,2 +0,2 @@

import React, { Component } from "react";
import { Component } from "react";
export interface Props<S> extends React.ComponentLifecycle<null, S> {

@@ -9,4 +9,4 @@ children?: (props: S) => JSX.Element;

constructor(props: Props<TConfig>);
render(): JSX.Element | (((props: TConfig) => JSX.Element) & string) | (((props: TConfig) => JSX.Element) & number) | (((props: TConfig) => JSX.Element) & false) | (((props: TConfig) => JSX.Element) & true) | (((props: TConfig) => JSX.Element) & React.ReactNodeArray) | undefined;
render(): JSX.Element | (((props: TConfig) => JSX.Element) & string) | (((props: TConfig) => JSX.Element) & number) | (((props: TConfig) => JSX.Element) & false) | (((props: TConfig) => JSX.Element) & true) | (((props: TConfig) => JSX.Element) & import("react").ReactNodeArray) | undefined;
}
export declare function createRenderableAppWrapper<TConfig>(props: Props<TConfig>): JSX.Element;
export declare function createRenderableAppWrapper<TConfig>(props: Props<TConfig>): import("react").CElement<Props<unknown>, AppWrapper<unknown>>;

@@ -14,14 +14,3 @@ var __extends = (this && this.__extends) || (function () {

})();
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
import React, { Component } from "react";
import { Component, createElement } from "react";
var AppWrapper = /** @class */ (function (_super) {

@@ -47,3 +36,3 @@ __extends(AppWrapper, _super);

_this[reactHook] = function () {
this.props[reactHook].apply(this, arguments);
return this.props[reactHook].apply(this, arguments);
};

@@ -63,4 +52,4 @@ });

export function createRenderableAppWrapper(props) {
return React.createElement(AppWrapper, __assign({}, props));
return createElement(AppWrapper, props);
}
//# sourceMappingURL=AppWrapper.js.map
import React, { StatelessComponent, ComponentClass, ComponentLifecycle } from "react";
import { GondelBaseComponent } from "@gondel/core";
import { KeysMatching, UnwrapPromise } from "./utils";
declare type RenderableReactComponent<State> = StatelessComponent<State> | ComponentClass<State, any>;
export declare class GondelReactComponent<State = {}, TElement extends HTMLElement = HTMLDivElement> extends GondelBaseComponent<TElement> implements ComponentLifecycle<null, State> {
static readonly AppPromiseMap: WeakMap<Promise<React.ComponentType<any>>, React.ComponentType<any>>;
declare type StateOfComponent<T> = T extends RenderableReactComponent<infer V> ? V : never;
interface ConstructableGondelReactComponent<State> {
new (...args: any[]): GondelReactComponent<State>;
}
/**
* Create a GondelReactComponent class which is directly linked with a loader
* for default exports
*
* @example
* const loader = () => import('./App');
* class MyComponent extends GondelReactComponent.create(loader) {
* }
*
*/
export declare function createGondelReactLoader<State extends {}>(loader: () => Promise<RenderableReactComponent<State>>): ConstructableGondelReactComponent<State>;
/**
* Create a GondelReactComponent class which is directly linked with a loader
*
* @example
* const loader = () => <span>Hello world</span>;
* class MyComponent extends GondelReactComponent.create(loader) {
* }
*
*/
export declare function createGondelReactLoader<State extends {}>(loader: () => RenderableReactComponent<State>): ConstructableGondelReactComponent<State>;
/**
* Create a GondelReactComponent class which is directly linked with a loader
* for named exports like `export const App = () => <span>Hello world</span>`
*
* @example
* const loader = () => import('./App');
* class MyComponent extends GondelReactComponent.create(loader, "App") {
* }
*
*/
export declare function createGondelReactLoader<State extends StateOfComponent<UnwrapPromise<Module>[ExportName]>, Module extends Promise<{
[key: string]: unknown;
}>, ExportName extends KeysMatching<UnwrapPromise<Module>, RenderableReactComponent<any>>>(loader: () => Module, exportName: ExportName): ConstructableGondelReactComponent<State>;
export declare class GondelReactComponent<State extends {} = {}, TElement extends HTMLElement = HTMLDivElement> extends GondelBaseComponent<TElement> implements ComponentLifecycle<null, State> {
static readonly AppPromiseMap: WeakMap<Promise<RenderableReactComponent<any>>, RenderableReactComponent<any>>;
/**
* Create a GondelReactComponent class which is directly linked with a loader
*/
static create: typeof createGondelReactLoader;
_setInternalState: (config: State) => void | undefined;

@@ -7,0 +50,0 @@ App?: RenderableReactComponent<State> | Promise<RenderableReactComponent<State>>;

@@ -14,6 +14,36 @@ var __extends = (this && this.__extends) || (function () {

})();
import React from "react";
import { createElement } from "react";
import { GondelBaseComponent } from "@gondel/core";
import { createRenderableAppWrapper } from "./AppWrapper";
import { isPromise } from "./utils";
export function createGondelReactLoader(loader, exportName) {
var unifiedLoader = function () {
var loaderResult = loader();
if (!isPromise(loaderResult)) {
return loaderResult;
}
return loaderResult.then(function (lazyLoadModule) {
if (exportName) {
var mod = lazyLoadModule;
/* istanbul ignore else */
if (mod[exportName]) {
return mod[exportName];
}
else {
throw new Error("export " + exportName + " not found");
}
}
return lazyLoadModule;
});
};
return /** @class */ (function (_super) {
__extends(GondelReactWrapperComponent, _super);
function GondelReactWrapperComponent() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.App = unifiedLoader();
return _this;
}
return GondelReactWrapperComponent;
}(GondelReactComponent));
}
var GondelReactComponent = /** @class */ (function (_super) {

@@ -26,5 +56,6 @@ __extends(GondelReactComponent, _super);

var ReactDOMPromise = import(
/* webpackPrefetch: true, webpackChunkName: 'ReactDom' */ "react-dom").then(function (ReactDOM) { return ReactDOM.default; });
/* webpackPrefetch: true, webpackChunkName: 'ReactDom' */ "react-dom").then(function (ReactDOM) { return ReactDOM.default || ReactDOM; });
var configScript = ctx.querySelector("script[type='text/json']");
_this.state = configScript ? JSON.parse(configScript.innerHTML) : {};
var unmountComponentAtNode;
_this.start = function () {

@@ -48,2 +79,4 @@ var _this = this;

var ReactDOM = _a[0], App = _a[1];
// Store unmountComponentAtNode for stopping the app
unmountComponentAtNode = ReactDOM.unmountComponentAtNode;
// Store unwrapped promise for this.App

@@ -61,4 +94,8 @@ if (App && isPromise(_this.App)) {

componentWillUnmount: function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
delete _this._setInternalState;
_this.componentWillUnmount && _this.componentWillUnmount();
_this.componentWillUnmount && _this.componentWillUnmount.apply(_this, args);
},

@@ -76,2 +113,13 @@ componentDidMount: _this.componentDidMount && _this.componentDidMount.bind(_this),

};
// Make sure that the stop method will tear down the react app
var originalStop = _this.stop;
_this.stop = function () {
var returnValue = originalStop && originalStop.apply(this, arguments);
// check if during this components start method unmountComponentAtNode
// was set - if not we don't need to unmound the app
if (unmountComponentAtNode) {
unmountComponentAtNode(this._ctx);
}
return returnValue;
};
return _this;

@@ -95,5 +143,9 @@ }

}
return React.createElement(App, this.state);
return createElement(App, this.state);
};
GondelReactComponent.AppPromiseMap = new WeakMap();
/**
* Create a GondelReactComponent class which is directly linked with a loader
*/
GondelReactComponent.create = createGondelReactLoader;
return GondelReactComponent;

@@ -100,0 +152,0 @@ }(GondelBaseComponent));

@@ -5,1 +5,12 @@ /**

export declare function isPromise<T>(obj: {} | Promise<T> | undefined | string | number | null): obj is Promise<T>;
/**
* Extracts all key from a type T which are assignable to V
* @see https://stackoverflow.com/a/54520829/159319
*/
export declare type KeysMatching<T, V> = {
[K in keyof T]: T[K] extends V ? K : never;
}[keyof T];
/**
* Returns the resolved Promise value type
*/
export declare type UnwrapPromise<T> = T extends Promise<infer V> ? V : never;
{
"name": "@gondel/plugin-react",
"version": "1.1.2",
"version": "1.2.0",
"description": "Gondel Plugin to boot react widgets and apps",

@@ -29,3 +29,3 @@ "bugs": "https://github.com/namics/gondel/issues",

"devDependencies": {
"@gondel/core": "^1.1.0",
"@gondel/core": "^1.2.0",
"jest": "24.9.0",

@@ -60,4 +60,3 @@ "npm-run-all": "4.1.5",

]
},
"gitHead": "40d0471a610ffa353af7956bac7a9d53e847a419"
}
}

@@ -114,24 +114,12 @@ # React Plugin

const loader = () => import('./App');
@Component('DemoWidget')
export class DemoWidget extends GondelReactComponent {
App = import('./App').then(({ App }) => App);
export class DemoWidget extends GondelReactComponent.create(loader, "App") {
}
```
### Async blocking linking example (code splitting)
To use a react App with a default export the second parameter of `create` can be skipped.
You can use a async start method to lazy load a component and tell Gondel to wait until the JavaScript of the component has been loaded.
This will guarantee that the sync method of all Gondel components will be delayed until the React component was completely loaded.
**HTML**
```html
<div data-g-name="DemoWidget">
<script type="text/json">{ "foo":"bar" }</script>
Loading..
</div>
```
**JavaScript**
```js

@@ -141,11 +129,10 @@ import { GondelReactComponent } from '@gondel/plugin-react';

const loader = () => import('./App');
@Component('DemoWidget')
export class DemoWidget extends GondelReactComponent {
async start() {
this.App = (await import('./App')).App;
}
export class DemoWidget extends GondelReactComponent.create(loader) {
}
```
## Manipulating state

@@ -171,3 +158,3 @@

const DemoApp = ({ theme }) => (
const DemoApp = ({ theme }: {theme: 'light' | 'dark'}) => (
<h1 className={theme === 'dark' ? 'dark' : 'light'}>

@@ -179,5 +166,3 @@ Hello World

@Component('DemoWidget')
export class DemoWidget extends GondelReactComponent<{theme: 'light' | 'dark'}> {
App = DemoApp;
export class DemoWidget extends GondelReactComponent.create(() => DemoApp) {
setTheme(theme: 'light' | 'dark') {

@@ -184,0 +169,0 @@ this.setState({ theme });

@@ -1,5 +0,4 @@

import { Component } from "@gondel/core";
import { TestApp } from "../fixtures/TestApp";
import { GondelReactComponent } from "./GondelReactComponent";
import { isPromise } from "./utils";
import { Component, getComponentByDomNode, startComponents } from "@gondel/core";
import { createElement } from "react";
import { createGondelReactLoader, GondelReactComponent } from "./GondelReactComponent";

@@ -32,20 +31,2 @@ const createComponentStateHTML = (initialState: object = {}) => {

it("should not expose certain react lifecycle methods", () => {
class TestComponent extends GondelReactComponent {
_componentName = "TestComponent";
}
const root = document.createElement("div");
const c = new TestComponent(root, "stub");
expect(c.componentWillMount).toBeUndefined();
expect(c.componentDidMount).toBeUndefined();
expect(c.componentWillReceiveProps).toBeUndefined();
expect(c.shouldComponentUpdate).toBeUndefined();
expect(c.componentWillUpdate).toBeUndefined();
expect(c.componentDidUpdate).toBeUndefined();
expect(c.componentWillUnmount).toBeUndefined();
expect(c.componentDidCatch).toBeUndefined();
});
it("should read child script config", () => {

@@ -140,20 +121,196 @@ const root = createComponentStateHTML({ theme: "light", loaded: true });

describe("render", () => {
// TODO: This test fails, strange behaviour, we need to investigate a bit here
it.skip("should be able to render React apps", async () => {
@Component("test")
class TestComponent extends GondelReactComponent {
App = TestApp;
}
it("should be able to render React apps syncronously", async () => {
const root = document.createElement("div");
const component = new TestComponent(root, "test");
component.setState({ a: 1 });
expect(typeof (component as any).start).toBeTruthy();
const startPromise = (component as any).start() as Promise<any>;
expect(isPromise(startPromise)).toBeTruthy();
const startResponse = await startPromise;
// const out = await (c as any).start();
// expect(typeof (c as any).start === 'function').toBeTruthy()
// expect(c.render()).toEqual('')
root.innerHTML = `<div data-g-name="Greeter"><script type="text/json">{ "title": "Hello World"}</script></div>`;
await new Promise(resolve => {
function TestTitleSpan(props: { title: string }) {
return createElement("span", null, props.title);
}
const loader = () => TestTitleSpan;
const GondelReactLoaderComponent = createGondelReactLoader(loader);
@Component("Greeter")
class Greeter extends GondelReactLoaderComponent {
start() {}
componentDidMount() {
resolve();
}
}
startComponents(root);
});
expect(root.innerHTML).toBe('<div data-g-name="Greeter"><span>Hello World</span></div>');
});
it("should be able to render React apps asyncronously", async () => {
const root = document.createElement("div");
root.innerHTML = `<div data-g-name="Greeter"><script type="text/json">{ "title": "Hello World"}</script></div>`;
await new Promise(resolve => {
function TestTitleSpan(props: { title: string }) {
return createElement("span", null, props.title);
}
const loader = async () => TestTitleSpan;
const GondelReactLoaderComponent = createGondelReactLoader(loader);
@Component("Greeter")
class Greeter extends GondelReactLoaderComponent {
componentDidMount() {
resolve();
}
}
startComponents(root);
});
expect(root.innerHTML).toBe('<div data-g-name="Greeter"><span>Hello World</span></div>');
});
it("should be able to render React apps named asyncronously", async () => {
const root = document.createElement("div");
root.innerHTML = `<div data-g-name="Greeter"><script type="text/json">{ "title": "Hello World"}</script></div>`;
await new Promise(resolve => {
function TestTitleSpan(props: { title: string }) {
return createElement("span", null, props.title);
}
const loader = async () => ({ TestTitleSpan } as const);
const GondelReactLoaderComponent = createGondelReactLoader(loader, "TestTitleSpan");
@Component("Greeter")
class Greeter extends GondelReactLoaderComponent {
componentDidMount() {
resolve();
}
}
startComponents(root);
});
expect(root.innerHTML).toBe('<div data-g-name="Greeter"><span>Hello World</span></div>');
});
it("should execute hooks during rendering", async () => {
const root = document.createElement("div");
root.innerHTML = `<div data-g-name="Greeter"></div>`;
const hooks: string[] = [];
await new Promise(resolve => {
const loader = () => () => createElement("span", null, "Hello World");
const GondelReactLoaderComponent = createGondelReactLoader(loader);
@Component("Greeter")
class Greeter extends GondelReactLoaderComponent {
componentDidMount() {
hooks.push("componentDidMount");
setTimeout(() => {
this.stop();
});
}
componentWillUnmount() {
hooks.push("componentWillUnmount");
resolve();
}
}
startComponents(root);
});
expect(hooks).toEqual(["componentDidMount", "componentWillUnmount"]);
});
it("should render after the start method is done", async () => {
const root = document.createElement("div");
root.innerHTML = `<div data-g-name="Greeter"></div>`;
await new Promise(resolve => {
function TestTitleSpan(props: { title: string }) {
return createElement("span", null, props.title);
}
const loader = () => TestTitleSpan;
const GondelReactLoaderComponent = createGondelReactLoader(loader);
@Component("Greeter")
class Greeter extends GondelReactLoaderComponent {
start() {
return new Promise(resolve => {
setTimeout(() => {
this.setState({
title: "Lazy loaded data"
});
resolve();
});
});
}
componentDidMount() {
resolve();
}
}
startComponents(root);
});
expect(root.innerHTML).toBe(
'<div data-g-name="Greeter"><span>Lazy loaded data</span></div>'
);
});
it("should render after the start method is done using a callback", async () => {
const root = document.createElement("div");
root.innerHTML = `<div data-g-name="Greeter"></div>`;
await new Promise(resolve => {
function TestTitleSpan(props: { title: string }) {
return createElement("span", null, props.title);
}
const loader = () => TestTitleSpan;
const GondelReactLoaderComponent = createGondelReactLoader(loader);
@Component("Greeter")
class Greeter extends GondelReactLoaderComponent {
start(resolve: () => void) {
setTimeout(() => {
this.setState({
title: "Lazy loaded data"
});
resolve();
});
}
componentDidMount() {
resolve();
}
}
startComponents(root);
});
expect(root.innerHTML).toBe(
'<div data-g-name="Greeter"><span>Lazy loaded data</span></div>'
);
});
it("should rerender once setState is called", async () => {
const root = document.createElement("div");
root.innerHTML = `<div data-g-name="Greeter"></div>`;
await new Promise(resolve => {
function TestTitleSpan(props: { title: string }) {
return createElement("span", null, props.title || "");
}
const GondelReactLoaderComponent = createGondelReactLoader(() => TestTitleSpan);
@Component("Greeter")
class Greeter extends GondelReactLoaderComponent {
componentDidMount() {
resolve();
}
componentDidUpdate() {}
shouldComponentUpdate() {
return true;
}
}
startComponents(root);
});
const component = getComponentByDomNode<any>(root.firstElementChild!);
component.setState({ title: "update using getComponentByDomNode" });
expect(root.innerHTML).toBe(
'<div data-g-name="Greeter"><span>update using getComponentByDomNode</span></div>'
);
});
it("base class should throw an error if no app provided", () => {

@@ -160,0 +317,0 @@ const root = document.createElement("div");

@@ -9,1 +9,12 @@ /**

}
/**
* Extracts all key from a type T which are assignable to V
* @see https://stackoverflow.com/a/54520829/159319
*/
export type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
/**
* Returns the resolved Promise value type
*/
export type UnwrapPromise<T> = T extends Promise<infer V> ? V : never;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc