339 lines
13 KiB
JavaScript
339 lines
13 KiB
JavaScript
|
"use client";
|
||
|
|
||
|
"use strict";
|
||
|
Object.defineProperty(exports, "__esModule", {
|
||
|
value: true
|
||
|
});
|
||
|
0 && (module.exports = {
|
||
|
handleClientScriptLoad: null,
|
||
|
initScriptLoader: null,
|
||
|
default: null
|
||
|
});
|
||
|
function _export(target, all) {
|
||
|
for(var name in all)Object.defineProperty(target, name, {
|
||
|
enumerable: true,
|
||
|
get: all[name]
|
||
|
});
|
||
|
}
|
||
|
_export(exports, {
|
||
|
handleClientScriptLoad: function() {
|
||
|
return handleClientScriptLoad;
|
||
|
},
|
||
|
initScriptLoader: function() {
|
||
|
return initScriptLoader;
|
||
|
},
|
||
|
default: function() {
|
||
|
return _default;
|
||
|
}
|
||
|
});
|
||
|
const _interop_require_default = require("@swc/helpers/_/_interop_require_default");
|
||
|
const _interop_require_wildcard = require("@swc/helpers/_/_interop_require_wildcard");
|
||
|
const _reactdom = /*#__PURE__*/ _interop_require_default._(require("react-dom"));
|
||
|
const _react = /*#__PURE__*/ _interop_require_wildcard._(require("react"));
|
||
|
const _headmanagercontextsharedruntime = require("../shared/lib/head-manager-context.shared-runtime");
|
||
|
const _headmanager = require("./head-manager");
|
||
|
const _requestidlecallback = require("./request-idle-callback");
|
||
|
const ScriptCache = new Map();
|
||
|
const LoadCache = new Set();
|
||
|
const ignoreProps = [
|
||
|
"onLoad",
|
||
|
"onReady",
|
||
|
"dangerouslySetInnerHTML",
|
||
|
"children",
|
||
|
"onError",
|
||
|
"strategy",
|
||
|
"stylesheets"
|
||
|
];
|
||
|
const insertStylesheets = (stylesheets)=>{
|
||
|
// Case 1: Styles for afterInteractive/lazyOnload with appDir injected via handleClientScriptLoad
|
||
|
//
|
||
|
// Using ReactDOM.preinit to feature detect appDir and inject styles
|
||
|
// Stylesheets might have already been loaded if initialized with Script component
|
||
|
// Re-inject styles here to handle scripts loaded via handleClientScriptLoad
|
||
|
// ReactDOM.preinit handles dedup and ensures the styles are loaded only once
|
||
|
if (_reactdom.default.preinit) {
|
||
|
stylesheets.forEach((stylesheet)=>{
|
||
|
_reactdom.default.preinit(stylesheet, {
|
||
|
as: "style"
|
||
|
});
|
||
|
});
|
||
|
return;
|
||
|
}
|
||
|
// Case 2: Styles for afterInteractive/lazyOnload with pages injected via handleClientScriptLoad
|
||
|
//
|
||
|
// We use this function to load styles when appdir is not detected
|
||
|
// TODO: Use React float APIs to load styles once available for pages dir
|
||
|
if (typeof window !== "undefined") {
|
||
|
let head = document.head;
|
||
|
stylesheets.forEach((stylesheet)=>{
|
||
|
let link = document.createElement("link");
|
||
|
link.type = "text/css";
|
||
|
link.rel = "stylesheet";
|
||
|
link.href = stylesheet;
|
||
|
head.appendChild(link);
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
const loadScript = (props)=>{
|
||
|
const { src, id, onLoad = ()=>{}, onReady = null, dangerouslySetInnerHTML, children = "", strategy = "afterInteractive", onError, stylesheets } = props;
|
||
|
const cacheKey = id || src;
|
||
|
// Script has already loaded
|
||
|
if (cacheKey && LoadCache.has(cacheKey)) {
|
||
|
return;
|
||
|
}
|
||
|
// Contents of this script are already loading/loaded
|
||
|
if (ScriptCache.has(src)) {
|
||
|
LoadCache.add(cacheKey);
|
||
|
// It is possible that multiple `next/script` components all have same "src", but has different "onLoad"
|
||
|
// This is to make sure the same remote script will only load once, but "onLoad" are executed in order
|
||
|
ScriptCache.get(src).then(onLoad, onError);
|
||
|
return;
|
||
|
}
|
||
|
/** Execute after the script first loaded */ const afterLoad = ()=>{
|
||
|
// Run onReady for the first time after load event
|
||
|
if (onReady) {
|
||
|
onReady();
|
||
|
}
|
||
|
// add cacheKey to LoadCache when load successfully
|
||
|
LoadCache.add(cacheKey);
|
||
|
};
|
||
|
const el = document.createElement("script");
|
||
|
const loadPromise = new Promise((resolve, reject)=>{
|
||
|
el.addEventListener("load", function(e) {
|
||
|
resolve();
|
||
|
if (onLoad) {
|
||
|
onLoad.call(this, e);
|
||
|
}
|
||
|
afterLoad();
|
||
|
});
|
||
|
el.addEventListener("error", function(e) {
|
||
|
reject(e);
|
||
|
});
|
||
|
}).catch(function(e) {
|
||
|
if (onError) {
|
||
|
onError(e);
|
||
|
}
|
||
|
});
|
||
|
if (dangerouslySetInnerHTML) {
|
||
|
// Casting since lib.dom.d.ts doesn't have TrustedHTML yet.
|
||
|
el.innerHTML = dangerouslySetInnerHTML.__html || "";
|
||
|
afterLoad();
|
||
|
} else if (children) {
|
||
|
el.textContent = typeof children === "string" ? children : Array.isArray(children) ? children.join("") : "";
|
||
|
afterLoad();
|
||
|
} else if (src) {
|
||
|
el.src = src;
|
||
|
// do not add cacheKey into LoadCache for remote script here
|
||
|
// cacheKey will be added to LoadCache when it is actually loaded (see loadPromise above)
|
||
|
ScriptCache.set(src, loadPromise);
|
||
|
}
|
||
|
for (const [k, value] of Object.entries(props)){
|
||
|
if (value === undefined || ignoreProps.includes(k)) {
|
||
|
continue;
|
||
|
}
|
||
|
const attr = _headmanager.DOMAttributeNames[k] || k.toLowerCase();
|
||
|
el.setAttribute(attr, value);
|
||
|
}
|
||
|
if (strategy === "worker") {
|
||
|
el.setAttribute("type", "text/partytown");
|
||
|
}
|
||
|
el.setAttribute("data-nscript", strategy);
|
||
|
// Load styles associated with this script
|
||
|
if (stylesheets) {
|
||
|
insertStylesheets(stylesheets);
|
||
|
}
|
||
|
document.body.appendChild(el);
|
||
|
};
|
||
|
function handleClientScriptLoad(props) {
|
||
|
const { strategy = "afterInteractive" } = props;
|
||
|
if (strategy === "lazyOnload") {
|
||
|
window.addEventListener("load", ()=>{
|
||
|
(0, _requestidlecallback.requestIdleCallback)(()=>loadScript(props));
|
||
|
});
|
||
|
} else {
|
||
|
loadScript(props);
|
||
|
}
|
||
|
}
|
||
|
function loadLazyScript(props) {
|
||
|
if (document.readyState === "complete") {
|
||
|
(0, _requestidlecallback.requestIdleCallback)(()=>loadScript(props));
|
||
|
} else {
|
||
|
window.addEventListener("load", ()=>{
|
||
|
(0, _requestidlecallback.requestIdleCallback)(()=>loadScript(props));
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
function addBeforeInteractiveToCache() {
|
||
|
const scripts = [
|
||
|
...document.querySelectorAll('[data-nscript="beforeInteractive"]'),
|
||
|
...document.querySelectorAll('[data-nscript="beforePageRender"]')
|
||
|
];
|
||
|
scripts.forEach((script)=>{
|
||
|
const cacheKey = script.id || script.getAttribute("src");
|
||
|
LoadCache.add(cacheKey);
|
||
|
});
|
||
|
}
|
||
|
function initScriptLoader(scriptLoaderItems) {
|
||
|
scriptLoaderItems.forEach(handleClientScriptLoad);
|
||
|
addBeforeInteractiveToCache();
|
||
|
}
|
||
|
function Script(props) {
|
||
|
const { id, src = "", onLoad = ()=>{}, onReady = null, strategy = "afterInteractive", onError, stylesheets, ...restProps } = props;
|
||
|
// Context is available only during SSR
|
||
|
const { updateScripts, scripts, getIsSsr, appDir, nonce } = (0, _react.useContext)(_headmanagercontextsharedruntime.HeadManagerContext);
|
||
|
/**
|
||
|
* - First mount:
|
||
|
* 1. The useEffect for onReady executes
|
||
|
* 2. hasOnReadyEffectCalled.current is false, but the script hasn't loaded yet (not in LoadCache)
|
||
|
* onReady is skipped, set hasOnReadyEffectCalled.current to true
|
||
|
* 3. The useEffect for loadScript executes
|
||
|
* 4. hasLoadScriptEffectCalled.current is false, loadScript executes
|
||
|
* Once the script is loaded, the onLoad and onReady will be called by then
|
||
|
* [If strict mode is enabled / is wrapped in <OffScreen /> component]
|
||
|
* 5. The useEffect for onReady executes again
|
||
|
* 6. hasOnReadyEffectCalled.current is true, so entire effect is skipped
|
||
|
* 7. The useEffect for loadScript executes again
|
||
|
* 8. hasLoadScriptEffectCalled.current is true, so entire effect is skipped
|
||
|
*
|
||
|
* - Second mount:
|
||
|
* 1. The useEffect for onReady executes
|
||
|
* 2. hasOnReadyEffectCalled.current is false, but the script has already loaded (found in LoadCache)
|
||
|
* onReady is called, set hasOnReadyEffectCalled.current to true
|
||
|
* 3. The useEffect for loadScript executes
|
||
|
* 4. The script is already loaded, loadScript bails out
|
||
|
* [If strict mode is enabled / is wrapped in <OffScreen /> component]
|
||
|
* 5. The useEffect for onReady executes again
|
||
|
* 6. hasOnReadyEffectCalled.current is true, so entire effect is skipped
|
||
|
* 7. The useEffect for loadScript executes again
|
||
|
* 8. hasLoadScriptEffectCalled.current is true, so entire effect is skipped
|
||
|
*/ const hasOnReadyEffectCalled = (0, _react.useRef)(false);
|
||
|
(0, _react.useEffect)(()=>{
|
||
|
const cacheKey = id || src;
|
||
|
if (!hasOnReadyEffectCalled.current) {
|
||
|
// Run onReady if script has loaded before but component is re-mounted
|
||
|
if (onReady && cacheKey && LoadCache.has(cacheKey)) {
|
||
|
onReady();
|
||
|
}
|
||
|
hasOnReadyEffectCalled.current = true;
|
||
|
}
|
||
|
}, [
|
||
|
onReady,
|
||
|
id,
|
||
|
src
|
||
|
]);
|
||
|
const hasLoadScriptEffectCalled = (0, _react.useRef)(false);
|
||
|
(0, _react.useEffect)(()=>{
|
||
|
if (!hasLoadScriptEffectCalled.current) {
|
||
|
if (strategy === "afterInteractive") {
|
||
|
loadScript(props);
|
||
|
} else if (strategy === "lazyOnload") {
|
||
|
loadLazyScript(props);
|
||
|
}
|
||
|
hasLoadScriptEffectCalled.current = true;
|
||
|
}
|
||
|
}, [
|
||
|
props,
|
||
|
strategy
|
||
|
]);
|
||
|
if (strategy === "beforeInteractive" || strategy === "worker") {
|
||
|
if (updateScripts) {
|
||
|
scripts[strategy] = (scripts[strategy] || []).concat([
|
||
|
{
|
||
|
id,
|
||
|
src,
|
||
|
onLoad,
|
||
|
onReady,
|
||
|
onError,
|
||
|
...restProps
|
||
|
}
|
||
|
]);
|
||
|
updateScripts(scripts);
|
||
|
} else if (getIsSsr && getIsSsr()) {
|
||
|
// Script has already loaded during SSR
|
||
|
LoadCache.add(id || src);
|
||
|
} else if (getIsSsr && !getIsSsr()) {
|
||
|
loadScript(props);
|
||
|
}
|
||
|
}
|
||
|
// For the app directory, we need React Float to preload these scripts.
|
||
|
if (appDir) {
|
||
|
// Injecting stylesheets here handles beforeInteractive and worker scripts correctly
|
||
|
// For other strategies injecting here ensures correct stylesheet order
|
||
|
// ReactDOM.preinit handles loading the styles in the correct order,
|
||
|
// also ensures the stylesheet is loaded only once and in a consistent manner
|
||
|
//
|
||
|
// Case 1: Styles for beforeInteractive/worker with appDir - handled here
|
||
|
// Case 2: Styles for beforeInteractive/worker with pages dir - Not handled yet
|
||
|
// Case 3: Styles for afterInteractive/lazyOnload with appDir - handled here
|
||
|
// Case 4: Styles for afterInteractive/lazyOnload with pages dir - handled in insertStylesheets function
|
||
|
if (stylesheets) {
|
||
|
stylesheets.forEach((styleSrc)=>{
|
||
|
_reactdom.default.preinit(styleSrc, {
|
||
|
as: "style"
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
// Before interactive scripts need to be loaded by Next.js' runtime instead
|
||
|
// of native <script> tags, because they no longer have `defer`.
|
||
|
if (strategy === "beforeInteractive") {
|
||
|
if (!src) {
|
||
|
// For inlined scripts, we put the content in `children`.
|
||
|
if (restProps.dangerouslySetInnerHTML) {
|
||
|
// Casting since lib.dom.d.ts doesn't have TrustedHTML yet.
|
||
|
restProps.children = restProps.dangerouslySetInnerHTML.__html;
|
||
|
delete restProps.dangerouslySetInnerHTML;
|
||
|
}
|
||
|
return /*#__PURE__*/ _react.default.createElement("script", {
|
||
|
nonce: nonce,
|
||
|
dangerouslySetInnerHTML: {
|
||
|
__html: "(self.__next_s=self.__next_s||[]).push(" + JSON.stringify([
|
||
|
0,
|
||
|
{
|
||
|
...restProps
|
||
|
}
|
||
|
]) + ")"
|
||
|
}
|
||
|
});
|
||
|
} else {
|
||
|
// @ts-ignore
|
||
|
_reactdom.default.preload(src, restProps.integrity ? {
|
||
|
as: "script",
|
||
|
integrity: restProps.integrity
|
||
|
} : {
|
||
|
as: "script"
|
||
|
});
|
||
|
return /*#__PURE__*/ _react.default.createElement("script", {
|
||
|
nonce: nonce,
|
||
|
dangerouslySetInnerHTML: {
|
||
|
__html: "(self.__next_s=self.__next_s||[]).push(" + JSON.stringify([
|
||
|
src
|
||
|
]) + ")"
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
} else if (strategy === "afterInteractive") {
|
||
|
if (src) {
|
||
|
// @ts-ignore
|
||
|
_reactdom.default.preload(src, restProps.integrity ? {
|
||
|
as: "script",
|
||
|
integrity: restProps.integrity
|
||
|
} : {
|
||
|
as: "script"
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
Object.defineProperty(Script, "__nextScript", {
|
||
|
value: true
|
||
|
});
|
||
|
const _default = Script;
|
||
|
|
||
|
if ((typeof exports.default === 'function' || (typeof exports.default === 'object' && exports.default !== null)) && typeof exports.default.__esModule === 'undefined') {
|
||
|
Object.defineProperty(exports.default, '__esModule', { value: true });
|
||
|
Object.assign(exports.default, exports);
|
||
|
module.exports = exports.default;
|
||
|
}
|
||
|
|
||
|
//# sourceMappingURL=script.js.map
|