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

forgo

Package Overview
Dependencies
Maintainers
1
Versions
140
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

forgo - npm Package Compare versions

Comparing version 0.0.5 to 0.0.6

.cache/7c/c5b3231b5348fa776a10009f576800.json

8

dist/index.d.ts

@@ -18,4 +18,3 @@ export declare type ForgoRef<T> = {

render: (props: TProps, args: ForgoRenderArgs) => ForgoElement<ForgoComponentCtor<TProps>, TProps>;
load?: () => void;
unload?: () => void;
unmount?: () => void;
};

@@ -39,3 +38,8 @@ export declare type ForgoElement<TType extends string | ForgoComponentCtor<TProps>, TProps extends ForgoElementProps> = {

};
export declare type EnvType = {
window: Window | typeof globalThis;
document: HTMLDocument;
};
export declare function setCustomEnv(value: any): void;
export declare function mount(forgoNode: ForgoNode, parentElement: HTMLElement | null): void;
export declare function rerender(element: ForgoElementArg | undefined): void;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.rerender = exports.mount = void 0;
exports.rerender = exports.mount = exports.setCustomEnv = void 0;
/*

@@ -11,2 +11,12 @@ The element types we care about.

const TEXT_NODE_TYPE = 3;
const documentObject = globalThis ? globalThis.document : document;
const windowObject = globalThis ? globalThis : window;
let env = {
window: windowObject,
document: documentObject,
};
function setCustomEnv(value) {
env = value;
}
exports.setCustomEnv = setCustomEnv;
/*

@@ -49,6 +59,13 @@ This is the main render function.

function renderString(text, node, pendingAttachStates) {
var _a;
// Text nodes will always be recreated
const textNode = document.createTextNode(text);
const textNode = env.document.createTextNode(text);
attachProps(text, textNode, pendingAttachStates);
if (node) {
// If there are old component states, we might need to unmount some of em.
// After comparing with the new states.
const oldComponentStates = (_a = getForgoState(node)) === null || _a === void 0 ? void 0 : _a.components;
if (oldComponentStates) {
unloadIncompatibleStates(pendingAttachStates, oldComponentStates);
}
node.replaceWith(textNode);

@@ -71,4 +88,11 @@ }

function renderDOMElement(forgoElement, node, pendingAttachStates) {
var _a;
if (node) {
let nodeToBindTo;
// If there are old component states, we might need to unmount some of em.
// After comparing with the new states.
const oldComponentStates = (_a = getForgoState(node)) === null || _a === void 0 ? void 0 : _a.components;
if (oldComponentStates) {
unloadIncompatibleStates(pendingAttachStates, oldComponentStates);
}
// if the nodes are not of the same of the same type, we need to replace.

@@ -78,3 +102,3 @@ if (node.nodeType === TEXT_NODE_TYPE ||

node.tagName.toLowerCase() !== forgoElement.type)) {
const newElement = document.createElement(forgoElement.type);
const newElement = env.document.createElement(forgoElement.type);
node.replaceWith(newElement);

@@ -92,3 +116,3 @@ nodeToBindTo = newElement;

// There was no node passed in, so create a new element.
const newElement = document.createElement(forgoElement.type);
const newElement = env.document.createElement(forgoElement.type);
if (forgoElement.props.ref) {

@@ -113,11 +137,2 @@ forgoElement.props.ref.value = newElement;

if (!hasCompatibleState) {
// If we don't have compatible state,
// unload all components from index upwards
const unloaded = state.components.slice(componentIndex);
state.components = state.components.slice(0, componentIndex);
for (const item of unloaded) {
if (item.component.unload) {
item.component.unload();
}
}
// We have to create a new component

@@ -147,3 +162,3 @@ const args = { element: { componentIndex } };

// we'll push the savedComponentState into pending states for later attachment.
const statesToAttach = pendingAttachStates.concat(savedComponentState);
const statesToAttach = pendingAttachStates.concat(Object.assign(Object.assign({}, savedComponentState), { props: forgoElement.props }));
// Get a new element by calling render on existing component.

@@ -216,23 +231,12 @@ const newForgoElement = savedComponentState.component.render(forgoElement.props, args);

else {
if (typeof forgoChild.type === "string") {
const findResult = findReplacementCandidateForDOMElement(forgoChild, childNodes, forgoChildIndex);
if (findResult.found) {
unloadNodes(parentElement, childNodes, forgoChildIndex, findResult.index);
render(forgoChild, childNodes[findResult.index], []);
}
else {
const { node } = render(forgoChild, undefined, []);
parentElement.insertBefore(node, childNodes[forgoChildIndex]);
}
const findResult = typeof forgoChild.type === "string"
? findReplacementCandidateForDOMElement(forgoChild, childNodes, forgoChildIndex)
: findReplacementCandidateForCustomComponent(forgoChild, childNodes, forgoChildIndex);
if (findResult.found) {
unloadNodes(parentElement, childNodes, forgoChildIndex, findResult.index);
render(forgoChild, childNodes[findResult.index], []);
}
else {
const findResult = findReplacementCandidateForCustomComponent(forgoChild, childNodes, forgoChildIndex);
if (findResult.found) {
unloadNodes(parentElement, childNodes, forgoChildIndex, findResult.index);
render(forgoChild, childNodes[findResult.index], []);
}
else {
const { node } = render(forgoChild, undefined, []);
parentElement.insertBefore(node, childNodes[forgoChildIndex]);
}
const { node } = render(forgoChild, undefined, []);
parentElement.insertBefore(node, childNodes[forgoChildIndex]);
}

@@ -247,3 +251,3 @@ }

/*
Unloads nodes from a node list
Unloads components from a node list
This does:

@@ -260,4 +264,4 @@ a) Remove the node

for (const componentState of state.components) {
if (componentState.component.unload) {
componentState.component.unload();
if (componentState.component.unmount) {
componentState.component.unmount();
}

@@ -269,2 +273,32 @@ }

/*
When states is attached to a new node,
or when states are reattached, some of the old component states need to go away.
The corresponding components will need to be unmounted.
While rendering, the component gets reused if the ctor is the same.
If the ctor is different, the component is discarded. And hence needs to be unmounted.
So we check the ctor type in old and new.
*/
function unloadIncompatibleStates(newStates, oldStates) {
let i = 0;
for (const newState of newStates) {
if (oldStates.length > i) {
const oldState = oldStates[i];
if (oldState.ctor !== newState.ctor) {
break;
}
i++;
}
else {
break;
}
}
for (let j = i; j < oldStates.length; j++) {
const oldState = oldStates[j];
if (oldState.component.unmount) {
oldState.component.unmount();
}
}
}
/*
When we try to find replacement candidates for DOM nodes,

@@ -271,0 +305,0 @@ we try to:

{
"name": "forgo",
"version": "0.0.5",
"main": "./dist"
"version": "0.0.6",
"main": "./dist",
"devDependencies": {
"@types/jsdom": "^16.2.6",
"@types/mocha": "^8.2.0",
"@types/should": "^13.0.0",
"esm": "^3.2.25",
"jsdom": "^16.4.0",
"mocha": "^8.2.1",
"should": "^13.2.3",
"typescript": "^4.1.3"
},
"scripts": {
"build-test": "(cd test && ./build.sh)",
"test": "mocha -r esm test/dist/test.js"
},
"license": "MIT"
}
# forgo
Forgo is a 4KB library that makes it super easy to create modern web apps using JSX (like React).
Forgo is a 4KB library that makes it super easy to create modern web apps using JSX (like React).
Unlike React, apps are plain JS with very little framework specific code. Everything you already know about DOM APIs and JavaScript will easily carry over.
- Use HTML DOM APIs for accessing elements
- There are no synthetic events, use standard DOM APIs
- There are no synthetic events
- Use closures for maintaining component state
- Use any singleton pattern for managing app-wide state
- There's no vDOM, DOM diffing etc. Renders are manually triggered.
- There's no vDOM or DOM diffing. Renders are manually triggered
Forgo is basically just one small JS file (actually TypeScript). It's somewhat decently documented, but I could use some help here. A stated goal of the project is to always remain within that single file.
Forgo is basically just one small JS file (actually TypeScript). It's somewhat decently documented, but I could use some help here. A stated goal of the project is to always remain within that single file.

@@ -57,2 +56,156 @@ ## Installation

## Child Components and Passing Props
That works just as you'd have seen in React.
```jsx
function Parent(props) {
return {
render(props, args) {
return (
<div>
<Greeter firstName="Jeswin" />
<Greeter firstName="Kai" />
</div>
);
},
};
}
function Greeter(props) {
return {
render(props, args) {
return <div>Hello {props.firstName}</div>;
},
};
}
```
## Reading Form Input Elements
You're expected to read form input control values with regular DOM APIs.
There's a small hurdle though - how do you we get a reference to these nodes? Well, that's where the ref attribute comes in. An object bound to the ref attribute in the markup will have its value property set to the DOM element.
See the usage below:
```jsx
function Component(props) {
const myInputRef = {};
return {
render(props, args) {
function onClick() {
const inputElement = myInputRef.value;
alert(inputElement.value); // Read the text input!
}
return (
<div>
<input type="text" ref={myInputRef} />
<button onclick={onClick}>Click me!</button>
</div>
);
},
};
}
```
## Multiple Components, passing props etc.
Finally, let's do a recap with a more complete example. Let's make a Todo List app in TypeScript.
There will be three components:
1. TodoList (the main component)
2. TodoListItem
3. AddTodo
Here's the TodoList, which hosts the other two components.
```tsx
type TodoListProps = {};
function TodoList(props: TodoListProps) {
let todos: string[] = [];
return {
render(props: TodoListProps, args: ForgoRenderArgs) {
function onTodoAdd(text: string) {
todos.push(text);
rerender(args.element);
}
return (
<div>
<h1>Forgo Todos</h1>
<ul>
{todos.map((t) => (
<TodoListItem text={t} />
))}
</ul>
<AddTodo onAdd={onTodoAdd} />
</div>
);
},
};
}
```
Here's the TodoList item, which simply displays a Todo.
```tsx
type TodoListItemProps = {
text: string;
};
function TodoListItem(props: TodoListItemProps) {
return {
render() {
return <li>{props.text}</li>;
},
};
}
```
And here's the AddTodo component. It takes an onAdd function from the parent, which gets called whenever a new todo is added.
```tsx
type AddTodoProps = {
onAdd: (text: string) => void;
};
function AddTodo(props: AddTodoProps) {
const input: { value?: HTMLInputElement } = {};
function onClick() {
const inputEl = input.value;
if (inputEl) {
props.onAdd(inputEl.value);
inputEl.value = "";
inputEl.focus();
}
}
return {
render() {
return (
<div>
<input type="text" ref={input} />
<button onclick={onClick}>Add me!</button>
</div>
);
},
};
}
```
That's all. Mount it, and we're ready to go.
```ts
window.addEventListener("load", () => {
mount(<TodoList />, document.getElementById("root"));
});
```
## Building

@@ -86,4 +239,4 @@

"jsxImportSource": "forgo"
},
}
}
```
```

@@ -36,4 +36,3 @@ /*

1. render() returns the actual DOM to render.
2. load() is optional. And gets called just before the component gets mounted.
3. unload() is optional. Opposite of load, gets called just before unmount.
2. unmount() is optional. Gets called just before unmount.
*/

