0.2.9 • Published 5 years ago

mantium v0.2.9

Weekly downloads
13
License
MIT
Repository
github
Last release
5 years ago

Mantium

Logo

This library demonstrates how to use JSX without React. It can be used as a learning tool to understand the internals of front-end frameworks using functional components/closures instead of classes. Many of the constructs included are very similiar/identical to Mithril (routing, requests), React (useState and useEffect), and I'm sure other modern libraries.

Installation

You can use either via a CDN or via a node module. Both JavaScript and TypeScript are supported. The types are included in the node module.

CDN

<script src="https://unpkg.com/mantium/dist/index.js"></script>
<script>
    const m = mantium.m;
    m.render(document.body, "hello world");
</script>

NPM

If you need a webpack setup for TypeScript with ESLint that supports JSX, you can use this template.

npm install mantium -S
import { m } from 'mantium';

m.render(document.body, 'hello world');

Features

This library supports these features:

  • Render function for JSX
  • HyperScript support
  • JSX using TypeScript (.tsx)
  • JSX using Babel (.jsx)
  • JSX fragments
  • JSX declarations/interfaces for: IntrinsicElements, Element, ElementChildrenAttribute
  • JSX children access via attributes (typing available using interfaces)
  • JSX attribute access (using interfaces)
  • JSX functional components
  • JSX class components
  • JSX as children in JSX components
  • JSX keys for loops
  • JSX attributes for strings
  • JSX attributes for booleans (like 'required') - this needs testing
  • Sort out class vs className
  • Test forceUpdate for event handlers
  • JSX event handling for 'on' functions
  • Virtual DOM
  • Reactivity
  • Redrawing on click events
  • Local variable state using 'useState'
  • Router
  • 404 page
  • Hash URL prefix handling
  • Router and virtual DOM handling
  • Virtual DOM handling of fragments at top level
  • Add Link to handle changing pages for URLs that don't include the hash
  • Support history handling on page URLs
  • Support regex on routes for authentication
  • Request Handling for JSON
  • Request handling for non-JSON
  • Handle redraws on requests to ensure loop don't occur
  • Remove all circular dependencies
  • Add useEffect which triggers after a redraw to support lifecycle methods of creation and destruction
  • On useEffect, allow specifying when to update (onLoad, on variable change, etc)
  • Add redraw after request (doesn't alway work, especially with nested requested, but if useing useState then it will)
  • Add redraw on setter from useState
  • Ability to batch setState commands to prevent rendering after each update
  • Allow useState to pass in function to get previous value
  • Add simplified useContext without provider
  • Easy way to view output of generated code (npm run build-clean)
  • Performance testing
  • Add Jest
  • Generate test coverage
  • Unit tests
  • Clean up the types
  • Launch on NPM to see how the process works
  • Publish on NPM in standalone JavaScript file format (Rollup)
  • Publish on NPM in CommonJS format (Rollup)

Code Samples

Sample code is here. npm package is here.

Render Content

const m = mantium.m;

m.render(document.body, 'hello world');
m.render(document.body, true);

Routing

import { m } from 'mantium';

m.state.routerPrefix = '#';
m.route(rootElem, '/', MainPage);
m.route(rootElem, '/app', UITestPage);
m.route(rootElem, '/404', ErrorPage);

Components using JSX

import { m } from 'mantium';

export const BooleanFlip = (): JSX.Element => {
  const [isBool, setBool] = m.useState(false);
  return (
    <>
      <button
        onclick={() => {
          setBool((prev) => !prev);
        }}
      >
        Change Boolean Value
      </button>
      <div>Current value: {isBool}</div>
    </>
  );
};

m.render(document.body, BooleanFlip);

Components using HyperScript

const m = mantium.m;
const h = mantium.m.createElement;

function MainPage() {
    return h('div', {}, 'hello world');
}

m.render(document.body, MainPage);

Fragments

import { m } from 'mantium';

export const FragLevel1 = (): JSX.Element => {
  return (
    <>
      <div>Fragment level 1.</div>
      <FragLevel2 />
    </>
  );
};

export const FragLevel2 = (): JSX.Element => {
  return (
    <>
      <div>Fragment level 2.</div>
    </>
  );
};

m.render(document.body, FragLevel1);

Fragments without JSX

const m = mantium.m;
const h = mantium.m.createElement;

function FragLevel1() {
  return h('FRAGMENT', {},
    h('div', {}, 'Fragment level 1.'),
    h(FragLevel2));
}

function FragLevel2() {
  return h('FRAGMENT', {},
    h('div', {}, 'Fragment level 2.'));
}

m.render(document.body, FragLevel1);

Passing Attributes and Children

import { m } from 'mantium';

export const App = (): JSX.Element => {
  return (
    <div class='app'>
      <FragmentChild num1='10A' num2='10B'>
        <div>Text should be in a div.</div>
      </FragmentChild>
    </div>
  );
};

interface FragmentsAttrs {
  num1: string;
  num2: string;
  children: JSX.Element | string;
}

const FragmentChild = (attrs: FragmentsAttrs): JSX.Element => {
  return (
    <>
      <div name={attrs.num1}>div {attrs.num1}</div>
      {attrs.children}
      <div name={attrs.num2}>div {attrs.num2}</div>
    </>
  );
};

const rootElem = document.createElement('div');
rootElem.setAttribute('id', 'root');
document.body.appendChild(rootElem);
m.render(rootElem, App);

Redrawing and Event Handlers

import { m } from 'mantium';

export const RedrawButtons = (): JSX.Element => {
  const [count, setCount] = m.useState(0);
  return (
    <>
      <button
        onclick={() => {
          setTimeout(() => {
            setCount((prev) => prev + 1);
          }, 1000);
        }}
      >
        1 Second Timer with setState [auto redraw] ({count} clicks)
      </button>

      <button
        onclick={() => {
          m.redraw();
        }}
      >
        Manual Redraw
      </button>

      <button
        onclick={() => {
          setTimeout(() => {
            globalCounter++;
          }, 1000);
        }}
      >
        1 Second Timer on Global Variable [requires manual redraw] (
        {globalCounter} clicks)
      </button>
    </>
  );
};

m.render(document.body, RedrawButtons);

Requests and useEffect

import { m } from 'mantium';

interface PostResponse {
  userId: number;
  id: number;
  title: string;
  body: string;
}

interface UserResponse {
  id: number;
  name: string;
  username: string;
  email: string;
  address: {
    street: string;
    suite: string;
    city: string;
    zipcode: string;
    geo: {
      lat: string;
      lng: string;
    };
  };
  phone: string;
  website: string;
  company: {
    name: string;
    catchPhrase: string;
    bs: string;
  };
}

export const JSONRequest = (): JSX.Element => {
  const [getPost, setPost] = m.useState({} as PostResponse);
  const [getUser, setUser] = m.useState({} as UserResponse);

  m.useEffect(() => {
    m.request<PostResponse>({
      url: 'https://jsonplaceholder.typicode.com/posts/5',
    })
      .then((data: PostResponse) => {
        setPost(data);

        return m.request<UserResponse>({
          url: `https://jsonplaceholder.typicode.com/users/${data.userId}`,
        });
      })
      .then((udata: UserResponse) => {
        setUser(udata);
      })
      .catch((error: Response) => {
        console.warn(error);
      });
  }, []);

  return (
    <>
      <a title='home' href='#/'>
        Back
      </a>
      <h1>Title: {getPost.title}</h1>
      <h2>By: {getUser.name}</h2>
      <i>Post ID: {getPost.id}</i>
      <p>{getPost.body}</p>
    </>
  );
};

m.render(document.body, JSONRequest);

Meiosis Pattern for State Management

You can read about it here.

import { m } from 'mantium';

interface StateAttrs {
  count: number;
  sqr: number;
}

const State = (): StateAttrs => ({
  count: 0,
  sqr: 0,
});

const Actions = (
  S: StateAttrs,
  A = {
    sqr: () => (S.sqr = S.count ** 2),
    inc: () => {
      S.count++;
      A.sqr();
    },
    dec: () => {
      S.count--;
      A.sqr();
    },
  },
) => A;

export const Meiosis = (): JSX.Element => {
  const [state] = m.useState(State());
  const [actions] = m.useState(Actions(state));

  return (
    <>
      <button
        onclick={() => {
          actions.inc();
          // Requires redraw if not interacting with useState setter directly.
          m.redraw();
        }}
      >
        Add
      </button>
      <button
        onclick={() => {
          actions.dec();
          // Requires redraw if not interacting with useState setter directly.
          m.redraw();
        }}
      >
        Subtract
      </button>
      <div>
        Current value: {state.count} | Squared: {state.sqr}
      </div>
    </>
  );
};

m.render(document.body, Meiosis);

useContext

import { m } from 'mantium';

const UserContext = m.createContext('monkey');

const ContextChild = (): JSX.Element => {
  const [value, setValue] = m.useContext(UserContext);
  return (
    <>
      <div>Child value: {value}</div>
      <button
        onclick={() => {
          setValue('duck');
        }}
      >
        Change 2
      </button>
    </>
  );
};

m.render(document.body, ContextChild);
0.2.9

5 years ago

0.2.7

5 years ago

0.2.8

5 years ago

0.2.6

5 years ago

0.2.5

5 years ago

0.2.4

5 years ago

0.2.3

5 years ago

0.2.2

5 years ago

0.2.1

5 years ago

0.2.0

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago