Move to own JSX implementation
continuous-integration/drone/push Build is failing Details

This commit is contained in:
Fabian Stamm 2021-01-11 14:55:54 +01:00
parent 7c1166bf87
commit 2af5d4f823
81 changed files with 159 additions and 10484 deletions

View File

@ -1,16 +0,0 @@
name: ci
on:
push:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: denolib/setup-deno@master
- run: deno --version
- run: deno test examples/01.tsx
- run: deno test examples/03-async.tsx

3
jsx-html/.gitignore vendored
View File

@ -1,3 +0,0 @@
/dist
node_modules
tmp

View File

@ -1,5 +0,0 @@
{
"trailingComma": "all",
"tabWidth": 4,
"singleQuote": true
}

View File

@ -1,4 +0,0 @@
{
"deno.enable": true,
"deno.unstable": true
}

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2020 Alexandre Piel
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,103 +0,0 @@
# jsx-html
`jsx-html` render JSX template to HTML asynchronously. Compatible with Deno, NodeJs and can also run in browser.
Try with runkit: https://runkit.com/apiel/jsx-html-example
## NodeJs
```sh
yarn add async-jsx-html
# or
npm install async-jsx-html
```
```tsx
import { React } from 'async-jsx-html';
const View = () => <div>Hello</div>;
// render return a Promise
(<View />).render().then((html: string) => console.log(html));
```
## Deno
```tsx
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/master/jsx.d.ts" />
import { React } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const View = () => <div>Hello</div>;
// render return a Promise
(<View />).render().then((html: string) => console.log(html));
```
```sh
deno run https://raw.githubusercontent.com/apiel/jsx-html/master/examples/00.tsx
```
## TsConfig
As you would do with React, you need to import `React` from `jsx-html` for the transpiler. If you are not feeling confortable with using `React` as import since it is not React, you can import `jsx` from `jsx-html` but you would have to update your tsconfig file: https://github.com/denoland/deno/issues/3572
```json
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "jsx"
}
}
```
```tsx
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/master/jsx.d.ts" />
import { jsx } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const View = () => <div>Hello</div>;
(<View />).render().then(console.log);
```
> **Note:** prefer using sermver tags version instead of master to avoid conflict with caching, e.g:
> `import { jsx } from 'https://raw.githubusercontent.com/apiel/jsx-html/1.0.0/mod.ts';`.
## Async component
Unlike React, components can be asynchrone, so you can fetch for data without to handle states.
```tsx
import { React } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const Data = async () => {
const res = await fetch('http://example.com/some/api');
const content = new Uint8Array(await res.arrayBuffer());
return <div>{content}</div>;
};
const View = () => (
<div>
<Data />
</div>
);
(<View />).render().then(console.log);
```
# InnerHTML
The Element property innerHTML sets the HTML or XML markup contained within the property.
In general, setting HTML from code is risky because its easy to inadvertently expose your users to a cross-site scripting (XSS) attack.
```tsx
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/master/jsx.d.ts" />
import { jsx } from 'https://raw.githubusercontent.com/apiel/jsx-html/master/mod.ts';
const View = () => <div innerHTML="<b>hello</b> world" />;
(<View />).render().then(console.log); // will output <div><b>hello</b> world</div>
```
## Browser
`jsx-html` can also be used directly in browser. Find an example with webpack [here](https://github.com/apiel/jsx-html/tree/master/examples/browser).

View File

@ -1,26 +0,0 @@
'use strict';
var regExp = /\.(ts|tsx|js|jsx)$/i;
module.exports = function () {
return {
visitor: {
ImportDeclaration: function ImportDeclaration(path) {
var source = path.node.source;
if (!source.value.match(regExp)) {
return;
}
source.value = source.value.replace(regExp, '');
},
ExportDeclaration: function ExportDeclaration(path) {
var source = path.node.source;
if (source) {
if (!source.value.match(regExp)) {
return;
}
source.value = source.value.replace(regExp, '');
}
},
},
};
};

View File

@ -1,6 +0,0 @@
export enum NODE_TYPE {
ELEMENT = 'element',
TEXT = 'text',
COMPONENT = 'component',
FRAGMENT = 'fragment',
};

3496
jsx-html/deno.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
{
"cmds": {
"start": ["den 00", "den 01", "den 02", "den 03", "den 04", "den 05", "den 06"],
"test": ["den 01:test", "den 03:test", "den 05:test", "den 06:test"],
"00": "deno run examples/00.tsx",
"01": "deno run examples/01.tsx",
"01:test": "deno test examples/01.tsx",
"02": "deno run -c examples/02/tsconfig.json examples/02/02.tsx",
"03": "deno run examples/03-async.tsx",
"03:test": "deno test examples/03-async.tsx",
"04": "deno run examples/04.tsx",
"05": "deno run examples/05.tsx",
"05:test": "deno test examples/05.tsx",
"06": "deno run examples/06.tsx",
"06:test": "deno test examples/06.tsx"
}
}

View File

@ -1,101 +0,0 @@
const fs = require('fs');
const { parse } = require('@babel/parser');
const { default: generator } = require('@babel/generator');
const { default: traverse } = require('@babel/traverse');
const { resolve, extname, join, dirname } = require('path');
const { tmpdir } = require('os');
const { readdir } = require('fs').promises;
const { cwd } = require('process');
const exts = ['.ts'];
const excludes = [
'/node_modules/',
'/examples/',
'/dist/',
'/jsx.d.ts',
'/mod.d.ts',
'/deno.d.ts',
];
const regExpRemoveExts = /\.(ts|tsx|js|jsx)$/i;
const tsconfig = {
compilerOptions: {
types: ['node'],
module: 'commonjs',
declaration: true,
removeComments: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
allowSyntheticDefaultImports: true,
target: 'es6',
sourceMap: true,
outDir: join(cwd(), 'nodejs'),
baseUrl: './',
},
};
// const distFolder = join(tmpdir(), `deno2nodejs-${+new Date()}`);
const distFolder = join(cwd(), `tmp`);
console.log('distFolder:', distFolder);
fs.mkdirSync(distFolder);
fs.writeFileSync(join(distFolder, 'tsconfig.json'), JSON.stringify(tsconfig));
async function getFiles(dir) {
const dirents = await readdir(dir, { withFileTypes: true });
const files = await Promise.all(
dirents.map((dirent) => {
const res = resolve(dir, dirent.name);
return dirent.isDirectory() ? getFiles(res) : res;
}),
);
return Array.prototype.concat(...files);
}
getFiles('./').then((files) => {
const tsFiles = files.filter(
(f) =>
exts.includes(extname(f)) &&
!excludes.some((val) => f.includes(val)),
);
tsFiles.forEach((file) => {
const code = deno2nodejs(file);
const dist = join(distFolder, file.substr(cwd().length));
const ensureDir = dirname(dist);
fs.mkdirSync(ensureDir, { recursive: true });
fs.writeFileSync(dist, code);
// console.log({ file, dist, ensureDir });
});
});
// we might want to execute `tsc -p ./tmp/tsconfig.json` in here
function deno2nodejs(file) {
const source = fs.readFileSync(file).toString();
const ast = parse(source, {
sourceType: 'module',
plugins: ['typescript', 'classProperties'],
});
traverse(ast, {
ImportDeclaration: function ImportDeclaration(path) {
var source = path.node.source;
if (!source.value.match(regExpRemoveExts)) {
return;
}
source.value = source.value.replace(regExpRemoveExts, '');
},
ExportDeclaration: function ExportDeclaration(path) {
var source = path.node.source;
if (source) {
if (!source.value.match(regExpRemoveExts)) {
return;
}
source.value = source.value.replace(regExpRemoveExts, '');
}
},
});
const { code } = generator(ast);
return code;
}

View File

@ -1,6 +0,0 @@
/// <reference path="https://raw.githubusercontent.com/apiel/jsx-html/latest/jsx.d.ts" />
import { React } from 'https://raw.githubusercontent.com/apiel/jsx-html/latest/mod.ts';
const View = () => <div>Hello</div>;
console.log((<View />).render());

View File

@ -1,62 +0,0 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { React, Fragment } from '../mod.ts';
const Title = () => <h1>title</h1>;
const Value = ({ val }: { val: string }) => <p>value: {val}</p>;
const Numeric = ({ num }: { num: number }) => <p>num: {num}</p>;
const View = () => (
<div class="deno">
<Title />
<p onclick={() => 'lol'} valid checked={true} select="">
land
</p>
<br />
<hr />
<Fragment>
<Value val="hello" />
<Numeric num={23} />
</Fragment>
</div>
);
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
// Run test
Deno.test('render title', async() => {
assertEquals(await (<Title />).render(), '<h1>title</h1>');
});
Deno.test('render value', async() => {
const val = 'hello';
assertEquals(await (<Value val={val} />).render(), `<p>value: ${val}</p>`);
});
Deno.test('render numeric', async() => {
const num = 123;
assertEquals(await (<Numeric num={num} />).render(), `<p>num: ${num}</p>`);
});
Deno.test('render view', async() => {
assertEquals(
await (<View />).render(),
'<div class="deno"><h1>title</h1><p valid checked select>land</p><br /><hr /><p>value: hello</p><p>num: 23</p></div>',
);
});
Deno.test('render empty', async ()=>{
assertEquals(
await (<div/>).render(),
`<div></div>`
)
assertEquals(
await (<img/>).render(),
`<img />`
)
})
}

