Skip to content

Commit 80dd7cf

Browse files
committed
feat: properly handle innerHTML and other direct script properties
1 parent e7c469e commit 80dd7cf

File tree

11 files changed

+1247
-10
lines changed

11 files changed

+1247
-10
lines changed

.changeset/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4+
with multi-package repos, or single-package repos to help you version and publish your code. You can
5+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6+
7+
We have a quick list of common questions to get you started engaging with this project in
8+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

.changeset/config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
3+
"changelog": "@changesets/cli/changelog",
4+
"commit": false,
5+
"fixed": [],
6+
"linked": [],
7+
"access": "restricted",
8+
"baseBranch": "main",
9+
"updateInternalDependencies": "patch",
10+
"ignore": []
11+
}

.changeset/modern-peas-check.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"solid-create-script": minor
3+
---
4+
5+
Adds proper handling of direct script properties like async, defer and innerHTML.

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"printWidth": 120
2+
"printWidth": 100
33
}

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"source.fixAll": "always"
44
},
55
"editor.formatOnSave": true,
6-
"editor.rulers": [120],
6+
"editor.rulers": [100],
77
"files.autoSave": "onFocusChange",
88
"files.insertFinalNewline": true
99
}

bun.lock

Lines changed: 1102 additions & 0 deletions
Large diffs are not rendered by default.

bun.lockb

