npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ignis-web/server-jsx-component

v0.0.15

Published

A minimalistic framework for creating reusable and encapsulated JSX view components on server side

Downloads

9

Readme

Server JSX components

Actions Status Coverage

A minimalistic framework for creating reusable and encapsulated html components on server side with the help of JSX. It uses standard abilities🚀 of typescript compiler to work with JSX. For projects written on JavaScript (without TypeScript) may use to convert JSX Babel. The framework uses ideology of CssInJs for work with css and support ability inline JavaScript code in components.

Install

npm i @ignis-web/server-jsx-component -S

Navigation:

Function component

import { getJsxFactory, render, JSX } from '@ignis-web/server-jsx-component';
// Create render function. TypeScript compiler uses her for transform jsx elements to html
const h = getJsxFactory();

const FormatDate = (props: { date: Date, style: string }) => {
  const { date, style } = props;
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();
  const dateStr = `${year}-${month}-${day}`;
  return <time datetime={dateStr} style={style}>{dateStr}</time>;
};

// second argument for turn on escape mode
const obj = render.toObject(
  <FormatDate date={new Date()} style="color:red"></FormatDate>,
  true
);
// <time datetime="2023-10-7" style="color:red">2023-10-7</time>
console.log(obj.html);

Class component

import { getJsxFactory, render, IgnisComp } from '@ignis-web/server-jsx-component';
// Create render function. TypeScript compiler uses her for transform jsx elements to html
const h = getJsxFactory();

class FormatDate extends IgnisComp<{ date: Date, style: string }> {
  render() {
    const { date, style } = this.props;
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    const dateStr = `${year}-${month}-${day}`;
    return <time datetime={dateStr} style={style}>{dateStr}</time>;
  }
}

// second argument for turn on escape mode
const obj = render.toObject(
  <FormatDate date={new Date()} style="color:red"></FormatDate>,
  true
);
// <time datetime="2023-10-7" style="color:red">2023-10-7</time>
console.log(obj.html);

IgnisComp (IgnisComp<Props, Children, TSharedData>) take 3 types: type of properties of component, type of children and type of shared data (for inline javascript code).

If you want render JSX to html not to object then you need method toHtmlPage:

import {
  getJsxFactory,
  render,
  JSX,
  IgnisComp,
  IgnisHtmlPage
} from '@ignis-web/server-jsx-component';
// Create render function. TypeScript compiler uses her for transform jsx elements to html
const h = getJsxFactory();

// For components witout parent:
// https://react.dev/reference/react/Fragment
const Fragment = function (_: any, children: any[]): JSX.Element {
  return <fragment>{children}</fragment>;
};

class FormatDate extends IgnisComp<{ date: Date, style: string }> {
  render() {
    const { date, style } = this.props;
    const year = date.getFullYear();
    const month = date.getMonth() + 1;
    const day = date.getDate();
    const dateStr = `${year}-${month}-${day}`;
    return <time datetime={dateStr} style={style}>{dateStr}</time>;
  }
}

class HtmlPage extends IgnisHtmlPage<{}> {
  render() {
    return <Fragment>{this.children}</Fragment>;
  }
}

const html = render.toHtmlPage(
  <HtmlPage>
    <FormatDate date={new Date()} style="color:red"></FormatDate>
  </HtmlPage>,
  { escape: true }
);
/* <!DOCTYPE html>
<html lang="EN">
<head>
  <title>Hello, I am page with IgnisComponent !</title>
  <meta name="description" content="">
  <meta name="keywords" content="">
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body><time datetime="2023-10-9" style="color:red">2023-10-9</time></body>
</html> */
console.log(html);

More details

Css

You can create css classes in components: in object style (CssInJS) or as simple string. Class names are generated with built-in generator:

import {
  getJsxFactory,
  render,
  IgnisComp
} from '@ignis-web/server-jsx-component';

const h = getJsxFactory();

class Book extends IgnisComp<
  { id: number, author: string, name: string, year: number }