View File

@ -1,6 +0,0 @@
/// <reference path="../../jsx.d.ts" />
import { jsx } from '../../mod.ts';
const View = () => <div>Hello</div>;
(<View />).render().then(console.log);

View File

@ -1,6 +0,0 @@
{
"compilerOptions": {
"jsx": "react",
"jsxFactory": "jsx"
}
}

View File

@ -1,30 +0,0 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { delay } from 'https://deno.land/std/async/delay.ts';
import { React } from '../mod.ts';
const Title = async () => {
await delay(100);
return <h1>title{ await delay(100) }</h1>;
};
const View = () => (
<div>
<Title />
</div>
);
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
// Run test
Deno.test('render title', async () => {
assertEquals(await (<Title />).render(), '<h1>title</h1>');
});
Deno.test('render view', async () => {
assertEquals(await (<View />).render(), '<div><h1>title</h1></div>');
});
}

View File

@ -1,9 +0,0 @@
/// <reference path="../jsx.d.ts" />
import { React } from '../mod.ts';
const View = () => <div>Hello</div>;
if (import.meta.main) {
(<View />).render().then(console.log);
}

View File

@ -1,27 +0,0 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { React } from '../mod.ts';
const View = () => {
const techs = ['NodeJS', 'React Native', 'Next'];
return (
<ul>
{techs.map((tech: any) => (
<li>{tech}</li>
))}
</ul>
);
};
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
Deno.test('render with array', async () => {
assertEquals(
await (<View />).render(),
'<ul><li>NodeJS</li><li>React Native</li><li>Next</li></ul>',
);
});
}

View File

@ -1,19 +0,0 @@
/// <reference path="../jsx.d.ts" />
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
import { React } from '../mod.ts';
const View = () => {
return <div innerHTML="<b>hello</b> world" />;
};
if (import.meta.main) {
(<View />).render().then(console.log);
} else {
Deno.test('render with array', async () => {
assertEquals(
await (<View />).render(),
'<div><b>hello</b> world</div>',
);
});
}

View File

@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<script src="index.js"></script>
<div id="div-container" />
<script>
demo.Test().render('#div-container').then((html) => {
document.getElementById('div-container').innerHTML = html;
});
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -1,25 +0,0 @@
{
"name": "jsx-to-html-testing",
"version": "1.0.0",
"description": "jsx-to-html-testing",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "webpack --watch",
"start": "webpack-dev-server --open",
"build": "webpack"
},
"keywords": [],
"author": "Alex",
"license": "ISC",
"devDependencies": {
"ts-loader": "^8.0.1",
"typescript": "^3.9.7",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"dependencies": {
"async-jsx-html": "^1.2.1"
}
}

View File

