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

subatomicjs

v0.2.0

Published

Subatomic JS is the minimalist Full-stack framework with SSR and subatomic bundles.

Downloads

34

Readme

Subatomic

Subatomic is the world's minimalistic JS framework

  • No unnecessary features: Subatomic does not contain nor never will contain bloatware or any non-necessary features.
  • Small and Features-Rich: Subatomic is very small and versatile while having important features like Server Side Rendering
  • Component-Based: Subatomic allows you to write once and use that component anywhere without copy & pasting or creating messy code.

Getting Started

You can create a new subatomic app by running:

npx create-subatomic-app@latest
# or
yarn create subatomic-app
# or
pnpm create subatomic-app
# or
bunx create-subatomic-app

After run subatomic by running:

npm run start
# or
bun run start

Please consider using bun as it will make subatomic run even faster than npm

Documentation

About index.js

index.js is where all of your code starts. index.js handles the backend of subatomic.js and uses express out of the box. It also comes with dotenv configured and is ready for more packages to be installed. You can write all of the routes you want like normal, but you wil need your "/" route.

app.get("/", (req, res) => {
  //import the corresponding code
  import("./pages/page.mjs").then((a) => {
    //handle possible middleware and server code
    a.default.middleware.forEach((a) => {
      if (!a(req, res)) {
        res.send("Unauthorized");
      }
    });
    let data = {}; //pass data from the server to the build
    //send the final build
    res.send(
      build(
        a.default.render,
        a.default.state,
        a.default.init,
        a.default.components,
        a.default.title,
        a.default.description,
        data
      )
    );
  });
});

There is a lot going on here, so let's break it down. The route will fetch the code for /pages/page.mjs, which is always used for the "/" route. If you want a custom route like "/home", create a folder in pages called home and create a page.mjs in there (mjs for module exports.) Afterwards we handle the exports from the file and check for all of the middleware found in the file. This code shows an example of how an auth might work, but middleware is still under more development to create a better dev experience and more sophisticated features. Once the middleware is finished and the server hasn't returned with an exit, the build happens using the code from the imported file. You may also pass in extra data as the last argument which could be used in the build.

The Build

Subatomic.js uses pre-rendering and sends over the initial ui. This is why the build is returned from the render function:

function build(render, state, init, components, title, description, data) {
  let [ui, variables] = render(true, data); //render() is being called, but with the first parameter as true to tell it to build

The code executed in the build is different from the rendering on the client, but still returns an initial ui for bots and crawlers. The first difference is this:

if (build) {
    state(); //initialize the variables and state
    variables.Test = function (req) { //create server components
      if (req) {
        variables.Test = function () {
          return `<h1>You are logged in!</h1>`;
        };
      } else {
        variables.Test = function () {
          return `<h1>You are not logged in!</h1>`;
        };
      }
    };
    variables.Test(req);
  }

The build will initialize the state first which will set the initial variables

function state() {
  variables.cookies = 0;
  variables.todos = [];
  variables.input = "";
}

These need to be set so that the UI can be rendered that may require this state, and also for conditional server components. variables.Test is an example of a conditional server component. If you have content that you may want to show to people that are logged in, then you can create a conditional server component. It runs on the server and returns a new component. This is different then simply returning content based off a condition, because a user could inspect and find information that you might not want them to find. Afterwards the component will be called and return the new component ready for the build and client. Now the UI can be built.

let ui = [
    `<h1>Easy state management across components</h1>`,
    `<button onclick="variables.cookies++;render();">Component found in current page</button>`,
    Button(variables),
    Cookies(variables.cookies),
    `<h1>Create conditional server components</h1>`,
    variables.Test(),
    `<input id="asdf"/><button onclick="variables.todos.push(asdf.value);render()">State management witih arrays with simple .push() syntax</button><button onclick="variables.todos.pop();render()">State management witih arrays with simple .pop() syntax</button>`,
    ...variables.todos.map((a) => {
      return `<h1>${a}</h1>`;
    }),
  ];

We'll get back to what this code does exactly, but now we've declared the UI with all components that were needed so we run this line:

if (build) {
    return [ui, variables];
  }

We also have this condition before that line of code

  if (typeof document !== "undefined") {
    useEffect(() => {
      console.log("use effect to run side effects");
    }, ["cookies"]);
    document.body.innerHTML = `<body>${parseArray(ui)}</body>`;
    asdf.value = variables.input;
    asdf.onchange = () => {
      variables.input = asdf.value;
    };
    Object.keys(variables).forEach((a) => {
      effectVariables[a] = variables[a];
    });
  }

This won't run on the build and will only happen on the client. Don't worry, the code executed will not be required for the SEO and can happen on the client. For example the useEffect only needs to be added on the client, and the setting of the html is used for re-rendering the content on the client. Some attributes are also set on the input#asdf, and this should be set on the client because this is the alternate way.

<input id="asdf" onchange="variables.input = '${asdf.value}'"/>

And this is an easy way to get code injected from the user and should not be done (both injecting code and this practice.) Now that the ui and variables and returned, let go back to index.js to finish the process.

function build(render, state, init, components, title, description, data) {
  let [ui, variables] = render(true, data);
let content = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="css/pico.min.css">
  <title>${title}</title>
  <meta name="description" content="${description}">

</head>
`

We create this huge string called content which will then be sent to the client. This is just the head of the document, which imports picocss and sets meta data. The body gets more messy, including scripts and the ui.

`
<body>
  ${parseArray(ui)}
  <script>
  function parseArray(arr) {
  return arr.join("");
}
    ${render.toString()}
    ${init.toString()}
    ${components
      .map((a) => {
        return `${a.toString()}`;
      })
      .join(";")}
      ${functions
        .map((a) => {
          return `${a.toString()}`;
        })
        .join(";")}
`

First the ui is passed in and parsed to become valid HTML instead of a simple array. Afterwards we have a script that declares functions that the client will need. It declares the render loop, the init function, all of the components exported, along with functions exported. Methods are used to make sure that these end up as strings so it can be read by the browser in the script tag.

`
let variables = {${Object.keys(variables).map((a) => {
    return `${a}: ${
      typeof variables[a] === "function"
        ? variables[a].toString()
        : JSON.stringify(variables[a])
    }`;
  })}};
let effectVariables = {};
    render();
    init();
  </script>
</body>
</html>`;
  return content;
}

Finally we declare the variables and state from the server using some method you don't need to worry about. Afterwards we set up the effect variables and run the first render loop. Afterwards we can run the init function which runs code after and only afer the first render. And that is the long and windy process of building the page, with not too much code!