-176 KB
Binary file not shown.

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@
3939
"build": "tsup",
4040
"build:watch": "tsup --watch",
4141
"dev": "vite",
42+
"changeset": "changeset",
43+
"update-pkg-version": "changeset version",
44+
"release": "changeset publish",
4245
"format": "prettier . --check",
4346
"format:fix": "prettier . --write",
4447
"lint": "eslint . --ext .ts,.tsx",
@@ -50,6 +53,7 @@
5053
"typecheck": "tsc --noEmit"
5154
},
5255
"devDependencies": {
56+
"@changesets/cli": "^2.29.3",
5357
"@solidjs/testing-library": "^0.8.10",
5458
"@testing-library/jest-dom": "^6.5.0",
5559
"@types/bun": "^1.1.10",

playground/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { createScript } from "../src";
22

33
export const App = () => {
4-
const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", { defer: true });
4+
const script = createScript("https://cdn.plaid.com/link/v2/stable/link-initialize.js", {
5+
defer: true,
6+
});
57

68
return (
79
<div>

src/createScript.tsx

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,70 @@
1-
import { createResource, type JSX, splitProps } from "solid-js";
1+
import { createResource, type JSX, mergeProps, splitProps, untrack } from "solid-js";
22
import type { DOMElement } from "solid-js/jsx-runtime";
33

4-
type ScriptAttributes = Omit<JSX.ScriptHTMLAttributes<HTMLScriptElement>, "src">;
4+
import type { FetchPriority, ReferrerPolicy, ScriptType } from "./types";
5+
6+
type ScriptAttributes = Omit<
7+
JSX.ScriptHTMLAttributes<HTMLScriptElement>,
8+
"src" | "referrerPolicy" | "fetchpriority" | "type"
9+
> & {
10+
referrerPolicy?: ReferrerPolicy;
11+
fetchPriority?: FetchPriority;
12+
type?: ScriptType;
13+
};
514

615
type ScriptEvent = Event & {
716
currentTarget: HTMLScriptElement;
817
target: DOMElement;
918
};
1019

20+
type DirectScriptAttributes = Pick<
21+
ScriptAttributes,
22+
| "async"
23+
| "defer"
24+
| "innerHTML"
25+
| "fetchPriority"
26+
| "noModule"
27+
| "type"
28+
| "crossOrigin"
29+
| "referrerPolicy"
30+
| "integrity"
31+
| "nonce"
32+
>;
33+
34+
const DIRECT_SCRIPT_ATTRIBUTES = [
35+
"async",
36+
"defer",
37+
"innerHTML",
38+
"fetchPriority",
39+
"noModule",
40+
"type",
41+
"crossOrigin",
42+
"referrerPolicy",
43+
"integrity",
44+
"nonce",
45+
] as Readonly<(keyof DirectScriptAttributes)[]>;
46+
1147
// Promise cache for script sources
1248
const SCRIPT_PROMISES = new Map<string, Promise<Event>>();
1349

1450
const loadScript = async (src: string, attributes: Readonly<ScriptAttributes>) => {
15-
const [initEvents, otherAttributes] = splitProps(attributes, ["onload", "onLoad", "onerror", "onError"]);
51+
const _attributes = mergeProps(
52+
{
53+
async: true,
54+
defer: false,
55+
innerHTML: "",
56+
fetchPriority: "auto" as FetchPriority,
57+
},
58+
attributes,
59+
);
60+
61+
const [initEvents, otherAttributes] = splitProps(_attributes, [
62+
"onload",
63+
"onLoad",
64+
"onerror",
65+
"onError",
66+
]);
67+
const [directAttributes, restAttributes] = splitProps(otherAttributes, DIRECT_SCRIPT_ATTRIBUTES);
1668

1769
// 1. Reject if no src provided
1870
if (!src) return Promise.reject(new Error('No "src" provided for createScript'));
@@ -21,12 +73,36 @@ const loadScript = async (src: string, attributes: Readonly<ScriptAttributes>) =
2173
if (SCRIPT_PROMISES.has(src)) return SCRIPT_PROMISES.get(src)!;
2274

2375
// 3. Check if script already exists (may have been added externally not via this hook)
24-
if (document.querySelector(`script[src="${src}"]`)) return Promise.resolve(new Event("already-loaded"));
76+
if (document.querySelector(`script[src="${src}"]`))
77+
return Promise.resolve(new Event("already-loaded"));
2578

2679
// 4. Create new script element
2780
const promise = new Promise<Event>((resolve, reject) => {
2881
const script = document.createElement("script");
82+
2983
script.src = src;
84+
script.async = untrack(() => directAttributes.async);
85+
script.defer = untrack(() => directAttributes.defer);
86+
script.innerHTML = untrack(() => directAttributes.innerHTML);
87+
script.fetchPriority = untrack(() => directAttributes.fetchPriority);
88+
89+
const type = untrack(() => directAttributes.type);
90+
if (type) script.type = type;
91+
92+
const crossOrigin = untrack(() => directAttributes.crossOrigin);
93+
if (crossOrigin) script.crossOrigin = crossOrigin;
94+
95+
const referrerPolicy = untrack(() => directAttributes.referrerPolicy);
96+
if (referrerPolicy) script.referrerPolicy = referrerPolicy;
97+
98+
const integrity = untrack(() => directAttributes.integrity);
99+
if (integrity) script.integrity = integrity;
100+
101+
const nonce = untrack(() => directAttributes.nonce);
102+
if (nonce) script.nonce = nonce;
103+
104+
const noModule = untrack(() => directAttributes.noModule);
105+
if (noModule) script.noModule = noModule;
30106

31107
script.onload = (e) => {
32108
if (typeof initEvents.onload === "function") {
@@ -39,17 +115,23 @@ const loadScript = async (src: string, attributes: Readonly<ScriptAttributes>) =
39115
};
40116

41117
script.onerror = (e) => {
118+
const event = typeof e === "string" ? new Error(e) : e;
119+
const errorEvent = event as ErrorEvent & {
120+
currentTarget: HTMLScriptElement;
121+
target: Element;
122+
};
123+
42124
if (typeof initEvents.onerror === "function") {
43-
initEvents.onerror(e as ScriptEvent);
125+
initEvents.onerror(errorEvent);
44126
} else if (typeof initEvents.onError === "function") {
45-
initEvents.onError(e as ScriptEvent);
127+
initEvents.onError(errorEvent);
46128
}
47129

48130
reject(e);
49131
};
50132

51133
// Apply additional attributes if any
52-
Object.entries(otherAttributes).forEach(([key, value]) => {
134+
Object.entries(restAttributes).forEach(([key, value]) => {
53135
script.setAttribute(key, value);
54136
});
55137

0 commit comments

Comments
 (0)