@engineerapart/nextscript
v2.0.2
Published
A custom Next script loader for Next.js. Replaces the built-in NextScript component and allows you to apply polyfill pre-loading and business logic.
Downloads
170
Readme
Version Notice
- For Next <= v6, please use nextscript@~0.0.8
- For Next >= 7, please use nextscript@^1.0.0
- For Next 8 & 9, please use nextscript@^2.0.0
- For Next >= 9.0.3 and above, if using experimental module support for modern browsers, this package is not needed (see: 9.0.3 release notes). This is due to the fact that all polyfills offered by polyfill.io are already supported by modern browsers.
In each new version of Next there are incompatible changes with the prior versions. This component was always a stepping stone to a more permanent solution, which the Next.js team are now working on in the Module/Nomodule RFC and the corresponding initial merge request that went into Next.js 9.0.3.
Once that feature is enabled by default, this component will be deprecated.
NextScript 💠
This project is not-so-cleverly named after the export of the same name in the amazing Next.js framework.
This component provides a flexible, business-rule oriented approach to loading the scripts in your Next project. By default, Next loads all scripts on page load, which may or may not be desirable behavior. Some of the reasons you may want to avoid that are:
- Performance: Only load these scripts when needed, or wait until the page load event (for things like _error, if you know it is not needed immediately)
- Preloading: It is not uncommon to need to run a script - or load a script from URL - before allowing your Next app to load.
- Polyfill preloading: This is the most common application of the previous point. Next bundles some polyfills, but only the ones NEXT requires - not the ones YOU require. If you require
IntersectionObserver
for example, it is on you to load it yourself. Since that is a HUGE polyfill (7kb minzipped!), you do not want to deliver it to all clients - only the ones that absolutely require it (IE <= 11, Safari ALL).
This NextScript component allows you to accomplish these goals and more in a flexible, minimal way. This component itself compiles down to 2.3kb minzipped (although it is only ever executed on the server!) and has a simple API.
Built originally out of the NextScript component itself, all of that functionality is supported, in addition to the features above.
Motivation
Take a look at this issue on the Next.js repository. This is a common problem and sometimes it is not clear how to resolve it. This component gives you a seamless, drop-in way to handle polyfills that is performant and efficient, only delivering what is needed to each browser.
This component also retains and expands Next's support for nonces on script elements: Simply assign the nonce
parameter and every script URL and every inline script will automatically get the nonce. This can be overridden on a per-script basis (can't imagine why you'd need to - but you can!).
Opt-In
This component's functionality is opt-in. You can configure the component to behave exactly as NextScript itself does, or you can optionally enable the above features. Simply rendering <NextScript />
will produce identical behavior to the component found in Next.
Getting started
Install from npm:
npm i @engineerapart/nextscript
# or
yarn add @engineerapart/nextscript
You will find an entry in NPM under nextscript
. This is our placeholder to avoid confusion with potential package collisions and is not the published package.
Examples
Duplicate Next.js
The only difference between this example and the default Next _document is where NextScript is imported from:
import { NextScript } from '@engineerapart/nextscript';
import Document, { Head, Main } from 'next/document';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<html>
<Head>
<style>{`body { margin: 0 } /* custom! */`}</style>
</Head>
<body className="custom_class">
<Main />
<NextScript />
</body>
</html>
);
}
}
Preload polyfills
The canonical example: How to preload polyfills, including polyfills not defined by this project.
import { NextScript, FeaturePolyfills } from '@engineerapart/nextscript';
import Document, { Head, Main } from 'next/document';
const features = [
FeaturePolyfills.FETCH,
FeaturePolyfills.CUSTOMEVENT,
{
test: `('entries' in Array.prototype)`,
feature: 'Array.prototype.entries',
},
];
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<html>
<Head></Head>
<body className="custom_class">
<Main />
<NextScript features={features} />
</body>
</html>
);
}
}
Polyfill Service
Current this component points to the Polyfill.io service, which serves ~150 million polyfill requests per DAY (4.5 billion/month!). However, eventually this project will also support pointing to your own self-hosted polyfill service, provided that it adheres to the Polyfill.io API (instructions for self-hosting the Polyfill.io service can be found in their repository.)
How it works
The default NextScript implementation captures the scripts generated by the Next build process and immediately renders them to <script>
tags in its render()
function.
This component takes a different approach: It collects the same script information from the build that the original Next component does, but instead of immediately rendering them, it inserts the script information into a preloader script. This provides the opportunity to:
- Load something else first
- Load something else after
- Load them on command
After you've configured the component, it will execute your configurations inside the preloader, and then execute the Next scripts. If you don't configure the component, it does exactly the same thing that the original Next component does!
API
You can configure the component to behave exactly as NextScript itself does, or you can optionally enable the above features. Simply rendering <NextScript />
will produce identical behavior to the component found in Next.
NextScript
Properties
If you are using Typescript, TS will automatically detect the typings, included with this project, and provide intellisense if you are using a TS-aware editor. These properties are also documented here.
All properties are optional. Rendering this component as <NextScript />
simply duplicates exactly what the NextScript component included with Next does.
| Prop | Type | Default | Description |
|--- |--- |--- |--- |
| nonce | string | undefined | Nonce to apply to all scripts loaded via this component |
| allowUserMonitoring | boolean | true | Sends the rum
URL parameter to the Polyfill service |
| minify | boolean | true | Instructs the Polyfill service to send minified polyfills |
| preLoadScripts | ScriptEntry[] | [] | Scripts to load before the Next scripts |
| postLoadScripts | ScriptEntry[] | [] | Scripts to load after the Next scripts |
| useFeatureDetection | boolean | true | Detect features in user's browser instead of letting the Polyfill service use the UA |
| features | PolyfillDefinition[] | [] | The list of required features, selected from the FeaturePolyfills enum, or objects you defined yourself if not included in this project (feel free to PR!) |
| preloadPolyfills | boolean | true | Flag to indicate whether the component will preload polyfills. If false, features
and useFeatureDetection
will be ignored. |
ScriptEntry
Interface
Defined in Typescript, this interface consists of the following fields:
| Field | Type | Default | Description |
|--- |--- |--- |--- |
| src | string | [required] | The script source to load. For now, only URL sources are supported. |
| nonce | string | undefined | The nonce to use for this script. If not supplied, the NextScript nonce will be used, or undefined if none are provided. |
| id | string | undefined | An ID to identify this script node. |
| async | boolean | false | Whether the async
flag should be applied to this script node. |
If you need to load arbitrary scripts, you can mount a script node before this element in your Next project. It's on our list to support, so it is coming!
PolyfillDefinition
Interface
This project supplies a small collection of default polyfill definitions to get you started, but does not currently support all 194 polyfills supported by Polyfill.io! Thus, it was important to be able to allow you to define arbitrary polyfills. This is accomplished by providing an array of PolyfillDefinition objects to the features
prop of NextScript
.
Defined in Typescript, this interface consists of the following fields:
| Field | Type | Default | Description |
|--- |--- |--- |--- |
| test | string | null | The test, as a string, the browser must run. For example '('fetch' in window)'
. This should not be null unless you are providing a Polyfill.io category (default, es6); however these are already configured in this project. If null, no test will be made, and the feature
value injected directly into the feature list sent to the Polyfill service |
| feature | string | [required] | The Feature ID known by the Polyfill service. A full list can be found at Polyfill.io |
TODO
- Support a self-hosted Polyfill service
- Add all known polyfills to the
FeaturePolyfills
collection - Support inline scripts to be injected in the preloader
Contributing
PRs are welcome. Make sure to read our contribution guidelines and code of conduct.
License
MIT