├── .gitignore ├── .npmignore ├── .npmrc ├── LICENSE ├── README.md ├── cjs ├── async.js ├── index.js └── package.json ├── esm ├── async.js └── index.js ├── package.json └── test ├── async.js ├── context-async.js ├── context.js ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | .travis.yml 4 | node_modules/ 5 | rollup/ 6 | test/ 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020-present, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🦄 µland-ssr 2 | 3 | ![tiny island](https://raw.githubusercontent.com/WebReflection/uland/master/uland-head.jpg) 4 | 5 | **Social Media Photo by [Ben Klea](https://unsplash.com/@benkleaphoto) on [Unsplash](https://unsplash.com/)** 6 | 7 | *micro* land SSR, is [µland](https://github.com/WebReflection/uland#readme) API for SSR / DOM-less environments. 8 | 9 | It allows using same components code on the client and the server, producing *html* or *svg* streams. 10 | 11 | This module also exports `uland-ssr/async`, which is based on `uland-nofx/async`. 12 | 13 | 14 | ### 📣 Community Announcement 15 | 16 | Please ask questions in the [dedicated forum](https://webreflection.boards.net/) to help the community around this project grow ♥ 17 | 18 | --- 19 | 20 | ## API 21 | 22 | ```js 23 | import {Component, render, html, useState} from 'uland-ssr'; 24 | 25 | const Counter = Component((initialState) => { 26 | const [count, setCount] = useState(initialState); 27 | return html` 28 | `; 31 | }); 32 | 33 | // basic example, creates the expected output 34 | // minus online events (would need re-hydration) 35 | render(String, () => html` 36 |
37 | A bounce of counters.
38 | ${Counter(0)} ${Counter(1)} 39 |
40 | `); 41 | ``` 42 | -------------------------------------------------------------------------------- /cjs/async.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isArray} = require('uarray'); 3 | const { 4 | html: $html, 5 | svg: $svg, 6 | render: $render 7 | } = require('uhtml-ssr/async'); 8 | 9 | const html = async (template, ...values) => { 10 | await unrollValues(values); 11 | return $html(template, ...values); 12 | }; 13 | html.for = createFor($html); 14 | 15 | const svg = async (template, ...values) => { 16 | await unrollValues(values); 17 | return $svg(template, ...values); 18 | }; 19 | svg.for = createFor($svg); 20 | 21 | const render = async (where, what) => { 22 | const value = await (typeof what === 'function' ? what() : what); 23 | return $render( 24 | where, 25 | await (value instanceof Hook ? unroll(value) : value) 26 | ); 27 | }; 28 | 29 | exports.Component = Component; 30 | exports.render = render; 31 | exports.html = html; 32 | exports.svg = svg; 33 | 34 | (m => { 35 | exports.createContext = m.createContext; 36 | exports.useContext = m.useContext; 37 | exports.useCallback = m.useCallback; 38 | exports.useMemo = m.useMemo; 39 | exports.useEffect = m.useEffect; 40 | exports.useLayoutEffect = m.useLayoutEffect; 41 | exports.useReducer = m.useReducer; 42 | exports.useState = m.useState; 43 | exports.useRef = m.useRef; 44 | })(require('uhooks-nofx')); 45 | 46 | const unroll = ({f, c, a}) => f.apply(c, a); 47 | 48 | const unrollValues = async (values) => { 49 | const {length} = values; 50 | for (let i = 0; i < length; i++) { 51 | const hook = await values[i]; 52 | if (hook instanceof Hook) 53 | values[i] = await unroll(hook); 54 | else if (isArray(hook)) 55 | await unrollValues(hook); 56 | } 57 | }; 58 | 59 | function Component(f) { 60 | return function () { 61 | return new Hook(f, this, arguments); 62 | }; 63 | } 64 | 65 | function Hook(f, c, a) { 66 | this.f = f; 67 | this.c = c; 68 | this.a = a; 69 | } 70 | 71 | function createFor(uhtml) { 72 | return (e, id) => { 73 | return async (template, ...values) => { 74 | await unrollValues(values); 75 | return uhtml.for(e, id)(template, ...values); 76 | }; 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {isArray} = require('uarray'); 3 | const { 4 | html: $html, 5 | svg: $svg, 6 | render: $render 7 | } = require('uhtml-ssr'); 8 | 9 | const html = /*async*/ (template, ...values) => { 10 | /*await*/ unrollValues(values); 11 | return $html(template, ...values); 12 | }; 13 | html.for = createFor($html); 14 | 15 | const svg = /*async*/ (template, ...values) => { 16 | /*await*/ unrollValues(values); 17 | return $svg(template, ...values); 18 | }; 19 | svg.for = createFor($svg); 20 | 21 | const render = /*async*/ (where, what) => { 22 | const value = /*await*/ (typeof what === 'function' ? what() : what); 23 | return $render( 24 | where, 25 | /*await*/ (value instanceof Hook ? unroll(value) : value) 26 | ); 27 | }; 28 | 29 | exports.Component = Component; 30 | exports.render = render; 31 | exports.html = html; 32 | exports.svg = svg; 33 | 34 | (m => { 35 | exports.createContext = m.createContext; 36 | exports.useContext = m.useContext; 37 | exports.useCallback = m.useCallback; 38 | exports.useMemo = m.useMemo; 39 | exports.useEffect = m.useEffect; 40 | exports.useLayoutEffect = m.useLayoutEffect; 41 | exports.useReducer = m.useReducer; 42 | exports.useState = m.useState; 43 | exports.useRef = m.useRef; 44 | })(require('uhooks-nofx')); 45 | 46 | const unroll = ({f, c, a}) => f.apply(c, a); 47 | 48 | const unrollValues = /*async*/ (values) => { 49 | const {length} = values; 50 | for (let i = 0; i < length; i++) { 51 | const hook = /*await*/ values[i]; 52 | if (hook instanceof Hook) 53 | values[i] = /*await*/ unroll(hook); 54 | else if (isArray(hook)) 55 | /*await*/ unrollValues(hook); 56 | } 57 | }; 58 | 59 | function Component(f) { 60 | return function () { 61 | return new Hook(f, this, arguments); 62 | }; 63 | } 64 | 65 | function Hook(f, c, a) { 66 | this.f = f; 67 | this.c = c; 68 | this.a = a; 69 | } 70 | 71 | function createFor(uhtml) { 72 | return (e, id) => { 73 | return /*async*/ (template, ...values) => { 74 | /*await*/ unrollValues(values); 75 | return uhtml.for(e, id)(template, ...values); 76 | }; 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /cjs/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /esm/async.js: -------------------------------------------------------------------------------- 1 | import {isArray} from 'uarray'; 2 | import { 3 | html as $html, 4 | svg as $svg, 5 | render as $render 6 | } from 'uhtml-ssr/async'; 7 | 8 | const html = async (template, ...values) => { 9 | await unrollValues(values); 10 | return $html(template, ...values); 11 | }; 12 | html.for = createFor($html); 13 | 14 | const svg = async (template, ...values) => { 15 | await unrollValues(values); 16 | return $svg(template, ...values); 17 | }; 18 | svg.for = createFor($svg); 19 | 20 | const render = async (where, what) => { 21 | const value = await (typeof what === 'function' ? what() : what); 22 | return $render( 23 | where, 24 | await (value instanceof Hook ? unroll(value) : value) 25 | ); 26 | }; 27 | 28 | export {Component, render, html, svg}; 29 | 30 | export { 31 | createContext, useContext, 32 | useCallback, useMemo, 33 | useEffect, useLayoutEffect, 34 | useReducer, useState, useRef 35 | } from 'uhooks-nofx'; 36 | 37 | const unroll = ({f, c, a}) => f.apply(c, a); 38 | 39 | const unrollValues = async (values) => { 40 | const {length} = values; 41 | for (let i = 0; i < length; i++) { 42 | const hook = await values[i]; 43 | if (hook instanceof Hook) 44 | values[i] = await unroll(hook); 45 | else if (isArray(hook)) 46 | await unrollValues(hook); 47 | } 48 | }; 49 | 50 | function Component(f) { 51 | return function () { 52 | return new Hook(f, this, arguments); 53 | }; 54 | } 55 | 56 | function Hook(f, c, a) { 57 | this.f = f; 58 | this.c = c; 59 | this.a = a; 60 | } 61 | 62 | function createFor(uhtml) { 63 | return (e, id) => { 64 | return async (template, ...values) => { 65 | await unrollValues(values); 66 | return uhtml.for(e, id)(template, ...values); 67 | }; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /esm/index.js: -------------------------------------------------------------------------------- 1 | import {isArray} from 'uarray'; 2 | import { 3 | html as $html, 4 | svg as $svg, 5 | render as $render 6 | } from 'uhtml-ssr'; 7 | 8 | const html = /*async*/ (template, ...values) => { 9 | /*await*/ unrollValues(values); 10 | return $html(template, ...values); 11 | }; 12 | html.for = createFor($html); 13 | 14 | const svg = /*async*/ (template, ...values) => { 15 | /*await*/ unrollValues(values); 16 | return $svg(template, ...values); 17 | }; 18 | svg.for = createFor($svg); 19 | 20 | const render = /*async*/ (where, what) => { 21 | const value = /*await*/ (typeof what === 'function' ? what() : what); 22 | return $render( 23 | where, 24 | /*await*/ (value instanceof Hook ? unroll(value) : value) 25 | ); 26 | }; 27 | 28 | export {Component, render, html, svg}; 29 | 30 | export { 31 | createContext, useContext, 32 | useCallback, useMemo, 33 | useEffect, useLayoutEffect, 34 | useReducer, useState, useRef 35 | } from 'uhooks-nofx'; 36 | 37 | const unroll = ({f, c, a}) => f.apply(c, a); 38 | 39 | const unrollValues = /*async*/ (values) => { 40 | const {length} = values; 41 | for (let i = 0; i < length; i++) { 42 | const hook = /*await*/ values[i]; 43 | if (hook instanceof Hook) 44 | values[i] = /*await*/ unroll(hook); 45 | else if (isArray(hook)) 46 | /*await*/ unrollValues(hook); 47 | } 48 | }; 49 | 50 | function Component(f) { 51 | return function () { 52 | return new Hook(f, this, arguments); 53 | }; 54 | } 55 | 56 | function Hook(f, c, a) { 57 | this.f = f; 58 | this.c = c; 59 | this.a = a; 60 | } 61 | 62 | function createFor(uhtml) { 63 | return (e, id) => { 64 | return /*async*/ (template, ...values) => { 65 | /*await*/ unrollValues(values); 66 | return uhtml.for(e, id)(template, ...values); 67 | }; 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uland-ssr", 3 | "version": "0.5.0", 4 | "description": "Same uland API for SSR / DOM-less environments", 5 | "main": "./cjs/index.js", 6 | "scripts": { 7 | "build": "npm run async && npm run cjs && npm run test", 8 | "cjs": "ascjs esm cjs", 9 | "async": "cp esm/index.js esm/async.js && sed -i.bck 's/uhtml-ssr/uhtml-ssr\\/async/; s/\\/\\*async\\*\\//async/; s/\\/\\*await\\*\\//await/' esm/async.js && rm -rf esm/async.js.bck", 10 | "test": "node test/index.js && node test/async.js" 11 | }, 12 | "keywords": [ 13 | "uland", 14 | "ssr", 15 | "hooks" 16 | ], 17 | "author": "Andrea Giammarchi", 18 | "license": "ISC", 19 | "devDependencies": { 20 | "ascjs": "^5.0.1" 21 | }, 22 | "module": "./esm/index.js", 23 | "type": "module", 24 | "exports": { 25 | ".": { 26 | "import": "./esm/index.js", 27 | "default": "./cjs/index.js" 28 | }, 29 | "./async": { 30 | "import": "./esm/async.js", 31 | "default": "./cjs/async.js" 32 | }, 33 | "./package.json": "./package.json" 34 | }, 35 | "dependencies": { 36 | "uarray": "^1.0.0", 37 | "uhooks-nofx": "^0.2.0", 38 | "uhtml-ssr": "^0.5.0" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "git+https://github.com/WebReflection/uland-ssr.git" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/WebReflection/uland-ssr/issues" 46 | }, 47 | "homepage": "https://github.com/WebReflection/uland-ssr#readme" 48 | } 49 | -------------------------------------------------------------------------------- /test/async.js: -------------------------------------------------------------------------------- 1 | const {Component, render, html} = require('../cjs/async.js'); 2 | 3 | const Item = Component(value => html` 4 |
  • ${value}
  • 5 | `); 6 | 7 | const AsyncList = Component(async items => html` 8 | 11 | `); 12 | 13 | render({write: console.log}, AsyncList(Promise.all([1, 2, 3]))); 14 | -------------------------------------------------------------------------------- /test/context-async.js: -------------------------------------------------------------------------------- 1 | const { 2 | Component, 3 | createContext, 4 | html, 5 | render, 6 | useContext, 7 | } = require("../cjs/async"); 8 | 9 | const FooContext = createContext(); 10 | 11 | const FooComponent = Component(() => { 12 | const value = useContext(FooContext); 13 | return html`
    ${value}
    `; 14 | }); 15 | 16 | async function doRender(fooValue) { 17 | FooContext.provide(fooValue); 18 | 19 | // Realistically, a delay would happen inside rendering of complex components. 20 | await new Promise((resolve) => setTimeout(resolve, 1)); 21 | 22 | const renderedHtml = await render( 23 | String, 24 | () => 25 | html` 26 | 27 | ${FooComponent()} 28 | 29 | `, 30 | ); 31 | 32 | console.log(renderedHtml); 33 | } 34 | 35 | doRender("123"); 36 | doRender("456"); 37 | -------------------------------------------------------------------------------- /test/context.js: -------------------------------------------------------------------------------- 1 | const { 2 | Component, 3 | createContext, 4 | html, 5 | render, 6 | useContext, 7 | } = require("../cjs"); 8 | 9 | const FooContext = createContext(); 10 | 11 | const Inner = Component(() => { 12 | const value = useContext(FooContext); 13 | return html`
    ${value}
    `; 14 | }); 15 | 16 | const Outer = Component(({ fooValue, children }) => { 17 | FooContext.provide(fooValue); 18 | return html`
    ${children}
    `; 19 | }); 20 | 21 | const renderedHtml = render( 22 | String, 23 | () => 24 | html` 25 | 26 | ${Outer({ fooValue: "123", children: Inner() })} 27 | 28 | `, 29 | ); 30 | 31 | console.log(renderedHtml); 32 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const {Component, render, html, useState} = require('../cjs'); 2 | 3 | const Counter = Component((initialState) => { 4 | const [count, setCount] = useState(initialState); 5 | return html` 6 | `; 9 | }); 10 | 11 | console.log( 12 | render(String, () => html` 13 |
    14 | A bounce of counters.
    15 | ${Counter(0)} ${Counter(1)} 16 |
    17 | `) 18 | ); 19 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} --------------------------------------------------------------------------------