> {
  render() {
    const { id, author, name, year } = this.props;
    // Create css class as css in js.
    // Class name is generated automatically
    const cl_book = this.css({
      color: 'red',
      // properties as camel case
      fontSize: '12px',
      '&:focus': {
        // properties as kebab case
        'background-color': 'orange'
      }
    });

    // Create css class as css in js, but use specific class name
    const cl_author = this.css('list-book__author', {
      textTransform: 'capitalize'
    });

    // Create css class as string.
    // Class name is generated automatically
    this.css('.list-book__name{font-size: 16px}');

    return (
      <div class={cl_book}>
        <p class="list-book__name">Name: {name}</p>
        <p class={cl_author}>Author: {author}</p>
        <p>Year: {year}</p>
      </div>
    );
  }
}

// second argument for turn on escape mode
const obj = render.toObject(
  <Book id={1} author="Ivan Turgenev" name="Hunter's Notes" year={1852}></Book>,
  true
);
/*
<div class="a">
  <p class="list-book__name">Name: Hunter&#39;s Notes</p>
  <p class="list-book__author">Author: Ivan Turgenev</p>
  <p>Year: 1852</p>
</div>
*/
console.log(obj.html);
/*
[
  CssClass {
    _name: 'a',
    _obj: { color: 'red', fontSize: '12px', '&:focus': [Object] }
  },
  CssClass {
    _name: 'list-book__author',
    _obj: { textTransform: 'capitalize' }
  },
  '.list-book__name{font-size: 16px}'
]
*/
console.log(obj.css);
Note:

You shouldn't create css inside block if/else statement:

  if (Math.random() > 0.5) {
    const cl_author = this.css('list-book__author', {
      textTransform: 'capitalize'
    });
  }

It will not work!

JavaScript

With methods headJs() and js() you can add javascript code in component as JSX block code, as string, or as script tag:

import {
  getJsxFactory,
  render,
  IgnisComp,
  JSX
} from '@ignis-web/server-jsx-component';

const h = getJsxFactory();

// Component for wrapping inline js code
const JsCode = function (
  props: { escape?: boolean }, children: any[]
): JSX.Element {
  // XSS escape is disable by default
  return <fragment escape={props.escape || false}>{children}</fragment>;
};

class Book extends IgnisComp<
  { id: number; name: string; author: string; year: number },
  never,
  // Third parameter is type of shared data
  { id: number }
> {

  // javascript for all components Book. It will be place in <head></head>
  headJs() {
    // get all ids for components Book
    // Array<{ id: number }>
    const data = this.getListSharedData();
    return [
      // Inject inline code as JSX
      <JsCode>
        console.log('Escaped value {`${this.escape('<body>')}`}');
      </JsCode>,
      // Inject script script src="..."
      this.script('/assets/book.js'),
      // Inject inline code as string
      `[${data.map(el => el.id).join(',')}].forEach(id => new Book(id);`
    ];
  }

  // javascript for all components Book.
  // It will be place before tag <body> close
  js() {
    return [
      // Inject inline code as string
      'console.log("I am book in footer!!!");',
    ];
  }


  render() {
    const { id, name, author, year } = this.props;

    // You can store shared data for specific type
    // components (in this case Book) which were used on page
    const id = this.setSharedData('id', id);
    // OR set whole of object
    const { id } = this.setSharedData({ id });

    return (
      <div id={id}>
        <p class="list-book__name">Name: {name}</p>
        <p>Author: {author}</p>
        <p>Year: {year}</p>
      </div>
    );
  }
}


const books = [
  { id: 1, author: 'Ivan Turgenev', name: 'Hunter\'s Notes', year: 1852 },
  { id: 2, author: 'Jack London', name: 'White Fang', year: 1906 }
];
const obj = render.toObject(
  <div>
    {books.map(el =>
      <Book
        id={el.id}
        author={el.author}
        name={el.name}
        year={el.year}
      ></Book>
    )}
  </div>,
// second argument for turn on escape mode
  true
);
/*
<div>
  <div>
    <p class="list-book__name">Name: Hunter&#39;s Notes</p>
    <p>Author: Ivan Turgenev</p>
    <p>Year: 1852</p>
  </div>
  <div>
    <p class="list-book__name">Name: White Fang</p>
    <p>Author: Jack London</p>
    <p>Year: 1906</p>
  </div>
</div>
*/
console.log(obj.html);
/*
[
  "console.log('Escaped value &lt;body&gt;');",
  Script {
    _src: '/assets/book.js',
    _howLoad: '',
    _code: '',
    _onloadFuncName: '',
    _onloadCb: null
  },
  '[1,2].forEach(id => new Book(id);'
]
*/
console.log(obj.headJs);
/*
[ 'console.log("I am book in footer!!!");' ]
*/
console.log(obj.js);
  • headJs() - this method is intended for declaration javascript for all specific type components (in this case for all components of "Book" type). Tags <script> will be placed in <head></head>.
  • js() - this method is similar to headJS(), but tags <script> will be placed before tag <body> close.
  • this.setSharedData(key, value): value | this.setSharedData(sharedData): sharedData - We can store shared data for specific type components (in this case for all components of "Book" type), which were used on the html page. For example, we save id of books and get them in declaration of javascript code for using in our business logic. You can set specific part of shared data by key or set whole state.
  • this.getListSharedData() - We get shared data as array (instance of components may be many on page).

