{"id":38860097,"url":"https://github.com/shuckster/add-javascript","last_synced_at":"2026-01-17T14:21:11.628Z","repository":{"id":165363983,"uuid":"640710259","full_name":"shuckster/add-javascript","owner":"shuckster","description":"Just add JavaScript. Includes validation, integrity checking, caching, and a cross-framework hook.","archived":false,"fork":false,"pushed_at":"2025-01-02T15:58:26.000Z","size":192,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-27T12:52:03.924Z","etag":null,"topics":["in-browser","javascript-loader","module-loader","script-loader"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/shuckster.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-15T00:36:09.000Z","updated_at":"2025-01-02T15:58:28.000Z","dependencies_parsed_at":"2024-04-12T22:44:27.114Z","dependency_job_id":"64fe1dac-cf93-4f89-a763-0f5f87badd86","html_url":"https://github.com/shuckster/add-javascript","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/shuckster/add-javascript","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fadd-javascript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fadd-javascript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fadd-javascript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fadd-javascript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shuckster","download_url":"https://codeload.github.com/shuckster/add-javascript/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shuckster%2Fadd-javascript/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28509941,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T13:38:16.342Z","status":"ssl_error","status_checked_at":"2026-01-17T13:37:44.060Z","response_time":85,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["in-browser","javascript-loader","module-loader","script-loader"],"created_at":"2026-01-17T14:21:11.559Z","updated_at":"2026-01-17T14:21:11.621Z","avatar_url":"https://github.com/shuckster.png","language":"JavaScript","funding_links":["https://www.buymeacoffee.com/shuckster"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\u003ccode\u003eadd-javascript\u003c/code\u003e 📜\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/shuckster/add-javascript/blob/master/LICENSE\"\u003e\n    \u003cimg\n      alt=\"MIT license\"\n      src=\"https://img.shields.io/npm/l/add-javascript?style=plastic\"\n    /\u003e\u003c/a\u003e\n  \u003ca href=\"https://bundlephobia.com/result?p=add-javascript\"\u003e\n    \u003cimg\n      alt=\"npm bundle size\"\n      src=\"https://img.shields.io/bundlephobia/minzip/add-javascript?style=plastic\"\n    /\u003e\u003c/a\u003e\n  \u003ca href=\"https://www.npmjs.com/package/add-javascript\"\u003e\n    \u003cimg\n      alt=\"Version\"\n      src=\"https://img.shields.io/npm/v/add-javascript?style=plastic\"\n    /\u003e\u003c/a\u003e\n\u003c/p\u003e\n\nJust add JavaScript.\n\nIncludes validation, integrity checking, caching, and a cross-framework hook.\n\n- 🤙 [addScript()](#addScript) - Core function, callback based\n- 🙏 [loadScript()](#loadscript) - Promise based\n- 🪝 [makeHook()](#makehook) - Create a React/Preact/Mithril/SolidJS hook\n- 📀 [Install / Use](#install--use)\n- 🤨 [But why?](#but-why)\n\n### `addScript()`\n\n```js\nimport { addScript } from \"add-javascript\";\n\naddScript(\"https://www.example.com/script.js\", options);\n// \"added\" | \"already-added\" | \"already-added (unmanaged)\" | \"skipped\"\n//\n//    \"unmanaged\" - means the script was not added by `add-javascript`\n//\n//    \"skipped\"   - means `skipLoading()` returned true. Callbacks will\n//                  still have run, so this does not imply a no-op.\n//\n```\n\n`options` (defaults shown, see [index.d.ts](./index.d.ts) for all, `loadBehavior` visualised [here](./LOADVIZ.md)):\n\n```js\n// options:\n{\n  isModule: false,\n  loadBehavior: \"async\",\n  fetchPriority: \"auto\",\n  noopIfModulesSupported: false,\n  ignoreQueryString: true,\n  security: {\n    crossOrigin: \"\",\n    nonce: \"\",\n    integrity: \"\",\n    referrerPolicy: \"\"\n  },\n  dataSet: {},\n  skipLoading: () =\u003e false,\n  onLoad: detail =\u003e {},\n  onError: detail =\u003e {}\n}\n```\n\n```ts\n// detail:\n{\n  event: Event,\n  removeScriptTag: () =\u003e void\n}\n```\n\n### `loadScript()`\n\n```js\nimport { loadScript } from \"add-javascript\";\n\nloadScript(\"https://www.example.com/script.js\", {\n  // Same options as addScript(), but\n  // without onLoad and onError since we\n  // can use Promise callbacks instead\n})\n  .then(successDetail =\u003e {})\n  .catch(failDetail =\u003e {});\n```\n\n```ts\n// successDetail:\n{\n  type: \"added\" | \"already-added\" | \"already-added (unmanaged)\" | \"skipped\",\n  event: Event,\n  removeScriptTag: () =\u003e void\n}\n\n// failDetail:\n{\n  type: \"error\",\n  event: Event,\n  removeScriptTag: () =\u003e void\n}\n```\n\n```js\n// Multiple scripts\nawait Promise.all([\n  loadScript(\"https://www.example.com/script-1.js\", options),\n  loadScript(\"https://www.example.com/script-2.js\", options)\n]);\n\n// ...or if you have common options:\nawait Promise.all(\n  [\n    \"https://www.example.com/script-1.js\",\n    \"https://www.example.com/script-2.js\"\n  ].map(src =\u003e loadScript(src, options))\n);\n```\n\n### `makeHook()`\n\n#### `React` / `Preact`\n\n```js\nimport { makeHook } from \"add-javascript\";\nimport { useState, useEffect } from \"react\";\n\n// Make the Hook\nconst useScriptReact = makeHook({ useState, useEffect });\n\nfunction ReactComponent(props) {\n  // Use it\n  const [state, detail] = useScriptReact(\"./my-script.js\", options);\n  // state == \"pending\" | \"loading\" | \"loaded\" | \"error\"\n  // detail == successDetail | failDetail\n\n  return \u003cdiv\u003e{state}\u003c/div\u003e;\n}\n```\n\n#### `Mithril`\n\n```js\nimport { makeHook } from \"add-javascript\";\nimport { withHooks, useState, useEffect } from \"mithril-hooks\";\n\n// Make the Hook\nconst useScriptMithril = makeHook({ useState, useEffect });\n\nconst MithrilComponent = withHooks(() =\u003e {\n  // Use it\n  const [state, detail] = useScriptMithril(\"./my-script.js\", options);\n  // state == \"pending\" | \"loading\" | \"loaded\" | \"error\"\n  // detail == successDetail | failDetail\n\n  return m(\"div\", state);\n});\n```\n\n#### `SolidJS`\n\n```js\nimport { makeHook } from \"add-javascript\";\nimport { createSignal, createEffect } from \"solid-js\";\n\n// Make the Hook\nconst useScriptSolidJS = makeHook({\n  useState: createSignal,\n  useEffect: createEffect\n});\n\nfunction SolidJSComponent() {\n  // Use it\n  const [state, detail] = useScriptSolidJS(\"./my-script.js\", options);\n  // state() == \"pending\" | \"loading\" | \"loaded\" | \"error\"\n  // detail() == successDetail | failDetail\n\n  return html`${state()}`;\n}\n```\n\nYou can use all of these frameworks on the same page (if you like.) Check out [the tests](./tests/www/index.html) for a working implementation of this.\n\nCreate `useScript` in its own file for your convenience:\n\n```js\n// useScript.js\nimport { makeHook } from \"add-javascript\";\nimport { useState, useEffect } from \"react\";\n\nexport const useScript = makeHook({ useState, useEffect });\n```\n\n```js\n// ReactComponent.js\nimport { useScript } from \"./useScript\";\n\nfunction ReactComponent(props) {\n  const [state] = useScript(\"./my-script.js\");\n  return \u003cdiv\u003e{state}\u003c/div\u003e;\n}\n```\n\n## Install / Use\n\n```\n$ pnpm i add-javascript\n```\n\nSupports `import`/`require` for ESM/CJS.\n\nBrowser/UMD version here:\n\n```html\n\u003cscript src=\"https://unpkg.com/add-javascript/dist/browser/add-javascript.browser.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const { loadScript } = addJs;\n\u003c/script\u003e\n```\n\n## But why?\n\n\u003e \"There are loooads of loadScript/useScript libraries. Why make another one?\"\n\nGood 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.\n\nAs 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](https://www.google.com/search?q=idempotent).\n\nThere 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.\n\nThe `options` format is also something of an itch I wanted to scratch.\n\nAll that is behind me now, but perhaps someone else will find the outcome of these efforts useful.\n\nStill, if your codebase is under your full control you're likely far better just rolling your own little helper than installing `add-javascript`:\n\n```js\nconst loadScript = (src, options = { async: true }) =\u003e\n  new Promise((resolve, reject) =\u003e {\n    const script = document.createElement(\"script\");\n    Object.assign(script, options);\n    script.onload = resolve;\n    script.onerror = reject;\n    script.src = src;\n    document.body.appendChild(script);\n  });\n```\n\n# Credits\n\n`add-javascript` was written by [Conan Theobald](https://github.com/shuckster/).\n\nI hope you found it useful! If so, I like [coffee ☕️](https://www.buymeacoffee.com/shuckster) :)\n\n## License\n\nMIT licensed: See [LICENSE](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuckster%2Fadd-javascript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshuckster%2Fadd-javascript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshuckster%2Fadd-javascript/lists"}