@ -1,5 +0,0 @@
import { jsx, ElementNode } from 'async-jsx-html';
export function Test(): ElementNode {
return <div>Hello World</div>;
}

View File

@ -1,15 +0,0 @@
{
"compilerOptions": {
"outDir": "./dist/",
"module": "commonjs",
"jsx": "react",
"jsxFactory": "jsx",
"esModuleInterop": true,
"sourceMap": true,
"allowJs": true,
"lib": [
"es6",
"dom"
]
}
}

View File

@ -1,33 +0,0 @@
const path = require('path');
const MODULE_NAME = 'demo';
module.exports = {
mode: 'development',
entry: {
index: './src/index.tsx'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist'
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: [ '.tsx', '.ts', '.js' ],
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
libraryTarget: 'umd',
globalObject: 'this',
// libraryExport: 'default',
library: MODULE_NAME
},
};

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +0,0 @@
let {
React,
Fragment
} = require('../nodejs/mod');
const Title = () => /*#__PURE__*/React.createElement("h1", null, "title");
const Value = ({
val
}) => /*#__PURE__*/React.createElement("p", null, "value: ", val);
const Numeric = ({
num
}) => /*#__PURE__*/React.createElement("p", null, "num: ", num);
const View = () => /*#__PURE__*/React.createElement("div", {
class: "deno"
}, /*#__PURE__*/React.createElement(Title, null), /*#__PURE__*/React.createElement("p", {
onclick: () => 'lol',
valid: true,
checked: true,
select: ""
}, "land"), /*#__PURE__*/React.createElement("br", null), /*#__PURE__*/React.createElement("hr", null), /*#__PURE__*/React.createElement(Fragment, null, /*#__PURE__*/React.createElement(Value, {
val: "hello"
}), /*#__PURE__*/React.createElement(Numeric, {
num: 23
})));
/*#__PURE__*/
React.createElement(View, null).render().then(console.log);

View File

@ -1,22 +0,0 @@
let { React, Fragment } = require('../nodejs/mod');
const Title = () => <h1>title</h1>;
const Value = ({ val }) => <p>value: {val}</p>;
const Numeric = ({ num }) => <p>num: {num}</p>;
const View = () => (
<div class="deno">
<Title />
<p onclick={() => 'lol'} valid checked={true} select="">
land
</p>
<br />
<hr />
<Fragment>
<Value val="hello" />
<Numeric num={23} />
</Fragment>
</div>
);
(<View />).render().then(console.log);

5
jsx-html/jsx.d.ts vendored
View File

@ -1,5 +0,0 @@
declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}

View File

@ -1,33 +0,0 @@
import type {
NodePropsType,
ComponentFunctionType,
NullableChildType,
ChildType,
} from './types.ts';
import { ElementNode } from './node/ElementNode.ts';
import { ComponentNode } from './node/ComponentNode.ts';
export const jsx = <P extends NodePropsType = NodePropsType>(
element: string | ComponentFunctionType,
props: P | null,
...children: NullableChildType[]
) => {
const nodeProps = props || {};
if (typeof element === 'string') {
return new ElementNode(element, nodeProps, children);
}
if (typeof element === 'function') {
return new ComponentNode(element, nodeProps, children);
}
throw new TypeError(`Expected jsx element to be a string or a function`);
};
export const Fragment = (
props: NodePropsType,
children: ChildType,
): NullableChildType => {
return children;
};

View File

@ -1,12 +0,0 @@
{
"name": "jsx-html",
"version": "0.0.1",
"description": "",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"files": [
"**/*.ts",
"**/*.js",
"README.md"
]
}

View File

@ -1,27 +0,0 @@
import { jsx, Fragment } from './jsx.ts';
import type {
ComponentFunctionType,
NodePropsType,
NullableChildType,
} from './types.ts';
export { ElementNode } from './node/ElementNode.ts';
export { ComponentNode } from './node/ComponentNode.ts';
export type {
jsx,
Fragment,
ComponentFunctionType,
NullableChildType,
NodePropsType,
};
export const React = {
Fragment,
createElement<P extends NodePropsType = NodePropsType>(
element: string | ComponentFunctionType,
props: P | null,
...children: NullableChildType[]
) {
return jsx(element, props, ...children);
},
};

View File

@ -1,37 +0,0 @@
import type {
NodePropsType,
ComponentFunctionType,
NullableChildType,
} from '../types.ts';
import { NODE_TYPE } from '../constants.ts';
import { FragmentNode } from './FragmentNode.ts';
import { Node } from './Node.ts';
import { normalizeChildren } from './utils/normalizeChildren.ts';
export class ComponentNode extends Node {
type = NODE_TYPE.COMPONENT;
constructor(
public component: ComponentFunctionType,
public props: NodePropsType,
children: NullableChildType[],
) {
super(children);
}
async render(): Promise<string | any[]> {
return [].concat((await this.renderComponent()) as any).join('');
}
async renderComponent() {
const child = await this.component(this.props, this.children);
const children = normalizeChildren(
Array.isArray(child) ? child : [child],
);
if (children.length === 1) {
return children[0].render();
} else if (children.length > 1) {
return new FragmentNode(children).render();
}
}
}

View File