@@ -45,4 +44,3 @@ export type ForgoComponent<TProps extends ForgoElementProps> = {

) => ForgoElement<ForgoComponentCtor<TProps>, TProps>;
load?: () => void;
unload?: () => void;
unmount?: () => void;
};

@@ -118,2 +116,22 @@

/*
The following adds support for injecting test environment objects.
Such as JSDOM.
*/
export type EnvType = {
window: Window | typeof globalThis;
document: HTMLDocument;
};
const documentObject = globalThis ? globalThis.document : document;
const windowObject = globalThis ? globalThis : window;
let env: EnvType = {
window: windowObject,
document: documentObject,
};
export function setCustomEnv(value: any) {
env = value;
}
/*
This is the main render function.

@@ -173,5 +191,12 @@ forgoNode is the node to render.

// Text nodes will always be recreated
const textNode = document.createTextNode(text);
const textNode = env.document.createTextNode(text);
attachProps(text, textNode, pendingAttachStates);
if (node) {
// If there are old component states, we might need to unmount some of em.
// After comparing with the new states.
const oldComponentStates = getForgoState(node)?.components;
if (oldComponentStates) {
unloadIncompatibleStates(pendingAttachStates, oldComponentStates);
}
node.replaceWith(textNode);

@@ -202,2 +227,9 @@ }

// If there are old component states, we might need to unmount some of em.
// After comparing with the new states.
const oldComponentStates = getForgoState(node)?.components;
if (oldComponentStates) {
unloadIncompatibleStates(pendingAttachStates, oldComponentStates);
}
// if the nodes are not of the same of the same type, we need to replace.

@@ -209,3 +241,3 @@ if (

) {
const newElement = document.createElement(forgoElement.type);
const newElement = env.document.createElement(forgoElement.type);
node.replaceWith(newElement);

@@ -217,2 +249,3 @@ nodeToBindTo = newElement;

attachProps(forgoElement, nodeToBindTo, pendingAttachStates);
renderChildNodes(forgoElement, nodeToBindTo as HTMLElement);

@@ -222,3 +255,3 @@ return { node: nodeToBindTo };

// There was no node passed in, so create a new element.
const newElement = document.createElement(forgoElement.type);
const newElement = env.document.createElement(forgoElement.type);
if (forgoElement.props.ref) {

@@ -251,12 +284,2 @@ forgoElement.props.ref.value = newElement;

if (!hasCompatibleState) {
// If we don't have compatible state,
// unload all components from index upwards
const unloaded = state.components.slice(componentIndex);
state.components = state.components.slice(0, componentIndex);
for (const item of unloaded) {
if (item.component.unload) {
item.component.unload();
}
}
// We have to create a new component

@@ -290,3 +313,6 @@ const args: ForgoRenderArgs = { element: { componentIndex } };

// we'll push the savedComponentState into pending states for later attachment.
const statesToAttach = pendingAttachStates.concat(savedComponentState);
const statesToAttach = pendingAttachStates.concat({
...savedComponentState,
props: forgoElement.props,
});

@@ -380,38 +406,26 @@ // Get a new element by calling render on existing component.

} else {
if (typeof forgoChild.type === "string") {
const findResult = findReplacementCandidateForDOMElement(
forgoChild as ForgoElement<string, any>,
const findResult =
typeof forgoChild.type === "string"
? findReplacementCandidateForDOMElement(
forgoChild as ForgoElement<string, any>,
childNodes,
forgoChildIndex
)
: findReplacementCandidateForCustomComponent(
forgoChild as ForgoElement<ForgoComponentCtor<any>, any>,
childNodes,
forgoChildIndex
);
if (findResult.found) {
unloadNodes(
parentElement,
childNodes,
forgoChildIndex
forgoChildIndex,
findResult.index
);
if (findResult.found) {
unloadNodes(
parentElement,
childNodes,
forgoChildIndex,
findResult.index
);
render(forgoChild, childNodes[findResult.index], []);
} else {
const { node } = render(forgoChild, undefined, []);
parentElement.insertBefore(node, childNodes[forgoChildIndex]);
}
render(forgoChild, childNodes[findResult.index], []);
} else {
const findResult = findReplacementCandidateForCustomComponent(
forgoChild as ForgoElement<ForgoComponentCtor<any>, any>,
childNodes,
forgoChildIndex
);
if (findResult.found) {
unloadNodes(
parentElement,
childNodes,
forgoChildIndex,
findResult.index
);
render(forgoChild, childNodes[findResult.index], []);
} else {
const { node } = render(forgoChild, undefined, []);
parentElement.insertBefore(node, childNodes[forgoChildIndex]);
}
const { node } = render(forgoChild, undefined, []);
parentElement.insertBefore(node, childNodes[forgoChildIndex]);
}

@@ -432,3 +446,3 @@ }

/*
Unloads nodes from a node list
Unloads components from a node list
This does:

@@ -450,4 +464,4 @@ a) Remove the node

for (const componentState of state.components) {
if (componentState.component.unload) {
componentState.component.unload();
if (componentState.component.unmount) {
componentState.component.unmount();
}

@@ -459,2 +473,37 @@ }

/*
When states is attached to a new node,
or when states are reattached, some of the old component states need to go away.
The corresponding components will need to be unmounted.
While rendering, the component gets reused if the ctor is the same.
If the ctor is different, the component is discarded. And hence needs to be unmounted.
So we check the ctor type in old and new.
*/
function unloadIncompatibleStates(
newStates: NodeAttachedComponentState<any>[],
oldStates: NodeAttachedComponentState<any>[]
) {
let i = 0;
for (const newState of newStates) {
if (oldStates.length > i) {
const oldState = oldStates[i];
if (oldState.ctor !== newState.ctor) {
break;
}
i++;
} else {
break;
}
}
for (let j = i; j < oldStates.length; j++) {
const oldState = oldStates[j];
if (oldState.component.unmount) {
oldState.component.unmount();
}
}
}
type CandidateSearchResult =

@@ -461,0 +510,0 @@ | {

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