Css and JavaScript in Function component

import { getJsxFactory, render, IgnisComp, JSX } from '@ignis-web/server-jsx-component';

const h = getJsxFactory();

// Component for wrapping inline js code
const JsCode = function (
  props: { escape?: boolean }, children: any[]
): JSX.Element {
  return <fragment escape={props.escape || false}>{children}</fragment>;
};

const FormatDate = (props: { date: Date, style: string }) => {
  // pass type of shared data
  IgnisComp.setDataForFuncComponent<{ id: number }>(FormatDate, {
    // set css
    css() {
      this.css('.time{color: green}');
      this.css(this.cssLink('http://my.domain/css/base.css'));
      this.css({
        fontSize: '12px',
        backgroundColor: 'red',
      });
    },
    // set shared data
    sharedData: { id: Date.now() },
    headJs() {
      return [
        this.script('http://cool-timepicker.js'),
        `console.log('Ids of TimePicker ${
          JSON.stringify(this.getSharedData())
        }');`,
      ]
    },
    js() {
      return [
        <JsCode>
          console.log('TimePicker init {JSON.stringify(this.getSharedData())}');
        </JsCode>
      ]
    },
  });
  const { date, style } = props;
  const year = date.getFullYear();
  const month = date.getMonth() + 1;
  const day = date.getDate();
  const dateStr = `${year}-${month}-${day}`;
  return <time datetime={dateStr} style={style}>{dateStr}</time>;
};

const obj = render.toObject(
  <FormatDate date={new Date()} style="color:red"></FormatDate>,
  true
);
// <time datetime="2023-10-7" style="color:red">2023-10-7</time>
console.log(obj.html);
/*
[
  '.time{color: green}',
  CssLink {
    _href: 'http://my.domain/css/base.css',
    _rel: 'stylesheet',
    _type: 'text/css'
  },
  CssClass {
    _name: 'a',
    _obj: { fontSize: '12px', backgroundColor: 'red' }
  }
]
*/
console.log(obj.css);
/*
[
  Script {
    _src: 'http://cool-timepicker.js',
    _howLoad: '',
    _code: '',
    _onloadFuncName: '',
    _onloadCb: null
  },
  `console.log('Ids of TimePicker [{"id":1696852720137}]');`
]
*/
console.log(obj.headJs);
/*
[ `console.log('TimePicker init [{"id":1696852720137}]');` ]
*/
console.log(obj.js);

Script and Link

There are useful set of methods in component

this.css(this.cssLink('https://cdn.jsdelivr.net/npm/[email protected]/css/bulma-rtl.min.css'))
// <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/bulma-rtl.min.css"/>

Method this.cssLink is used for convenient creation of tag <link>.

this.script('https://cdnjs.cloudflare.com/ajax/libs/highcharts/9.3.2/highcharts.js')
  .async()
  .onload('console.log("Highcharts is loading");')

Method this.script is used for convenient creation of tag <script>.

  • this.script(src?) - to create tag with link. Src is optional
  • async() - to add attribute async
  • defer() - to add attribute defer
  • onload(string) - to add attribute onload with passed js code
  • code(string) - to add js code inside <script></script>

Html Page

If you don't want manually building html page you can using special class IgnisHtmlPage which collecting html code of components and inserting all of needed css and js code. For example, we create three components: HtmlPage(inherited from IgnisHtmlPage), List Book, Book and you will see the basic capabilities such as component approach and isolated css in js.