@ -1,91 +0,0 @@
import { NODE_TYPE } from '../constants.ts';
import type { NodePropsType, NullableChildType } from '../types.ts';
import { Node } from './Node.ts';
import { doubleQuoteEncode } from './utils/htmlEncode.ts';
const ELEMENT_PROP = {
INNER_HTML: 'innerHTML',
};
// List taken from http://w3c.github.io/html-reference/syntax.html
const VOID_ELEMENTS = new Set<string>([
'area',
'base',
'br',
'col',
'command',
'embed',
'hr',
'img',
'input',
'keygen',
'link',
'meta',
'param',
'source',
'track',
'wbr',
]);
export class ElementNode extends Node {
type = NODE_TYPE.ELEMENT;
constructor(
public name: string,
public props: NodePropsType,
children: NullableChildType[],
) {
super(children);
}
async render(): Promise<string | any[]> {
const renderedProps = this.propsToHTML();
const renderedChildren =
typeof this.props[ELEMENT_PROP.INNER_HTML] === 'string'
? this.props[ELEMENT_PROP.INNER_HTML]
: (await this.renderChildren()).join('');
return renderedChildren || !VOID_ELEMENTS.has(this.name)
? `<${this.name}${renderedProps}>${renderedChildren || ''}</${
this.name
}>`
: `<${this.name}${renderedProps} />`;
}
private getValidProps() {
const props = this.props;
return Object.keys(this.props).filter((key) => {
if (key === ELEMENT_PROP.INNER_HTML) {
return false;
}
const val = props[key];
return (
typeof val === 'string' ||
typeof val === 'number' ||
val === true
);
});
}
private propsToHTML(): string {
const keys = this.getValidProps();
if (!keys.length) {
return '';
}
const props = this.props;
const pairs = keys.map((key) => {
if (!/^[a-zA-Z0-9-:\._]+$/.test(key)) {
throw new Error(`Invalid attribute name format ${key}`);
}
const val = props[key];
// https://html.spec.whatwg.org/multipage/dom.html#attributes
return val === true || val === ''
? key
: `${key}="${doubleQuoteEncode(val.toString())}"`;
});
return ` ${pairs.join(' ')}`;
}
}

View File

@ -1,15 +0,0 @@
import { NODE_TYPE } from '../constants.ts';
import type { ChildNodeType } from '../types.ts';
import { Node } from './Node.ts';
export class FragmentNode extends Node {
type = NODE_TYPE.FRAGMENT;
constructor(children: ChildNodeType[]) {
super(children);
}
render() {
return this.renderChildren();
}
}

View File

@ -1,29 +0,0 @@
import type { NODE_TYPE } from '../constants.ts';
import type { NullableChildType } from '../types.ts';
import { normalizeChildren } from './utils/normalizeChildren.ts';
export abstract class Node {
abstract type: NODE_TYPE;
constructor(public children: NullableChildType[]) {}
abstract async render(): Promise<string | any[]>;
async renderChildren() {
const result: string[] = [];
const children = normalizeChildren(this.children);
for (const child of children) {
const renderedChild = await child.render();
if (renderedChild) {
if (Array.isArray(renderedChild)) {
renderedChild.forEach(
(subchild) => subchild && result.push(subchild),
);
} else {
result.push(renderedChild);
}
}
}
return result;
}
}

View File

@ -1,12 +0,0 @@
import { NODE_TYPE } from '../constants.ts';
import { htmlEncode } from './utils/htmlEncode.ts';
export class TextNode {
type = NODE_TYPE.TEXT;
constructor(public text: string) {}
async render(): Promise<string | any[]> {
return htmlEncode(this.text);
}
}

View File

