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

@simpedit-class/local-client

v1.0.0

Published

<sub>**_Note:_** I will reorganize docs later</sub>

Downloads

1

Readme

Interactive Coding Environment

Note: I will reorganize docs later

React application that is able to locally (on the user's personal machine) create text cells and code cells with a preview window beside each cell through the use of the Monaco editor. Multiple programming languages will be able to be configured into this environment (currently only jsx and css files).

  1. Run command to start application (e.g., jbook serve)
    • This should start a server on something like localhost:4005
  2. User will write code into an editor
  3. App bundles code in the browser
  4. Execute user's code in an iframe with sandbox="allow-scripts"

Downside

Some in-browser features will not be accessible to the user's code (e.g., localStorage.getItem("something") will not work) due to the use of the combination of srcDoc and sandbox in the iframe.

  1. Code will be provided to Preview as a string. This string must be executed safely.

  2. This code might have advanced JavaScript in it (e.g., JSX) that the browser cannot execute.

    • will need to use a transpiler, like Babel. For this app, we can:
      • setup a backend server to transpile the sent code
      • use an in-browser transpiler
  3. The code might have import statements for other JavaScript or CSS files. These import statements must be dealt with before executing the code.

    • will need to find all the modules the user has imported from NPM

Transpiling & Bundling Locally

  • Removes an extra request to the API server (which means faster code execution).
  • An API server will not have to be maintained.
  • Less complexity - no moving code back and forth.

This calls for webpack needing to built into the react app with a custom plugin to fetch individual files from NPM.

Problem with bundling locally is that webpack does NOT work in the browser.

Solve webpack problem by using a webpack and babel replacement called esbuild.

Contains:

  • build: S => (g(), $.build(S))
  • serve: f serve(S, K)
  • stop: f stop()
  • transform: f transforms(S, K)

transform will attempt to execute transpiling on the code that is user provided.

build bundles the user provided code. Bundling in the browser requires extra setup.

build relies on a file system. If user writes:

import React from "react";

esbuild will look for a filesystem that the browser will not have. The app will use a plugin to intercept the request from esbuild for react code and send a request to the NPM Registry to get the URL to react.

Running the following in a command line:

npm view react dist.tarball

will return https://registry.npmjs.org/react/-/react-18.2.0.tgz. This provides the react source code.

For this app, inside of the tarball is /package/index.js which has the code:

if (process.env.NODE_ENV === 'production') {
    module.exports = require('./cjs/react.production.min.js');
} else {
    module.exports = require('./cjs/react.development.js');
}

esbuild will interpret the require() statements in order to join the needed files.

To help with getting the above JS code, UNPKG will be used (unpkg.com/react) to fetch the above index.js.

Esbuild Bundling Process

To create a bundle in the browser with esbuild, onResolve and onLoad will need to be used.

Both onResolve and onLoad have an object with filter that is a regular expression. The regex controls when onResolve & onLoad are executed. onResolve handles the different types of files attempting to be loaded. e.g., one onResolve may have a filter for loading a JS file, and another for loading a CSS file.

The namespace is similar to filter in that it specifies a set of files. An example of applying an onLoad on only files with a namespace of a:

build.onResolve({...}, async (args: any) => ({ path: args.path, namespace: 'a' }));

build.onLoad({ filter: /.*/, namespace: 'a' }, async (args: any) => {...});

| Description | Step | | :-----------------------------------------------------------------------------------: | :--------------: | | Figure out where the index.js file is stored | onResolve step | | Attempt to load up the index.js file | onLoad step | | Parse the index.js file, find any import/require/exports | | | If there are any import/require/exports, figure out where the requested file is | onResolve step | | Attempt to load that file up | onLoad step |

onResolve

onResolve will find where index.js is stored. This function overrides esbuild's natural process of finding out what a file's path is.

Only one onResolve function is needed. It can be defined with multiple if statements to determine paths through one filter:

build.onResolve({ filter: /.*/ }, () => {...});

For this application, several onResolve functions will have more specific filters:

build.onResolve({ filter: /^index\.js$/ }, () => ({ path: "index.js" namespace: "a" }));
build.onResolve({ filter: /^\.{1,2}\// }, (args: any) => ({ path:..., namespace: "a" }));
build.onResolve({ filter: /\.*/ }, (...) => {...});
  • This first filter looks for exactly "index.js"
  • The second handles relative paths (i.e., ./ or ../, for something like ./utils)
  • The last will handle the main file of a module.
  • User-provided code might throw errors that cause program to crash.
    • Solved if execute user's code is contained in an iframe
  • User-provided code might mutate the DOM, causing program to crash
    • e.g., user types in document.body.innerHTML = '';, which will wipe out webpage body
    • Solved if iframe's reference pre-installs html framework when user clicks submit
  • User might accdentally run code provided by another malicious user
    • Solved if execute user's code in an iframe with direct communication disabled
      • Done when setting sandbox to anything other than allow-same-origin
      • Malicious code cannot be used to obtain security information from parent document

iframes can help isolate code. An iframe is an html document within another html doucment. iframss can be configured to allow communication between a parent document and a child document.

Direct access between frames is allowed with BOTH of the following:

  • The iframe element does not have a sandbox property, or has a sandbox="allow-same-origin" property
  • The parent HTML doc and the iframe HTML doc are fetched from the exact same Domain/Port/Protocol (http vs https)

If window.a = 1 is run in the parent document and window.a = 3 is run in the child document, the parent can access the child's a and vice-versa with the following:

For the child document to reach into the parent document:

parent.window.a;
// output: 1

For the parent document to react into the child document:

document.querySelector("iframe").contentWindow.a;
// output: 3

Note: For this app, the iframe will use srcDoc instead of src. srcDoc takes a string that will be generated locally. This way, there will be no different Domain/Port/Protocol because content will not be fetched.

Inside of a React component:

const App = () => {
    // run bundler
    const onClick = async () => {
        const result = await ref.current.build({...});

        setCode(result.outputFiles[0].text);
    }

    const html = `
    <script>{code}</script>
        `;

    return (
        <div>
            <iframe srcDoc={html}></iframe>
        </div>
    );
};

The above snippet will have an error when importing packages that contain a closing script tag. The error is due to the string parsed in srcDoc terminating the contents of the script too soon.

The fix is to refactor the const html var's string to have a message event listener and when the code is bundled, post the message via the iframe's ref:

const html = `
<html>
  <head></head>
  <body>
    <div id="root"></div>
    <script>
    window.addEventListener("message", (event) => {
        eval(event.data);
            })
    </script>
  </body>
</html>
`

// run bundler
const onClick = async () => {
    const result = await ref.current.build({...});

    // iframe is a ref to the iframe tag
    iframe.current.contentWindow.postMessage(result.outputFiles[0].text, "*");
}

Each of these packages will be developed and deployed as separate NPM packages.

The future architecture just describes additional add-ons that can be developed.

Lerna

Lerna will make it very easy to consume updates between our modules on local machines as the modules are being developed. So, instead of having to re-publish to npm and re-installing into node_modules, lerna will setup a link from node_modules to a copy of the package on local machines.