import { getJsxFactory, render, IgnisComp, IgnisHtmlPage, noEscape, JSX } from '@ignis-web/server-jsx-component';

const h = getJsxFactory();

// Component for wrapping inline js code
const JsCode = function (
  props: { escape?: boolean }, children: any[]
): JSX.Element {
  return <fragment escape={props.escape || false}>{children}</fragment>;
};
// For components witout parent: https://react.dev/reference/react/Fragment
const Fragment = function (_: any, children: any[]): JSX.Element {
  return <fragment>{children}</fragment>;
};

class HtmlPage extends IgnisHtmlPage<{}> {

  // turn on/of minificaton of output html
  minify() {
    return true;
  }

  title() {
    return 'Jsx Server component';
  }

  description() {
    return 'It\'s description of page';
  }

  keywords() {
    return `
      keyword1,
      keyword2
    `;
  }


  // global javascript for section: It will be place in <head></head>
  headJs() {
    return [
      this.script('https://cdnjs.cloudflare.com/ajax/libs/highcharts/9.3.2/highcharts.js')
        .async()
        .onload('console.log("Highcharts is loading");')
    ];
  }

  // global javascript for footer.
  // It will be place before tag <body> close
  js() {
    return [
      'console.log("I am run in end of page");',
    ];
  }

  render() {
    const id = this.createId();
    const cls = this.createClassName();


    this.css(
      this.cssLink('https://cdn.jsdelivr.net/npm/[email protected]/css/bulma-rtl.min.css')
    );
    this.css('.column', { display: 'flex', borderLeft: '12px solid red' });

    return (
      <Fragment>
        <div id={id} class={'columns ' + cls}>
          {this.children}
        </div>
      </Fragment>
    )
  }
}

class ListBook extends IgnisComp<
  {
    title: JSX.Element,
    books: Array<{ id: number; name: string; author: string; year: number }>
  }
> {
  render() {
    const { title, books } = this.props;
    return (
      <div>
        {title}
        <p>Count: {books.length}</p>
        {/* Collection of methods for handy work with JSX elements: forEach, if/else/else if, switch/case, each and etc. */}
        {this.tpl.forEach(books, el => <Book data={el}></Book>)}
      </div>
    );
  }
}

class Book extends IgnisComp<
  { data: { id: number; name: string; author: string; year: number } },
  never,
  { id: number }
> {

  // javascript for all components Book.
  // It will be place in <head></head>
  headJs() {
    // get all ids for components Book
    const data = this.getListSharedData();
    return [
      <JsCode>
        console.log('Escaped value {`${this.escape('<body>')}`}');
      </JsCode>,
      this.script('/assets/book.js'),
      `[${data.map(el => el.id).join(',')}].forEach(id => new Book(id);`
    ];
  }

  // javascript for all components Book.
  // It will be place before tag <body> close
  js() {
    return [
      'console.log("I am book in footer!!!");',
    ];
  }


  render() {
    const { data: { id, name, author, year } } = this.props;

    // create class via css in js
    const cl_book = this.css({
      color: 'red',
      '&:focus': {
        'background-color': 'orange'
      }
    });

    const cl_author = this.css('list-book__author', {
      textTransform: 'capitalize'
    });

    // create class as simple string
    this.css('.list-book__name{font-size: 16px}');

    // You can store shared data for specific type
    // components (in this case Book) which were used on page
    this.setSharedData('id', id);

    return (
      <div class={cl_book}>
        <p class="list-book__name">Name: {name}</p>
        <p class={cl_author}>Author: {author}</p>
        <p>Year: {year} ${'\n\n\n'}</p>
      </div>
    );
  }
}