@ -1,13 +0,0 @@
export function doubleQuoteEncode(text: string): string {
return text
.replace(/"/g, '&quot;')
}
export function htmlEncode(text: string): string {
return doubleQuoteEncode(text
.replace(/&/g, '&amp;')
.replace(/\//g, '&#x2F;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#39;'));
}

View File

@ -1,31 +0,0 @@
import type { NullableChildType, ChildNodeType } from '../../types.ts';
import { TextNode } from '../TextNode.ts';
import { NODE_TYPE } from '../../constants.ts';
export function normalizeChildren(
children: NullableChildType[],
): ChildNodeType[] {
const result: any[] = [];
for (const child of children) {
if (child && typeof child !== 'boolean') {
if (typeof child === 'string' || typeof child === 'number') {
result.push(new TextNode(`${child}`));
} else if (Array.isArray(child)) {
normalizeChildren(child).forEach((normalized) =>
result.push(normalized),
);
} else if (
child.type === NODE_TYPE.ELEMENT ||
child.type === NODE_TYPE.TEXT ||
child.type === NODE_TYPE.COMPONENT
) {
result.push(child);
} else {
throw new TypeError(`Unrecognized node type: ${typeof child}`);
}
}
}
return result;
}

View File

@ -1,6 +0,0 @@
export declare enum NODE_TYPE {
ELEMENT = "element",
TEXT = "text",
COMPONENT = "component",
FRAGMENT = "fragment"
}

View File

@ -1,12 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NODE_TYPE = void 0;
var NODE_TYPE;
(function (NODE_TYPE) {
NODE_TYPE["ELEMENT"] = "element";
NODE_TYPE["TEXT"] = "text";
NODE_TYPE["COMPONENT"] = "component";
NODE_TYPE["FRAGMENT"] = "fragment";
})(NODE_TYPE = exports.NODE_TYPE || (exports.NODE_TYPE = {}));
;
//# sourceMappingURL=constants.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../tmp/constants.ts"],"names":[],"mappings":";;;AAAA,IAAY,SAKX;AALD,WAAY,SAAS;IACnB,gCAAmB,CAAA;IACnB,0BAAa,CAAA;IACb,oCAAuB,CAAA;IACvB,kCAAqB,CAAA;AACvB,CAAC,EALW,SAAS,GAAT,iBAAS,KAAT,iBAAS,QAKpB;AACD,CAAC"}

View File

@ -1,5 +0,0 @@
import { NodePropsType, ComponentFunctionType, NullableChildType, ChildType } from "./types";
import { ElementNode } from "./node/ElementNode";
import { ComponentNode } from "./node/ComponentNode";
export declare const jsx: <P extends NodePropsType = NodePropsType>(element: string | ComponentFunctionType, props: P, ...children: NullableChildType[]) => ElementNode | ComponentNode;
export declare const Fragment: (props: NodePropsType, children: ChildType) => NullableChildType;

View File

@ -1,19 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Fragment = exports.jsx = void 0;
const ElementNode_1 = require("./node/ElementNode");
const ComponentNode_1 = require("./node/ComponentNode");
exports.jsx = (element, props, ...children) => {
const nodeProps = props || {};
if (typeof element === 'string') {
return new ElementNode_1.ElementNode(element, nodeProps, children);
}
if (typeof element === 'function') {
return new ComponentNode_1.ComponentNode(element, nodeProps, children);
}
throw new TypeError(`Expected jsx element to be a string or a function`);
};
exports.Fragment = (props, children) => {
return children;
};
//# sourceMappingURL=jsx.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"jsx.js","sourceRoot":"","sources":["../tmp/jsx.ts"],"names":[],"mappings":";;;AACA,oDAAiD;AACjD,wDAAqD;AACxC,QAAA,GAAG,GAAG,CAA0C,OAAuC,EAAE,KAAe,EAAE,GAAG,QAA6B,EAAE,EAAE;IACzJ,MAAM,SAAS,GAAG,KAAK,IAAI,EAAE,CAAC;IAE9B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE;QAC/B,OAAO,IAAI,yBAAW,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;KACtD;IAED,IAAI,OAAO,OAAO,KAAK,UAAU,EAAE;QACjC,OAAO,IAAI,6BAAa,CAAC,OAAO,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC;KACxD;IAED,MAAM,IAAI,SAAS,CAAC,mDAAmD,CAAC,CAAC;AAC3E,CAAC,CAAC;AACW,QAAA,QAAQ,GAAG,CAAC,KAAoB,EAAE,QAAmB,EAAqB,EAAE;IACvF,OAAO,QAAQ,CAAC;AAClB,CAAC,CAAC"}

View File

@ -1,9 +0,0 @@
import { jsx, Fragment } from "./jsx";
import { ComponentFunctionType, NodePropsType, NullableChildType } from "./types";
export { ElementNode } from "./node/ElementNode";
export { ComponentNode } from "./node/ComponentNode";
export { jsx, Fragment, ComponentFunctionType, NullableChildType, NodePropsType };
export declare const React: {
Fragment: (props: NodePropsType, children: import("./types").ChildType) => NullableChildType;
createElement<P extends NodePropsType = NodePropsType>(element: string | ComponentFunctionType, props: P, ...children: NullableChildType[]): import("./node/ElementNode").ElementNode | import("./node/ComponentNode").ComponentNode;
};

View File

@ -1,17 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.React = exports.Fragment = exports.jsx = void 0;
const jsx_1 = require("./jsx");
Object.defineProperty(exports, "jsx", { enumerable: true, get: function () { return jsx_1.jsx; } });
Object.defineProperty(exports, "Fragment", { enumerable: true, get: function () { return jsx_1.Fragment; } });
var ElementNode_1 = require("./node/ElementNode");
Object.defineProperty(exports, "ElementNode", { enumerable: true, get: function () { return ElementNode_1.ElementNode; } });
var ComponentNode_1 = require("./node/ComponentNode");
Object.defineProperty(exports, "ComponentNode", { enumerable: true, get: function () { return ComponentNode_1.ComponentNode; } });
exports.React = {
Fragment: jsx_1.Fragment,
createElement(element, props, ...children) {
return jsx_1.jsx(element, props, ...children);
}
};
//# sourceMappingURL=mod.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../tmp/mod.ts"],"names":[],"mappings":";;;AAAA,+BAAsC;AAI7B,oFAJA,SAAG,OAIA;AAAE,yFAJA,cAAQ,OAIA;AAFtB,kDAAiD;AAAxC,0GAAA,WAAW,OAAA;AACpB,sDAAqD;AAA5C,8GAAA,aAAa,OAAA;AAET,QAAA,KAAK,GAAG;IACnB,QAAQ,EAAR,cAAQ;IAER,aAAa,CAA0C,OAAuC,EAAE,KAAe,EAAE,GAAG,QAA6B;QAC/I,OAAO,SAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,QAAQ,CAAC,CAAC;IAC1C,CAAC;CAEF,CAAC"}

View File

@ -1,11 +0,0 @@
import { NodePropsType, ComponentFunctionType, NullableChildType } from "../types";
import { NODE_TYPE } from "../constants";
import { Node } from "./Node";
export declare class ComponentNode extends Node {
component: ComponentFunctionType;
props: NodePropsType;
type: NODE_TYPE;
constructor(component: ComponentFunctionType, props: NodePropsType, children: NullableChildType[]);
render(): Promise<string | any[]>;
renderComponent(): Promise<string | any[]>;
}

View File

@ -1,43 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ComponentNode = void 0;
const constants_1 = require("../constants");
const FragmentNode_1 = require("./FragmentNode");
const Node_1 = require("./Node");
const normalizeChildren_1 = require("./utils/normalizeChildren");
class ComponentNode extends Node_1.Node {
constructor(component, props, children) {
super(children);
this.component = component;
this.props = props;
this.type = constants_1.NODE_TYPE.COMPONENT;
}
render() {
return __awaiter(this, void 0, void 0, function* () {
return [].concat(yield this.renderComponent()).join('');
});
}
renderComponent() {
return __awaiter(this, void 0, void 0, function* () {
const child = yield this.component(this.props, this.children);
const children = normalizeChildren_1.normalizeChildren(Array.isArray(child) ? child : [child]);
if (children.length === 1) {
return children[0].render();
}
else if (children.length > 1) {
return new FragmentNode_1.FragmentNode(children).render();
}
});
}
}
exports.ComponentNode = ComponentNode;
//# sourceMappingURL=ComponentNode.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"ComponentNode.js","sourceRoot":"","sources":["../../tmp/node/ComponentNode.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,4CAAyC;AACzC,iDAA8C;AAC9C,iCAA8B;AAC9B,iEAA8D;AAC9D,MAAa,aAAc,SAAQ,WAAI;IAGrC,YAAmB,SAAgC,EAAS,KAAoB,EAAE,QAA6B;QAC7G,KAAK,CAAC,QAAQ,CAAC,CAAC;QADC,cAAS,GAAT,SAAS,CAAuB;QAAS,UAAK,GAAL,KAAK,CAAe;QAFhF,SAAI,GAAG,qBAAS,CAAC,SAAS,CAAC;IAI3B,CAAC;IAEK,MAAM;;YACV,OAAO,EAAE,CAAC,MAAM,CAAE,MAAM,IAAI,CAAC,eAAe,EAAU,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;KAAA;IAEK,eAAe;;YACnB,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9D,MAAM,QAAQ,GAAG,qCAAiB,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAE3E,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE;gBACzB,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;aAC7B;iBAAM,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;gBAC9B,OAAO,IAAI,2BAAY,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,CAAC;aAC5C;QACH,CAAC;KAAA;CAEF;AAtBD,sCAsBC"}

View File

@ -1,12 +0,0 @@
import { NODE_TYPE } from "../constants";
import { NodePropsType, NullableChildType } from "../types";
import { Node } from "./Node";
export declare class ElementNode extends Node {
name: string;
props: NodePropsType;
type: NODE_TYPE;
constructor(name: string, props: NodePropsType, children: NullableChildType[]);
render(): Promise<string | any[]>;
private getValidProps;
private propsToHTML;
}

View File

@ -1,60 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ElementNode = void 0;
const constants_1 = require("../constants");
const Node_1 = require("./Node");
const htmlEncode_1 = require("./utils/htmlEncode");
const ELEMENT_PROP = {
INNER_HTML: 'innerHTML'
};
class ElementNode extends Node_1.Node {
constructor(name, props, children) {
super(children);
this.name = name;
this.props = props;
this.type = constants_1.NODE_TYPE.ELEMENT;
}
render() {
return __awaiter(this, void 0, void 0, function* () {
const renderedProps = this.propsToHTML();
const renderedChildren = typeof this.props[ELEMENT_PROP.INNER_HTML] === 'string' ? this.props[ELEMENT_PROP.INNER_HTML] : (yield this.renderChildren()).join('');
return renderedChildren ? `<${this.name}${renderedProps}>${renderedChildren}</${this.name}>` : `<${this.name}${renderedProps} />`;
});
}
getValidProps() {
const props = this.props;
return Object.keys(this.props).filter(key => {
if (key === ELEMENT_PROP.INNER_HTML) {
return false;
}
const val = props[key];
return typeof val === 'string' || typeof val === 'number' || val === true;
});
}
propsToHTML() {
const keys = this.getValidProps();
if (!keys.length) {
return '';
}
const props = this.props;
const pairs = keys.map(key => {
if (!/^[a-zA-Z0-9-:\._]+$/.test(key)) {
throw new Error(`Invalid attribute name format ${key}`);
}
const val = props[key];
return val === true || val === '' ? key : `${key}="${htmlEncode_1.doubleQuoteEncode(val.toString())}"`;
});
return ` ${pairs.join(' ')}`;
}
}
exports.ElementNode = ElementNode;
//# sourceMappingURL=ElementNode.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"ElementNode.js","sourceRoot":"","sources":["../../tmp/node/ElementNode.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAyC;AAEzC,iCAA8B;AAC9B,mDAAuD;AACvD,MAAM,YAAY,GAAG;IACnB,UAAU,EAAE,WAAW;CACxB,CAAC;AACF,MAAa,WAAY,SAAQ,WAAI;IAGnC,YAAmB,IAAY,EAAS,KAAoB,EAAE,QAA6B;QACzF,KAAK,CAAC,QAAQ,CAAC,CAAC;QADC,SAAI,GAAJ,IAAI,CAAQ;QAAS,UAAK,GAAL,KAAK,CAAe;QAF5D,SAAI,GAAG,qBAAS,CAAC,OAAO,CAAC;IAIzB,CAAC;IAEK,MAAM;;YACV,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACzC,MAAM,gBAAgB,GAAG,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChK,OAAO,gBAAgB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,aAAa,IAAI,gBAAgB,KAAK,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,GAAG,aAAa,KAAK,CAAC;QACpI,CAAC;KAAA;IAEO,aAAa;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;YAC1C,IAAI,GAAG,KAAK,YAAY,CAAC,UAAU,EAAE;gBACnC,OAAO,KAAK,CAAC;aACd;YAED,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,WAAW;QACjB,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QAElC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;YAChB,OAAO,EAAE,CAAC;SACX;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;YAC3B,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;gBACpC,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC;aACzD;YAED,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;YAEvB,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,8BAAiB,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC;QAC5F,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAC/B,CAAC;CAEF;AA7CD,kCA6CC"}

View File

@ -1,8 +0,0 @@
import { NODE_TYPE } from "../constants";
import { ChildNodeType } from "../types";
import { Node } from "./Node";
export declare class FragmentNode extends Node {
type: NODE_TYPE;
constructor(children: ChildNodeType[]);
render(): Promise<string[]>;
}

View File

@ -1,16 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FragmentNode = void 0;
const constants_1 = require("../constants");
const Node_1 = require("./Node");
class FragmentNode extends Node_1.Node {
constructor(children) {
super(children);
this.type = constants_1.NODE_TYPE.FRAGMENT;
}
render() {
return this.renderChildren();
}
}
exports.FragmentNode = FragmentNode;
//# sourceMappingURL=FragmentNode.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"FragmentNode.js","sourceRoot":"","sources":["../../tmp/node/FragmentNode.ts"],"names":[],"mappings":";;;AAAA,4CAAyC;AAEzC,iCAA8B;AAC9B,MAAa,YAAa,SAAQ,WAAI;IAGpC,YAAY,QAAyB;QACnC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAHlB,SAAI,GAAG,qBAAS,CAAC,QAAQ,CAAC;IAI1B,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,cAAc,EAAE,CAAC;IAC/B,CAAC;CAEF;AAXD,oCAWC"}

View File

@ -1,9 +0,0 @@
import { NODE_TYPE } from "../constants";
import { NullableChildType } from "../types";
export declare abstract class Node {
children: NullableChildType[];
abstract type: NODE_TYPE;
constructor(children: NullableChildType[]);
abstract render(): Promise<string | any[]>;
renderChildren(): Promise<string[]>;
}

View File

@ -1,38 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Node = void 0;
const normalizeChildren_1 = require("./utils/normalizeChildren");
class Node {
constructor(children) {
this.children = children;
}
renderChildren() {
return __awaiter(this, void 0, void 0, function* () {
const result = [];
const children = normalizeChildren_1.normalizeChildren(this.children);
for (const child of children) {
const renderedChild = yield child.render();
if (renderedChild) {
if (Array.isArray(renderedChild)) {
renderedChild.forEach(subchild => subchild && result.push(subchild));
}
else {
result.push(renderedChild);
}
}
}
return result;
});
}
}
exports.Node = Node;
//# sourceMappingURL=Node.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"Node.js","sourceRoot":"","sources":["../../tmp/node/Node.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,iEAA8D;AAC9D,MAAsB,IAAI;IAGxB,YAAmB,QAA6B;QAA7B,aAAQ,GAAR,QAAQ,CAAqB;IAAG,CAAC;IAI9C,cAAc;;YAClB,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,qCAAiB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAElD,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE;gBAC5B,MAAM,aAAa,GAAG,MAAM,KAAK,CAAC,MAAM,EAAE,CAAC;gBAE3C,IAAI,aAAa,EAAE;oBACjB,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE;wBAChC,aAAa,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;qBACtE;yBAAM;wBACL,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;qBAC5B;iBACF;aACF;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;KAAA;CAEF;AA1BD,oBA0BC"}

View File

@ -1,7 +0,0 @@
import { NODE_TYPE } from "../constants";
export declare class TextNode {
text: string;
type: NODE_TYPE;
constructor(text: string);
render(): Promise<string | any[]>;
}

View File

@ -1,27 +0,0 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextNode = void 0;
const constants_1 = require("../constants");
const htmlEncode_1 = require("./utils/htmlEncode");
class TextNode {
constructor(text) {
this.text = text;
this.type = constants_1.NODE_TYPE.TEXT;
}
render() {
return __awaiter(this, void 0, void 0, function* () {
return htmlEncode_1.htmlEncode(this.text);
});
}
}
exports.TextNode = TextNode;
//# sourceMappingURL=TextNode.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"TextNode.js","sourceRoot":"","sources":["../../tmp/node/TextNode.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,4CAAyC;AACzC,mDAAgD;AAChD,MAAa,QAAQ;IAGnB,YAAmB,IAAY;QAAZ,SAAI,GAAJ,IAAI,CAAQ;QAF/B,SAAI,GAAG,qBAAS,CAAC,IAAI,CAAC;IAEY,CAAC;IAE7B,MAAM;;YACV,OAAO,uBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;KAAA;CAEF;AATD,4BASC"}

View File

@ -1,2 +0,0 @@
export declare function doubleQuoteEncode(text: string): string;
export declare function htmlEncode(text: string): string;

View File

@ -1,12 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.htmlEncode = exports.doubleQuoteEncode = void 0;
function doubleQuoteEncode(text) {
return text.replace(/"/g, '&quot;');
}
exports.doubleQuoteEncode = doubleQuoteEncode;
function htmlEncode(text) {
return doubleQuoteEncode(text.replace(/&/g, '&amp;').replace(/\//g, '&#x2F;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/'/g, '&#39;'));
}
exports.htmlEncode = htmlEncode;
//# sourceMappingURL=htmlEncode.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"htmlEncode.js","sourceRoot":"","sources":["../../../tmp/node/utils/htmlEncode.ts"],"names":[],"mappings":";;;AAAA,SAAgB,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtC,CAAC;AAFD,8CAEC;AACD,SAAgB,UAAU,CAAC,IAAY;IACrC,OAAO,iBAAiB,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;AACpJ,CAAC;AAFD,gCAEC"}

View File

@ -1,2 +0,0 @@
import { NullableChildType, ChildNodeType } from "../../types";
export declare function normalizeChildren(children: NullableChildType[]): ChildNodeType[];

View File

@ -1,27 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeChildren = void 0;
const TextNode_1 = require("../TextNode");
const constants_1 = require("../../constants");
function normalizeChildren(children) {
const result = [];
for (const child of children) {
if (child && typeof child !== 'boolean') {
if (typeof child === 'string' || typeof child === 'number') {
result.push(new TextNode_1.TextNode(`${child}`));
}
else if (Array.isArray(child)) {
normalizeChildren(child).forEach(result.push);
}
else if (child.type === constants_1.NODE_TYPE.ELEMENT || child.type === constants_1.NODE_TYPE.TEXT || child.type === constants_1.NODE_TYPE.COMPONENT) {
result.push(child);
}
else {
throw new TypeError(`Unrecognized node type: ${typeof child}`);
}
}
}
return result;
}
exports.normalizeChildren = normalizeChildren;
//# sourceMappingURL=normalizeChildren.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"normalizeChildren.js","sourceRoot":"","sources":["../../../tmp/node/utils/normalizeChildren.ts"],"names":[],"mappings":";;;AACA,0CAAuC;AACvC,+CAA4C;AAC5C,SAAgB,iBAAiB,CAAC,QAA6B;IAC7D,MAAM,MAAM,GAAU,EAAE,CAAC;IAEzB,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE;QAC5B,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE;YACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;gBAC1D,MAAM,CAAC,IAAI,CAAC,IAAI,mBAAQ,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;aACvC;iBAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;gBAC/B,iBAAiB,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;aAC/C;iBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAS,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAS,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAS,CAAC,SAAS,EAAE;gBAClH,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;aACpB;iBAAM;gBACL,MAAM,IAAI,SAAS,CAAC,2BAA2B,OAAO,KAAK,EAAE,CAAC,CAAC;aAChE;SACF;KACF;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAlBD,8CAkBC"}

View File

@ -1,15 +0,0 @@
import { ElementNode } from "./node/ElementNode";
import { TextNode } from "./node/TextNode";
import { ComponentNode } from "./node/ComponentNode";
import { FragmentNode } from "./node/FragmentNode";
export declare type NodePropsType = {
[key: string]: any;
};
declare type Primitive = string | boolean | number;
declare type NullablePrimitive = Primitive | null | void;
export declare type ChildNodeType = ElementNode | TextNode | ComponentNode;
export declare type NodeType = ChildNodeType | FragmentNode;
export declare type ChildType = ChildNodeType | Primitive;
export declare type NullableChildType = ChildType | ChildNodeType | NullablePrimitive;
export declare type ComponentFunctionType = (props: NodePropsType, child?: NullableChildType[]) => NullableChildType | Promise<NullableChildType>;
export {};

View File

@ -1,3 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../tmp/types.ts"],"names":[],"mappings":""}

View File

@ -1,16 +0,0 @@
deno test examples/01.tsx
deno run examples/00.tsx
deno run examples/01.tsx
deno run -c examples/02/tsconfig.json examples/02/02.tsx
https://deno.land/manual/contributing/style_guide
- would be great to make it as well node compatible:
tsc examples/01.tsx --jsx react --outDir dist && node dist/examples/01.js
git tag --delete latest
git push --delete origin latest
git tag latest
git push --tags

View File

@ -1,25 +0,0 @@
{
"name": "async-jsx-html",
"version": "1.2.1",
"main": "nodejs/mod.js",
"types": "nodejs/mod.d.ts",
"repository": "git@github.com:apiel/jsx-html.git",
"author": "Alexandre Piel <alexandre.piel@gmail.com>",
"license": "MIT",
"scripts": {
"clean": "rm -rf nodejs/ && rm -rf tmp/",
"build": "yarn clean && node deno2nodejs.js && tsc -p ./tmp/tsconfig.json",
"transpile": "babel --no-babelrc --plugins @babel/plugin-transform-react-jsx ./examples/node_01.jsx -o ./examples/node_01.js",
"start": "yarn transpile && node ./examples/node_01.js"
},
"devDependencies": {
"@babel/cli": "^7.10.3",
"@babel/core": "^7.10.3",
"@babel/generator": "^7.10.3",
"@babel/parser": "^7.10.3",
"@babel/plugin-transform-react-jsx": "^7.10.3",
"@babel/traverse": "^7.10.3",
"@types/node": "^14.0.13",
"typescript": "^3.9.5"
}
}

View File

@ -1,23 +0,0 @@
import type { ElementNode } from './node/ElementNode.ts';
import type { TextNode } from './node/TextNode.ts';
import type { ComponentNode } from './node/ComponentNode.ts';
import type { FragmentNode } from './node/FragmentNode.ts';
export type NodePropsType = {
[key: string]: any;
};
type Primitive = string | boolean | number;
type NullablePrimitive = Primitive | null | void;
export type ChildNodeType = ElementNode | TextNode | ComponentNode;
export type NodeType = ChildNodeType | FragmentNode;
export type ChildType = ChildNodeType | Primitive;
export type NullableChildType = ChildType | ChildNodeType | NullablePrimitive;
export type ComponentFunctionType = (
props: NodePropsType,
child?: NullableChildType[],
) => NullableChildType | Promise<NullableChildType>;

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
Denreg JSX renderer
**deprecated**
**DO NOT USE**

View File

@ -1,13 +1,9 @@
{
"name": "@denreg-jsx",
"version": "0.0.3",
"description": "Denreg JSX renderer",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"deprecated": true,
"files": [
"**/*.ts",
"**/*.js",
"README.md"
]
}
"name": "@denreg-jsx",
"version": "0.1.0",
"description": "Denreg JSX renderer",
"author": "Fabian Stamm <dev@fabianstamm.de>",
"contributors": [],
"deprecated": false,
"files": ["**/*.ts", "**/*.js", "tsconfig.json", "README.md"]
}

133
jsx/mod.ts Normal file
View File

@ -0,0 +1,133 @@
import "./types.ts";
const Fragment = Symbol("fragment");
declare namespace JSX {
interface Element {}
interface IntrinsicElements {
div: any;
}
}
export { Fragment };
export type Element = {
component: Component | string;
props: any;
children: any[];
};
export type ComponentRetElm = Element | Element[];
export type Component = (
props: any,
children: any
) => ComponentRetElm | Promise<ComponentRetElm>;
export function h(
component: string | Component,
props: any,
...children: Element[]
): Element {
return {
component,
props,
children,
};
}
export async function renderSSR(element: Element | string): Promise<string> {
if (typeof element === "string") return element;
else if (typeof element.component === "string")
return await renderHTML(element as Element);
else if (typeof element.component === "function")
return await renderCustom(element as Element);
console.warn("renderSSR: invalid element", element);
return "";
}
const selfClosing = new Set([
"area",
"base",
"br",
"col",
"embed",
"hr",
"img",
"input",
"link",
"meta",
"param",
"source",
"track",
"wbr",
]);
function flatDeep(arr: any, d = Infinity): any[] {
if (Array.isArray(arr) && d >= 0) {
let res = [];
for (const val of arr) {
const v = flatDeep(val, d - 1);
res.push(...v);
}
return res;
}
return [arr];
}
function cleanChildren(children: any) {
return flatDeep(children).filter((e) => !!e);
}
async function renderHTML(element: Element) {
if (typeof element.component !== "string")
throw new Error("Internal consistency error");
console.log("Element:", element.component);
let props = "";
for (const key in element.props) {
if (key == "innerHTML") continue;
props += `${key}="${element.props[key] || ""}" `;
}
const tag = element.component;
if (selfClosing.has(element.component)) {
return `<${tag} ${props}/>`;
} else {
let inner = "";
if (element.props && element.props["innerHTML"]) {
inner = element.props["innerHTML"];
} else {
const children = cleanChildren(element.children);
if (tag == "body") console.log(element.children, children);
inner = (
await Promise.all(children.map((child) => renderSSR(child)))
).join("");
}
return `<${tag} ${props}>${inner || ""}</${tag}>`;
}
}
async function renderCustom(element: Element) {
if (typeof element.component === "string")
throw new Error("Internal consistency error");
console.log("Component:", element.component);
const res = await Promise.resolve(
element.component(
{
...element.props,
children: element.children,
},
element.children
)
);
const ch = (
await Promise.all(cleanChildren(res).map((child) => renderSSR(child)))
).join("");
return ch;
}

7
jsx/tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext", "deno.ns", "deno.unstable"],
"jsxFactory": "h",
"strictPropertyInitialization": false
}
}

11
jsx/types.ts Normal file
View File

@ -0,0 +1,11 @@
declare namespace JSX {
interface IntrinsicElements {
[elemName: string]: any;
}
}
declare namespace JSX {
interface ElementClass {
render: any;
}
}