rsnext/test/e2e/next-script-worker-strategy/index.test.ts
Houssein Djirdeh 79016b879f
Adds web worker support to <Script /> using Partytown (#34244)
## Summary

This PR adds a new `worker` strategy to the `<Script />` component that automatically relocates and executes the script in a web worker.

```jsx
<Script 
  strategy="worker"
  ...
/>
```

[Partytown](https://partytown.builder.io/) is used under the hood to provide this functionality.

## Behavior

- This will land as an experimental feature and will only work behind an opt-in flag in `next.config.js`:

  ```js
  experimental: {
    nextScriptWorkers: true
  }
  ```

- This setup use a similar approach to how ESLint and Typescript is used in Next.js by showing an error to the user to install the dependency locally themselves if they've enabled the experimental `nextScriptWorkers` flag.

  <img width="1068" alt="Screen Shot 2022-03-03 at 2 33 13 PM" src="https://user-images.githubusercontent.com/12476932/156639227-42af5353-a2a6-4126-936e-269112809651.png">
- For Partytown to work, a number of static files must be served directly from the site (see [docs](https://partytown.builder.io/copy-library-files)). In this PR, these files are automatically copied to a `~partytown` directory in `.next/static` during `next build` and `next dev` if the `nextScriptWorkers` flag is set to true.

## Checklist

- [X] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [X] Related issues linked using `fixes #number`
- [X] Integration tests added
- [X] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.

This PR fixes #31517.
2022-03-11 22:26:46 +00:00

216 lines
5.8 KiB
TypeScript

import webdriver from 'next-webdriver'
import { createNext } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { BrowserInterface } from 'test/lib/browsers/base'
import { waitFor } from 'next-test-utils'
describe('experimental.nextScriptWorkers: false with no Partytown dependency', () => {
let next: NextInstance
beforeAll(async () => {
next = await createNext({
files: {
'pages/index.js': `
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js"
strategy="worker"
/>
</>
)
}
`,
},
dependencies: {},
})
})
afterAll(() => next.destroy())
it('Partytown snippet is not injected to head if not enabled in configuration', async () => {
let browser: BrowserInterface
try {
browser = await webdriver(next.appPort, '/')
const snippetScript = await browser.eval(
`document.querySelector('script[data-partytown]')`
)
expect(snippetScript).toEqual(null)
} finally {
if (browser) await browser.close()
}
})
})
describe('experimental.nextScriptWorkers: true with required Partytown dependency', () => {
let next: NextInstance
beforeAll(async () => {
next = await createNext({
nextConfig: {
experimental: {
nextScriptWorkers: true,
},
},
files: {
'pages/index.js': `
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js"
strategy="worker"
/>
</>
)
}
`,
},
dependencies: {
'@builder.io/partytown': '0.4.2',
},
})
})
afterAll(() => next.destroy())
it('Partytown snippets are injected to head if enabled in configuration', async () => {
let browser: BrowserInterface
try {
browser = await webdriver(next.appPort, '/')
const snippetScript = await browser.eval(
`document.querySelector('script[data-partytown]').innerHTML`
)
const configScript = await browser.eval(
`document.querySelector('script[data-partytown-config]').innerHTML`
)
expect(snippetScript).not.toEqual(null)
// A default config is included that points to the correct folder that hosts partytown's static files
expect(configScript).not.toEqual(null)
expect(configScript.replace(/(?: *[\n\r])+ */g, '')).toEqual(
'partytown = {lib: "/_next/static/~partytown/"};'
)
} finally {
if (browser) await browser.close()
}
})
it('Worker scripts are modified by Partytown to execute on a worker thread', async () => {
let browser: BrowserInterface
try {
browser = await webdriver(next.appPort, '/')
const predefinedWorkerScripts = await browser.eval(
`document.querySelectorAll('script[type="text/partytown"]').length`
)
expect(predefinedWorkerScripts).toEqual(1)
await waitFor(1000)
// Partytown modifes type to "text/partytown-x" after it has been executed in the web worker
const processedWorkerScripts = await browser.eval(
`document.querySelectorAll('script[type="text/partytown-x"]').length`
)
expect(processedWorkerScripts).toEqual(1)
} finally {
if (browser) await browser.close()
}
})
})
describe('experimental.nextScriptWorkers: true with config override', () => {
let next: NextInstance
beforeAll(async () => {
next = await createNext({
nextConfig: {
experimental: {
nextScriptWorkers: true,
},
},
files: {
'pages/_document.js': `
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
<script
data-partytown-config
dangerouslySetInnerHTML={{
__html: \`
partytown = {
lib: "/_next/static/~partytown/",
debug: true
};
\`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
`,
'pages/index.js': `
import Script from 'next/script'
export default function Page() {
return (
<>
<Script
src="https://cdn.jsdelivr.net/npm/cookieconsent@3/build/cookieconsent.min.js"
strategy="worker"
/>
</>
)
}
`,
},
dependencies: {
'@builder.io/partytown': '0.4.2',
},
})
})
afterAll(() => next.destroy())
it('Partytown config script is overwritten', async () => {
let browser: BrowserInterface
try {
browser = await webdriver(next.appPort, '/')
const configScript = await browser.eval(
`document.querySelector('script[data-partytown-config]').innerHTML`
)
expect(configScript).not.toEqual(null)
expect(configScript.replace(/(?: *[\n\r])+ */g, '')).toEqual(
'partytown = {lib: "/_next/static/~partytown/",debug: true};'
)
} finally {
if (browser) await browser.close()
}
})
})