@b9g/crank
Advanced tools
Comparing version 0.5.7 to 0.6.0
@@ -11,3 +11,3 @@ /** | ||
*/ | ||
export type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps : Record<string, unknown>; | ||
export type TagProps<TTag extends Tag> = TTag extends string ? JSX.IntrinsicElements[TTag] : TTag extends Component<infer TProps> ? TProps & JSX.IntrinsicAttributes : Record<string, unknown> & JSX.IntrinsicAttributes; | ||
/*** | ||
@@ -88,3 +88,3 @@ * SPECIAL TAGS | ||
*/ | ||
export type Component<TProps extends Record<string, unknown> = any> = (this: Context<TProps>, props: TProps) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>; | ||
export type Component<TProps extends Record<string, unknown> = any> = (this: Context<TProps>, props: TProps, ctx: Context<TProps>) => Children | PromiseLike<Children> | Iterator<Children, Children | void, any> | AsyncIterator<Children, Children | void, any>; | ||
/** | ||
@@ -112,26 +112,6 @@ * A type to keep track of keys. Any value can be a key, though null and | ||
/** | ||
* An object containing the “properties” of an element. These correspond to | ||
* An object containing the "properties" of an element. These correspond to | ||
* the attribute syntax from JSX. | ||
*/ | ||
props: TagProps<TTag>; | ||
/** | ||
* A value which uniquely identifies an element from its siblings so that it | ||
* can be added/updated/moved/removed by key rather than position. | ||
* | ||
* Passed in createElement() as the prop "c-key". | ||
*/ | ||
key: Key; | ||
/** | ||
* A callback which is called with the element’s result when it is committed. | ||
* | ||
* Passed in createElement() as the prop "c-ref". | ||
*/ | ||
ref: ((value: unknown) => unknown) | undefined; | ||
/** | ||
* A possible boolean which indicates that element should NOT be rerendered. | ||
* If the element has never been rendered, this property has no effect. | ||
* | ||
* Passed in createElement() as the prop "c-static". | ||
*/ | ||
static_: boolean | undefined; | ||
} | ||
@@ -159,3 +139,6 @@ /** | ||
export declare class Element<TTag extends Tag = Tag> { | ||
constructor(tag: TTag, props: TagProps<TTag>, key: Key, ref?: ((value: unknown) => unknown) | undefined, static_?: boolean | undefined); | ||
constructor(tag: TTag, props: TagProps<TTag>); | ||
get key(): Key; | ||
get ref(): unknown; | ||
get copy(): boolean; | ||
} | ||
@@ -179,8 +162,9 @@ export declare function isElement(value: any): value is Element; | ||
* | ||
* When asking the question, what is the “value” of a specific element, the | ||
* When asking the question, what is the "value" of a specific element, the | ||
* answer varies depending on the tag: | ||
* | ||
* For host elements, the value is the nodes created for the element. | ||
* For host elements, the value is the nodes created for the element, e.g. the | ||
* DOM node in the case of the DOMRenderer. | ||
* | ||
* For fragments, the value is usually an array of nodes. | ||
* For fragments, the value is the value of the | ||
* | ||
@@ -290,5 +274,5 @@ * For portals, the value is undefined, because a Portal element’s root and | ||
raw(value: string | TNode, scope: TScope | undefined, hydration: HydrationData<TNode> | undefined): ElementValue<TNode>; | ||
patch<TTag extends string | symbol, TName extends string>(tag: TTag, node: TNode, name: TName, value: TagProps<TTag>[TName], oldValue: TagProps<TTag>[TName] | undefined, scope: TScope): unknown; | ||
arrange<TTag extends string | symbol>(tag: TTag, node: TNode, props: TagProps<TTag>, children: Array<TNode | string>, oldProps: TagProps<TTag> | undefined, oldChildren: Array<TNode | string> | undefined): unknown; | ||
dispose<TTag extends string | symbol>(tag: TTag, node: TNode, props: TagProps<TTag>): unknown; | ||
patch<TTag extends string | symbol, TName extends string>(tag: TTag, node: TNode, name: TName, value: unknown, oldValue: unknown, scope: TScope): unknown; | ||
arrange<TTag extends string | symbol>(tag: TTag, node: TNode, props: Record<string, unknown>, children: Array<TNode | string>, oldProps: Record<string, unknown> | undefined, oldChildren: Array<TNode | string> | undefined): unknown; | ||
dispose<TTag extends string | symbol>(tag: TTag, node: TNode, props: Record<string, unknown>): unknown; | ||
flush(root: TRoot): unknown; | ||
@@ -299,3 +283,4 @@ } | ||
* An abstract class which is subclassed to render to different target | ||
* environments. This class is responsible for kicking off the rendering | ||
* environments. Subclasses will typically call super() with a custom | ||
* RendererImpl. This class is responsible for kicking off the rendering | ||
* process and caching previous trees by root. | ||
@@ -408,6 +393,2 @@ * | ||
* The current props of the associated element. | ||
* | ||
* Typically, you should read props either via the first parameter of the | ||
* component or via the context iterator methods. This property is mainly for | ||
* plugins or utilities which wrap contexts. | ||
*/ | ||
@@ -418,5 +399,3 @@ get props(): ComponentProps<T>; | ||
* | ||
* Typically, you should read values via refs, generator yield expressions, | ||
* or the refresh, schedule, cleanup, or flush methods. This property is | ||
* mainly for plugins or utilities which wrap contexts. | ||
* @deprecated | ||
*/ | ||
@@ -486,2 +465,26 @@ get value(): TResult; | ||
} | ||
interface IntrinsicAttributes { | ||
children?: unknown; | ||
key?: unknown; | ||
ref?: unknown; | ||
["static"]?: unknown; | ||
/** @deprecated */ | ||
["crank-key"]?: unknown; | ||
/** @deprecated */ | ||
["crank-ref"]?: unknown; | ||
/** @deprecated */ | ||
["crank-static"]?: unknown; | ||
/** @deprecated */ | ||
["c-key"]?: unknown; | ||
/** @deprecated */ | ||
["c-ref"]?: unknown; | ||
/** @deprecated */ | ||
["c-static"]?: unknown; | ||
/** @deprecated */ | ||
$key?: unknown; | ||
/** @deprecated */ | ||
$ref?: unknown; | ||
/** @deprecated */ | ||
$static?: unknown; | ||
} | ||
interface ElementChildrenAttribute { | ||
@@ -488,0 +491,0 @@ children: {}; |
@@ -10,3 +10,3 @@ import { Children, Context, ElementValue, Renderer, RendererImpl } from "./crank.js"; | ||
declare global { | ||
module Crank { | ||
namespace Crank { | ||
interface EventMap extends GlobalEventHandlersEventMap { | ||
@@ -13,0 +13,0 @@ } |
@@ -113,2 +113,9 @@ /// <reference types="dom.d.ts" /> | ||
default: { | ||
if (name[0] === "o" && | ||
name[1] === "n" && | ||
name[2] === name[2].toUpperCase() && | ||
typeof value === "function") { | ||
// Support React-style event names (onClick, onChange, etc.) | ||
name = name.toLowerCase(); | ||
} | ||
if (name in node && | ||
@@ -115,0 +122,0 @@ // boolean properties will coerce strings, but sometimes they map to |
@@ -12,3 +12,3 @@ import { Renderer } from "./crank.js"; | ||
declare global { | ||
module Crank { | ||
namespace Crank { | ||
interface EventMap extends GlobalEventHandlersEventMap { | ||
@@ -15,0 +15,0 @@ } |
13
html.js
@@ -55,2 +55,15 @@ /// <reference types="html.d.ts" /> | ||
case name === "innerHTML": | ||
case name === "key": | ||
case name === "ref": | ||
case name === "static": | ||
case name === "crank-key": | ||
case name === "crank-ref": | ||
case name === "crank-static": | ||
case name === "c-key": | ||
case name === "c-ref": | ||
case name === "c-static": | ||
case name === "$key": | ||
case name === "$ref": | ||
case name === "$static": | ||
// TODO: Remove deprecated special props | ||
break; | ||
@@ -57,0 +70,0 @@ case name === "style": { |
@@ -5,2 +5,3 @@ declare function jsxAdapter(tag: any, props: Record<string, any>, key: any): import("./crank.js").Element<any>; | ||
export declare const jsxs: typeof jsxAdapter; | ||
export declare const jsxDEV: typeof jsxAdapter; | ||
export {}; |
@@ -5,6 +5,9 @@ /// <reference types="jsx-runtime.d.ts" /> | ||
// This file is provided for compatibility reasons with the JSX automatic | ||
// runtime. Besides automatic imports, the JSX automatic runtime provides no | ||
// actual advantage over the createElement transform. | ||
function jsxAdapter(tag, props, key) { | ||
// The new JSX transform extracts the key from props for reasons, but key is | ||
// not a special property in Crank. | ||
return createElement(tag, { ...props, key }); | ||
// The new JSX transform extracts the key from props for performance reasons, | ||
// but key is not a special property in Crank. | ||
props.key = key; | ||
return createElement(tag, props); | ||
} | ||
@@ -14,4 +17,5 @@ const Fragment = ""; | ||
const jsxs = jsxAdapter; | ||
const jsxDEV = jsxAdapter; | ||
export { Fragment, jsx, jsxs }; | ||
export { Fragment, jsx, jsxDEV, jsxs }; | ||
//# sourceMappingURL=jsx-runtime.js.map |
import type { Element } from "./crank.js"; | ||
export declare function jsx(spans: TemplateStringsArray, ...expressions: Array<unknown>): Element; |
@@ -0,0 +0,0 @@ /// <reference types="jsx-tag.d.ts" /> |
{ | ||
"name": "@b9g/crank", | ||
"version": "0.5.7", | ||
"version": "0.6.0", | ||
"description": "Write JSX-driven components with functions, promises and generators.", | ||
@@ -89,18 +89,18 @@ "homepage": "https://crank.js.org", | ||
"@arkweid/lefthook": "^0.7.7", | ||
"@types/sinon": "^10.0.13", | ||
"@typescript-eslint/eslint-plugin": "^5.48.2", | ||
"@typescript-eslint/parser": "^5.48.2", | ||
"eslint": "^8.32.0", | ||
"eslint-config-prettier": "^8.6.0", | ||
"eslint-plugin-prettier": "^4.2.1", | ||
"eslint-plugin-react": "^7.32.0", | ||
"magic-string": "^0.27.0", | ||
"playwright-test": "^8.1.2", | ||
"prettier": "^2.8.3", | ||
"rollup": "^3.10.0", | ||
"rollup-plugin-typescript2": "^0.34.1", | ||
"@types/sinon": "^17.0.3", | ||
"@typescript-eslint/eslint-plugin": "^6.19.0", | ||
"@typescript-eslint/parser": "^6.19.0", | ||
"eslint": "^8.56.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"eslint-plugin-prettier": "^5.1.3", | ||
"eslint-plugin-react": "^7.33.2", | ||
"magic-string": "^0.30.5", | ||
"playwright-test": "^14.0.0", | ||
"prettier": "^3.2.4", | ||
"rollup": "^4.9.6", | ||
"rollup-plugin-typescript2": "^0.36.0", | ||
"shx": "^0.3.4", | ||
"sinon": "^15.0.1", | ||
"ts-node": "^10.9.1", | ||
"typescript": "^4.9.5", | ||
"sinon": "^17.0.1", | ||
"ts-node": "^10.9.2", | ||
"typescript": "^5.3.3", | ||
"uvu": "^0.5.6" | ||
@@ -107,0 +107,0 @@ }, |
247
README.md
@@ -1,21 +0,218 @@ | ||
# Crank.js | ||
### The Just JavaScript web framework. | ||
## Try Crank | ||
Crank is a web framework where components can be defined with sync functions, async functions and generator functions. The documentation for Crank.js is available at [crank.js.org](https://crank.js.org). | ||
The fastest way to try Crank is via the [online playground](https://crank.js.org/playground). In addition, many of the code examples in these guides feature live previews. | ||
## Get Started | ||
## Installation | ||
Crank.js is published on NPM under the `@b9g` organization (short for “b*ikeshavin*g”). | ||
The Crank package is available on [NPM](https://npmjs.org/@b9g/crank) through | ||
the [@b9g organization](https://www.npmjs.com/org/b9g) (short for | ||
b*ikeshavin*g). | ||
```shell | ||
$ npm i @b9g/crank | ||
npm i @b9g/crank | ||
``` | ||
### Key Examples | ||
### Importing Crank with the **classic** JSX transform. | ||
#### A Simple Component | ||
```jsx live | ||
/** @jsx createElement */ | ||
/** @jsxFrag Fragment */ | ||
import {createElement, Fragment} from "@b9g/crank"; | ||
import {renderer} from "@b9g/crank/dom"; | ||
renderer.render( | ||
<p>This paragraph element is transpiled with the classic transform.</p>, | ||
document.body, | ||
); | ||
``` | ||
### Importing Crank with the **automatic** JSX transform. | ||
```jsx live | ||
/** @jsxImportSource @b9g/crank */ | ||
import {renderer} from "@b9g/crank/dom"; | ||
renderer.render( | ||
<p>This paragraph element is transpiled with the automatic transform.</p>, | ||
document.body, | ||
); | ||
``` | ||
You will likely have to configure your tools to support JSX, especially if you do not want to use `@jsx` comment pragmas. See below for common tools and configurations. | ||
### Importing the JSX template tag. | ||
Starting in version `0.5`, the Crank package ships a [tagged template function](/guides/jsx-template-tag) which provides similar syntax and semantics as the JSX transform. This allows you to write Crank components in vanilla JavaScript. | ||
```js live | ||
import {jsx} from "@b9g/crank/standalone"; | ||
import {renderer} from "@b9g/crank/dom"; | ||
renderer.render(jsx` | ||
<p>No transpilation is necessary with the JSX template tag.</p> | ||
`, document.body); | ||
``` | ||
### ECMAScript Module CDNs | ||
Crank is also available on CDNs like [unpkg](https://unpkg.com) | ||
(https://unpkg.com/@b9g/crank?module) and [esm.sh](https://esm.sh) | ||
(https://esm.sh/@b9g/crank) for usage in ESM-ready environments. | ||
```jsx live | ||
/** @jsx createElement */ | ||
// This is an ESM-ready environment! | ||
// If code previews work, your browser is an ESM-ready environment! | ||
import {createElement} from "https://unpkg.com/@b9g/crank/crank?module"; | ||
import {renderer} from "https://unpkg.com/@b9g/crank/dom?module"; | ||
renderer.render( | ||
<div id="hello"> | ||
Running on <a href="https://unpkg.com">unpkg.com</a> | ||
</div>, | ||
document.body, | ||
); | ||
``` | ||
## Common tool configurations | ||
The following is an incomplete list of configurations to get started with Crank. | ||
### [TypeScript](https://www.typescriptlang.org) | ||
TypeScript is a typed superset of JavaScript. | ||
Here’s the configuration you will need to set up automatic JSX transpilation. | ||
```tsconfig.json | ||
{ | ||
"compilerOptions": { | ||
"jsx": "react-jsx", | ||
"jsxImportSource": "@b9g/crank" | ||
} | ||
} | ||
``` | ||
The classic transform is supported as well. | ||
```tsconfig.json | ||
{ | ||
"compilerOptions": { | ||
"jsx": "react", | ||
"jsxFactory": "createElement", | ||
"jsxFragmentFactory": "Fragment" | ||
} | ||
} | ||
``` | ||
Crank is written in TypeScript. Refer to [the guide on TypeScript](/guides/working-with-typescript) for more information about Crank types. | ||
```tsx | ||
import type {Context} from "@b9g/crank"; | ||
function *Timer(this: Context) { | ||
let seconds = 0; | ||
const interval = setInterval(() => { | ||
seconds++; | ||
this.refresh(); | ||
}, 1000); | ||
for ({} of this) { | ||
yield <div>Seconds: {seconds}</div>; | ||
} | ||
clearInterval(interval); | ||
} | ||
``` | ||
### [Babel](https://babeljs.io) | ||
Babel is a popular open-source JavaScript compiler which allows you to write code with modern syntax (including JSX) and run it in environments which do not support the syntax. | ||
Here is how to get Babel to transpile JSX for Crank. | ||
Automatic transform: | ||
```.babelrc.json | ||
{ | ||
"plugins": [ | ||
"@babel/plugin-syntax-jsx", | ||
[ | ||
"@babel/plugin-transform-react-jsx", | ||
{ | ||
"runtime": "automatic", | ||
"importSource": "@b9g/crank", | ||
"throwIfNamespace": false, | ||
"useSpread": true | ||
} | ||
] | ||
] | ||
} | ||
``` | ||
Classic transform: | ||
```.babelrc.json | ||
{ | ||
"plugins": [ | ||
"@babel/plugin-syntax-jsx", | ||
[ | ||
"@babel/plugin-transform-react-jsx", | ||
{ | ||
"runtime": "class", | ||
"pragma": "createElement", | ||
"pragmaFrag": "''", | ||
"throwIfNamespace": false, | ||
"useSpread": true | ||
} | ||
] | ||
] | ||
} | ||
``` | ||
### [ESLint](https://eslint.org) | ||
ESLint is a popular open-source tool for analyzing and detecting problems in JavaScript code. | ||
Crank provides a configuration preset for working with ESLint under the package name `eslint-plugin-crank`. | ||
```bash | ||
npm i eslint eslint-plugin-crank | ||
``` | ||
In your eslint configuration: | ||
```.eslintrc.json | ||
{ | ||
"extends": ["plugin:crank/recommended"] | ||
} | ||
``` | ||
### [Astro](https://astro.build) | ||
Astro.js is a modern static site builder and framework. | ||
Crank provides an [Astro integration](https://docs.astro.build/en/guides/integrations-guide/) to enable server-side rendering and client-side hydration with Astro. | ||
```bash | ||
npm i astro-crank | ||
``` | ||
In your `astro.config.mjs`. | ||
```astro.config.mjs | ||
import {defineConfig} from "astro/config"; | ||
import crank from "astro-crank"; | ||
// https://astro.build/config | ||
export default defineConfig({ | ||
integrations: [crank()], | ||
}); | ||
``` | ||
## Key Examples | ||
### A Simple Component | ||
```jsx live | ||
import {renderer} from "@b9g/crank/dom"; | ||
function Greeting({name = "World"}) { | ||
@@ -30,3 +227,3 @@ return ( | ||
#### A Stateful Component | ||
### A Stateful Component | ||
@@ -54,18 +251,24 @@ ```jsx live | ||
#### An Async Component | ||
### An Async Component | ||
```jsx live | ||
import {renderer} from "@b9g/crank/dom"; | ||
async function Definition({word}) { | ||
// API courtesy https://dictionaryapi.dev | ||
const res = await fetch(`https://api.dictionaryapi.dev/api/v2/entries/en/${word}`); | ||
const data = await res.json(); | ||
if (!Array.isArray(data)) { | ||
return <p>No definition found for {word}</p>; | ||
} | ||
async function QuoteOfTheDay() { | ||
const res = await fetch("https://favqs.com/api/qotd"); | ||
const {quote} = await res.json(); | ||
return ( | ||
<p> | ||
“{quote.body}” – <a href={quote.url}>{quote.author}</a> | ||
</p> | ||
); | ||
const {phonetic, meanings} = data[0]; | ||
const {partOfSpeech, definitions} = meanings[0]; | ||
const {definition} = definitions[0]; | ||
return <> | ||
<p>{word} <code>{phonetic}</code></p> | ||
<p><b>{partOfSpeech}.</b> {definition}</p> | ||
</>; | ||
} | ||
renderer.render(<QuoteOfTheDay />, document.body); | ||
await renderer.render(<Definition word="framework" />, document.body); | ||
``` | ||
@@ -117,6 +320,6 @@ | ||
<Fragment> | ||
<div> | ||
<RandomDogLoader throttle={throttle} /> | ||
<p> | ||
<button>Show me another dog.</button> | ||
</div> | ||
<RandomDogLoader throttle={throttle} /> | ||
</p> | ||
</Fragment> | ||
@@ -123,0 +326,0 @@ ); |
export * from "./crank.js"; | ||
export { jsx } from "./jsx-tag.js"; |
export * from "./crank.js"; | ||
export * as dom from "./dom.js"; | ||
export * as html from "./html.js"; |
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 too big to display
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
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
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
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1230562
8877
329