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

add-javascript

v1.0.6

Published

Just add JavaScript. Includes validation, integrity checking, caching, and a cross-framework hook.

Downloads

14

Readme

Just add JavaScript.

Includes validation, integrity checking, caching, and a cross-framework hook.

addScript()

import { addScript } from "add-javascript";

addScript("https://www.example.com/script.js", options);
// "added" | "already-added" | "already-added (unmanaged)" | "skipped"
//
//    "unmanaged" - means the script was not added by `add-javascript`
//
//    "skipped"   - means `skipLoading()` returned true. Callbacks will
//                  still have run, so this does not imply a no-op.
//

options (defaults shown, see index.d.ts for all, loadBehavior visualised here):

// options:
{
  isModule: false,
  loadBehavior: "async",
  fetchPriority: "auto",
  noopIfModulesSupported: false,
  ignoreQueryString: true,
  security: {
    crossOrigin: "",
    nonce: "",
    integrity: "",
    referrerPolicy: ""
  },
  dataSet: {},
  skipLoading: () => false,
  onLoad: detail => {},
  onError: detail => {}
}
// detail:
{
  event: Event,
  removeScriptTag: () => void
}

loadScript()

import { loadScript } from "add-javascript";

loadScript("https://www.example.com/script.js", {
  // Same options as addScript(), but
  // without onLoad and onError since we
  // can use Promise callbacks instead
})
  .then(successDetail => {})
  .catch(failDetail => {});
// successDetail:
{
  type: "added" | "already-added" | "already-added (unmanaged)" | "skipped",
  event: Event,
  removeScriptTag: () => void
}

// failDetail:
{
  type: "error",
  event: Event,
  removeScriptTag: () => void
}
// Multiple scripts
await Promise.all([
  loadScript("https://www.example.com/script-1.js", options),
  loadScript("https://www.example.com/script-2.js", options)
]);

// ...or if you have common options:
await Promise.all(
  [
    "https://www.example.com/script-1.js",
    "https://www.example.com/script-2.js"
  ].map(src => loadScript(src, options))
);

makeHook()

React / Preact

import { makeHook } from "add-javascript";
import { useState, useEffect } from "react";

// Make the Hook
const useScriptReact = makeHook({ useState, useEffect });

function ReactComponent(props) {
  // Use it
  const [state, detail] = useScriptReact("./my-script.js", options);
  // state == "pending" | "loading" | "loaded" | "error"
  // detail == successDetail | failDetail

  return <div>{state}</div>;
}

Mithril

import { makeHook } from "add-javascript";
import { withHooks, useState, useEffect } from "mithril-hooks";

// Make the Hook
const useScriptMithril = makeHook({ useState, useEffect });

const MithrilComponent = withHooks(() => {
  // Use it
  const [state, detail] = useScriptMithril("./my-script.js", options);
  // state == "pending" | "loading" | "loaded" | "error"
  // detail == successDetail | failDetail

  return m("div", state);
});

SolidJS

import { makeHook } from "add-javascript";
import { createSignal, createEffect } from "solid-js";

// Make the Hook
const useScriptSolidJS = makeHook({
  useState: createSignal,
  useEffect: createEffect
});

function SolidJSComponent() {
  // Use it
  const [state, detail] = useScriptSolidJS("./my-script.js", options);
  // state() == "pending" | "loading" | "loaded" | "error"
  // detail() == successDetail | failDetail

  return html`${state()}`;
}

You can use all of these frameworks on the same page (if you like.) Check out the tests for a working implementation of this.

Create useScript in its own file for your convenience:

// useScript.js
import { makeHook } from "add-javascript";
import { useState, useEffect } from "react";

export const useScript = makeHook({ useState, useEffect });
// ReactComponent.js
import { useScript } from "./useScript";

function ReactComponent(props) {
  const [state] = useScript("./my-script.js");
  return <div>{state}</div>;
}

Install / Use

$ pnpm i add-javascript

Supports import/require for ESM/CJS.

Browser/UMD version here:

<script src="https://unpkg.com/add-javascript/dist/browser/add-javascript.browser.js"></script>
<script>
  const { loadScript } = addJs;
</script>

But why?

"There are loooads of loadScript/useScript libraries. Why make another one?"

Good question. Perhaps I didn't really need my own library, but I once found myself battling a bug caused by Hot Module Reloading vs. a 3rd-party script and I just ended up writing one to solve the problem. This is the result.

As for the bug, the size of the codebase I was working on at the time make the problem difficult to debug. Long story short, the 3rd-party script was being loaded multiple times and was also not idempotent.

There was also a chance it could be loaded in a vanilla way or via a Hook, hence the desire to offer an API that would consolidate the script-adding logic. This also explains the options default of ignoreQueryString: true, which considers a script loaded regardless of its (in my case: cache-busting) query-string parameters.

The options format is also something of an itch I wanted to scratch.

All that is behind me now, but perhaps someone else will find the outcome of these efforts useful.

Still, if your codebase is under your full control you're likely far better just rolling your own little helper than installing add-javascript:

const loadScript = (src, options = { async: true }) =>
  new Promise((resolve, reject) => {
    const script = document.createElement("script");
    Object.assign(script, options);
    script.onload = resolve;
    script.onerror = reject;
    script.src = src;
    document.body.appendChild(script);
  });

Credits

add-javascript was written by Conan Theobald.

I hope you found it useful! If so, I like coffee ☕️ :)

License

MIT licensed: See LICENSE