rsnext/packages/next/client/app-bootstrap.js

62 lines
1.4 KiB
JavaScript
Raw Normal View History

Load `beforeInteractive` scripts properly without blocking hydration (#41164) This PR ensures that for the app directory, `beforeInteractive`, `afterInteractive` and `lazyOnload` scripts via `next/script` are properly supported. For both `beforeInteractive` and `afterInteractive` scripts, a preload link tag needs to be injected by Float. For `beforeInteractive` scripts and Next.js' polyfills, they need to be manually executed in order before starting the Next.js' runtime, without blocking the downloading of HTML and other scripts. This PR doesn't include the `worker` type of scripts yet. Note: in this PR I changed the inlined flight data `__next_s` to `__next_f`, and use `__next_s` for scripts data, because I can't find a better name for `next/script` that is also short at the same time. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
2022-10-09 17:08:51 +02:00
/**
* Before starting the Next.js runtime and requiring any module, we need to make
* sure the following scripts are executed in the correct order:
* - Polyfills
* - next/script with `beforeInteractive` strategy
*/
const version = process.env.__NEXT_VERSION
window.next = {
version,
appDir: true,
}
function loadScriptsInSequence(scripts, hydrate) {
if (!scripts || !scripts.length) {
return hydrate()
}
return scripts
.reduce((promise, [src, props]) => {
return promise.then(() => {
return new Promise((resolve, reject) => {
const el = document.createElement('script')
if (props) {
for (const key in props) {
if (key !== 'children') {
el.setAttribute(key, props[key])
}
}
}
if (src) {
el.src = src
el.onload = resolve
el.onerror = reject
} else if (props) {
el.innerHTML = props.children
setTimeout(resolve)
}
document.head.appendChild(el)
})
})
}, Promise.resolve())
.then(() => {
hydrate()
})
.catch((err) => {
console.error(err)
// Still try to hydrate even if there's an error.
hydrate()
})
}
export function appBootstrap(callback) {
loadScriptsInSequence(self.__next_s, () => {
callback()
})
}