Skip to content

Client-Side JavaScript/TypeScript Library for Building Single Page Applications

License

Notifications You must be signed in to change notification settings

josephspurrier/mantium

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.umd.min.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);

About

Client-Side JavaScript/TypeScript Library for Building Single Page Applications

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published