const books = [
  { id: 1, author: 'Leo Tolstoy', name: 'War and Peace', year: 1863 },
  { id: 2, author: 'Jack London', name: 'White Fang', year: 1906 }
];
const htmlPage: JSX.ElementPage = (
  <HtmlPage
  // You can redefine meta tags for page via attributes
  /*
    title='Test'
    description='Test'
    keywords='Test'
  */
  >
    <div class="column">
      <ListBook
        books={books}
        title={<h1>User's list books:</h1>}>
      </ListBook>
      {/* Disable escape XSS content for value */ }
      <div
        data-el-data={noEscape(JSON.stringify({ key: 'key', name: '<script></script>' }))}
      ></div>
    </div>
    {/* Disable escape XSS content for node */ }
    <script noEscape>
      console.log('console.log');
    </script>
  </HtmlPage>
);
const html = render.toHtmlPage(htmlPage, { escape: true });
// <!DOCTYPE html><html lang="EN"><head><title>Jsx Server component</title>...</body></html>
console.log(html);

More examples

Tpl

Collection of methods for handy work with JSX elements: forEach, if/else/else if, switch/case, each, class and etc.

forEach:
<div>
  {this.tpl.forEach(books, el => <Book data={el}></Book>)}
</div>
if/else/else if:
<div>
  {this.tpl
    .if(value === 1, () => <p>is if</p>)
    .elseIf(value === 2, <p>is else if</p>)
    .else(() => `<p>is else</p>`)
  }
</div>

Passing result of condition to callback:

const object: { key1: string } | { key2: number } = { key1: 'ssdf' } as any;
<div>
  {this.tpl
    .if('key1' in object && object.key1, value =>
      <p>object has key1, value = {value}, type value is string</p>
    )
    .elseIf('key2' in object && object.key2, value =>
      <p>object has key2, value = {value}, type value is number</p>
    )
    .else(() => `<p>is unknown object</p>`)
  }
</div>

Work with attributes:

<span {...this.tpl
  .if('key1' in object && object.key1, (value) => ({ class: `key1 ${value}` }))
  .elseIf('key2' in object && object.key2, (value) => ({ class: `default ${value}` }))
  .else({ ...object })
}></span>

If you want use two or more conditions in attributes, you should call method getAsAttr() in the end:

<span
  {...this.tpl
    .if('key1' in object && object.key1, (value) => ({ class: `key1 ${value}` }))
    .elseIf('key2' in object && object.key2, (value) => ({ class: `default ${value}` }))
    .else({ ...object })
    .getAsAttr()
  }
  {...this.tpl
    .if(value === 1, id => ({ id }))
    .getAsAttr()
  }
></span>
switch/case
<div>
  {this.tpl
    .switch(value)
    .case(1, (val) => <p>is {val}</p>)
    .case([2,3], <p>is 2 or 3</p>)
    .default(() => `<p>is other</p>`)
  }
</div>

Work with attributes:

<span {...this.tpl
  .switch(value)
  .case(1, (val) => ({ class: `one ${val}` }))
  .case([2, 3], { class: 'two three' })
  .default({})
}></span>

If you want use two or more conditions in attributes, you should call method getAsAttr() in the end:

<span
  {...this.tpl
    .switch(value)
    .case(1, (val) => ({ class: `one ${val}` }))
    .case([2, 3], ({ class: 'two three' }))
    .default({})
    .getAsAttr()
  }
  {...this.tpl
    .swich(value)
    .case(1, id => ({ id }))
    .getAsAttr()
  }
></span>
each
<div>
  {tpl.each({ k1: value, k2: date }, (value, key, index) => {
    if (index > 0) {
      return;
    }
    return (
      <li>
        <FormatDate date={value} style="margin-top:10px" />
      </li>
    );
  })}
</div>
class

Method for create css classes:

<div>
  {/* <div class="danger"></div> */}
  <div {...this.tpl.class({ danger: true, success: false })}></div>
</div>
func

It's alternative IIFE:

<div>
  {this.tpl.func(() => {
    if (true) {
      return <div>True</div>;
    } else {
      return <div>False</div>;
    }
  })}
</div>

Tsconfig

For proper build project, you should add follow fields in tsconfig.json:

{
  "compilerOptions": {
    ...
    "lib": [
      "DOM"
    ],
    ...
    "jsx": "react",
    "jsxFactory": "h"
  }
}

jsx and jsxFactory are options for typescript compiler which translating jsx to javascript code. DOM is option for correct work of autocomplete for css properties (method this.css()).

If you want transform jsx to javascript wihout typescript compiler, you should use babel:

npm i babel-cli babel-plugin-transform-jsx -D

.babelrc

{
  "plugins": ["babel-plugin-transform-jsx"]
}

TODO

  • Add support handlers onlClick/onChange and etc