{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "useExternal", "title": "useExternal", "description": "useExternal hook", "files": [ { "path": "packages/hooks/src/useExternal/index.ts", "content": "import { useEffect, useRef, useState } from 'react';\n\ntype JsOptions = {\n type: 'js';\n js?: Partial;\n keepWhenUnused?: boolean;\n};\n\ntype CssOptions = {\n type: 'css';\n css?: Partial;\n keepWhenUnused?: boolean;\n};\n\ntype DefaultOptions = {\n type?: never;\n js?: Partial;\n css?: Partial;\n keepWhenUnused?: boolean;\n};\n\nexport type Options = JsOptions | CssOptions | DefaultOptions;\n\n// {[path]: count}\n// remove external when no used\nconst EXTERNAL_USED_COUNT: Record = {};\n\nexport type Status = 'unset' | 'loading' | 'ready' | 'error';\n\ninterface LoadResult {\n ref: Element;\n status: Status;\n}\n\ntype LoadExternal = (path: string, props?: Partial) => LoadResult;\n\nconst loadScript: LoadExternal = (path, props = {}) => {\n const script = document.querySelector(`script[src=\"${path}\"]`);\n\n if (!script) {\n const newScript = document.createElement('script');\n newScript.src = path;\n\n Object.keys(props).forEach((key) => {\n (newScript as any)[key] = (props as any)[key];\n });\n\n newScript.setAttribute('data-status', 'loading');\n document.body.appendChild(newScript);\n\n return {\n ref: newScript,\n status: 'loading',\n };\n }\n\n return {\n ref: script,\n status: (script.getAttribute('data-status') as Status) || 'ready',\n };\n};\n\nconst loadCss: LoadExternal = (path, props = {}) => {\n const css = document.querySelector(`link[href=\"${path}\"]`);\n if (!css) {\n const newCss = document.createElement('link');\n\n newCss.rel = 'stylesheet';\n newCss.href = path;\n Object.keys(props).forEach((key) => {\n (newCss as any)[key] = (props as any)[key];\n });\n // IE9+\n const isLegacyIECss = 'hideFocus' in newCss;\n // use preload in IE Edge (to detect load errors)\n if (isLegacyIECss && newCss.relList) {\n newCss.rel = 'preload';\n newCss.as = 'style';\n }\n newCss.setAttribute('data-status', 'loading');\n document.head.appendChild(newCss);\n\n return {\n ref: newCss,\n status: 'loading',\n };\n }\n\n return {\n ref: css,\n status: (css.getAttribute('data-status') as Status) || 'ready',\n };\n};\n\nconst useExternal = (path?: string, options?: Options) => {\n const [status, setStatus] = useState(path ? 'loading' : 'unset');\n\n const ref = useRef(undefined);\n\n useEffect(() => {\n if (!path) {\n setStatus('unset');\n return;\n }\n const pathname = path.replace(/[|#].*$/, '');\n if (options?.type === 'css' || (!options?.type && /(^css!|\\.css$)/.test(pathname))) {\n const result = loadCss(path, options?.css);\n ref.current = result.ref;\n setStatus(result.status);\n } else if (options?.type === 'js' || (!options?.type && /(^js!|\\.js$)/.test(pathname))) {\n const result = loadScript(path, options?.js);\n ref.current = result.ref;\n setStatus(result.status);\n } else {\n // do nothing\n console.error(\n \"Cannot infer the type of external resource, and please provide a type ('js' | 'css'). \" +\n 'Refer to the https://ahooks.js.org/hooks/dom/use-external/#options',\n );\n }\n\n if (!ref.current) {\n return;\n }\n\n if (EXTERNAL_USED_COUNT[path] === undefined) {\n EXTERNAL_USED_COUNT[path] = 1;\n } else {\n EXTERNAL_USED_COUNT[path] += 1;\n }\n\n const handler = (event: Event) => {\n const targetStatus = event.type === 'load' ? 'ready' : 'error';\n ref.current?.setAttribute('data-status', targetStatus);\n setStatus(targetStatus);\n };\n\n ref.current.addEventListener('load', handler);\n ref.current.addEventListener('error', handler);\n return () => {\n ref.current?.removeEventListener('load', handler);\n ref.current?.removeEventListener('error', handler);\n\n EXTERNAL_USED_COUNT[path] -= 1;\n\n if (EXTERNAL_USED_COUNT[path] === 0 && !options?.keepWhenUnused) {\n ref.current?.remove();\n }\n\n ref.current = undefined;\n };\n }, [path]);\n\n return status;\n};\n\nexport default useExternal;\n", "type": "registry:file", "target": "src/hooks/ahooks/useExternal/index.ts" } ], "type": "registry:hook" }