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

artworkjs

v2.0.6

Published

A front-end framework.

Downloads

7

Readme

Artwork

A small front-end framework that lets you easily write pure JavaScript and CSS, without any HTML. Artwork lets you write applications the way they were made before web applications came along. This results in much faster loading and execution times, less code to write, and no translation layers to get in the way.

Go to the art-project repository if you want to run the examples below or need a template for creating a new project.

npm install artworkjs

Examples

A very basic example that simply returns a native HTML element instead of a web component.

import { html } from 'artworkjs';

const hello = (name) => html.div(`Hello ${name}`);

A timer that uses connected, which is a function run when the custom element is added to the DOM. The return value is run when the element is removed from the DOM.

import { html } from 'artworkjs';

const timer = () => {
  let seconds = 0;

  const div = html.div();

  const tick = () => {
    div.innerText = `Seconds: ${seconds}`;
    seconds++;
  };

  tick();

  const connected = () => {
    const intervalId = setInterval(tick, 1000);
    return () => clearInterval(intervalId);
  };

  return html.register({
    root: div,
    connected,
    name: 'seconds-timer'
  });
}

A todo list.

import { html, htmlFor } from 'artworkjs';

const todo = () => {
  const { div, input, form, ul, h3, label, button } = html.create();

  h3.innerText = 'Todo';
  label.innerText = 'What needs to be done?';
  button.innerText = 'Add #1';

  htmlFor(label, input, 'new-todo');

  form.append(label, input, button);

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    if (input.value.length === 0) {
      return;
    }
    const item = html.li(input.value);
    ul.append(item);
    
    button.innerText = `Add #${ul.childElementCount + 1}`;
    input.value = '';
  });

  div.append(h3, ul, form);

  return html.register({
    root: div,
    styles: 'form > * { margin-right: 5px; }',
    name: 'todo-list'
  });
}

Creating elements

Components will often be composed of one or more HTML elements. Artwork provides multiple convenience methods for creating elements so that you don't have to do document.createElement('div'); each time.

Which method you use will depend on what you are trying to do and the way you write components. If you want to create an element with lots of attributes, it is often best to pass in a list of properties to html.tagName.

import { html } from 'artworkjs';

const city = html.div({
  className: 'city',
  innerText: 'Brisbane',
  title: 'Australia'
});

city will now be an HTMLDivElement that looks like this:

<div class="city" title="Australia">Brisbane</div>

Often, a component will have a bunch of div elements that are used for nothing other than styling and layout. In this case, you can use the styled function. This function returns elements with their className set to the name of the variable after it is converted from camel case to dashes.

import { html } from 'artworkjs';

const { root, sidePanel, content } = html.styled();

root.append(content, sidePanel);

root is now an HTMLDivElement that looks like this:

<div class="root">
  <div class="content"></div>
  <div class="side-panel"></div>
</div>

If you want many different elements, you can use html.create without any arguments. The name of the properties will be the type of the element.

const { div, input, form, ul, h3, label, button } = html.create();

Creating components

html.register is used to register a component if it doesn't already exist, and then return a new instance of that component. It takes the following arguments:

root: The root element that you want to append to the shadow DOM.

styles: This is an optional argument that can be either a string, an CSSStyleSheet, or an array of either of these if you need to combine styles.

props: An optional object that contains properties and functions that will be available to consumers of the web component. See the thumbnail.js and login.js examples in the art-project to see how this works.

name: The name of the web component. It should have a dash in it.

extends: An optional class that the web component will inherit from.

By default, web components do not participate in forms. To allow them to work in forms, so that for example, pressing enter on an input will submit a form, you can inherit from the FormInput class.

import { html, FormInput } from 'artworkjs';

const input = (labelText) => {
  const { div, label, input } = html.create();
  label.innerText = labelText;

  div.append(label, input);

  return html.register({
    root: div,
    props: { input },
    name: 'basic-input',
    extends: FormInput
  });
}

You should provide html.register with the input element. This will then expose a type and value property on the web component that can be accessed by consumers of the component.

Styles

Every component can have CSS that is scoped to that component, to avoid clashes with other components.

import { html } from 'artworkjs';
import styles from './thumbnail.css' with { type: 'css' };

const thumbnail = () => {
  const root = html.p('Star Wars');
  return html.register({
    root,
    styles,
    name: 'movie-thumbnail'
  });
}

styles can also be set to an array of styles if you have more than one. Subclasses can only add more styles, not remove existing styles. You can also use strings containing CSS.

Routes

Artwork includes a router for handling requests. When creating a router, you can pass in the root element, and the router will replace the child element of the root with whatever is returned by the route handler. In the example below, the child element will be the hello component from earlier. If you don't provide a root element, your route handler should not return anything, as it is left up to you to determine what happens when the route is hit.

import { Router, html } from 'artworkjs';
import hello from './examples/hello.js';

const root = document.getElementById('root');

const router = new Router(root);

router.add('/hello', ({ name }) => hello(name));

router.start();

The add method takes two arguments, the path and a route handler. The path can be specified as a string or a regular expression. If the regular expression has named capturing groups, these groups are added to the parameters object that is passed to the route handler.

router.add(/\/(?<username>[^/]+)\/(?<repository>[^/]+)/, ({ username, repository }) => {
  const user = users.find(u => u.username === username);
});

The above example will match routes such as /thebinarysearchtree/artwork. Other routing libraries will allow you to specify routes like this:

/:username/:repository

but this requires more code from the libraries perspective, is less flexible, and can be ambiguous.

The anchor tags in a single page application need to be handled correctly to prevent the page reloading. When you want to use the a tag, you can import the routerLink function, which takes the same arguments as the a function, and adds a state property that represents the history state.

import { html, routerLink } from 'artworkjs';

render() {
  const div = html.div();

  const a1 = routerLink({ href: '/hello?name=World', innerText: 'Hello World' });

  div.append(a1);

  return div;
}

To manually navigate to a different route, you can use pushState.

import { pushState } from 'artworkjs';

pushState('/');

When you are loading an asynchronous component, you often want to display a loading indicator. You can do this by returning the loading indicator in the route handler, and then replacing the indicator once the asynchronous component has loaded. These are all standard DOM API methods.

router.add(/\/movies/, () => {
  const loading = html.div('Loading...');
  movies().then((m) => loading.replaceWith(m));
  return loading;
});

Artwork lets you create as many routers as you want. The router that is created last will override the earlier routers when it has a match that is also in the other routers. Usually, you will have one router that starts when the application begins, and then other routers that are started inside components. When the component loads, the router will become active. Make sure to remove the router once the component is removed from the DOM.

In the example below, the connected function is used to create a new router that intercepts routes and only replaces the title of the movie instead of reloading the side panel.

const routes = async () => {
  const { root, sidePanel, content, video } = html.styled();

  const movies = await getMovies();
  const thumbnails = movies.map(m => thumbnail(m));
  sidePanel.append(...thumbnails);

  const connected = () => {
    const router = new Router();

    router.add('/routes', ({ v }) => {
      const videoId = parseInt(v, 10);
      const movie = movies.find(m => m.id === videoId);

      thumbnails.forEach(t => t.toggleSelected(videoId));

      const title = html.h3(movie.name);
      content.replaceChildren(video, title);
    });
    
    router.start();
    return () => router.remove();
  }

  root.append(content, sidePanel);

  return html.register({
    root,
    connected,
    styles,
    name: 'movie-routes'
  });
}