Fix Web Workers with Fast Refresh (#15145)

This commit is contained in:
Joe Haddad 2020-07-14 12:17:10 -04:00 committed by GitHub
parent b088881a5d
commit a131909857
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 10 deletions

View file

@ -122,7 +122,8 @@
"tree-kill": "1.2.1",
"typescript": "3.8.3",
"wait-port": "0.2.2",
"webpack-bundle-analyzer": "3.6.1"
"webpack-bundle-analyzer": "3.6.1",
"worker-loader": "2.0.0"
},
"resolutions": {
"browserslist": "^4.8.3",

View file

@ -6,8 +6,31 @@ import {
// @ts-ignore exists in webpack 5
RuntimeGlobals,
version,
compilation as Compilation,
} from 'webpack'
// Shared between webpack 4 and 5:
function injectRefreshFunctions(compilation: Compilation.Compilation) {
const hookVars: typeof compilation['mainTemplate']['hooks']['requireExtensions'] = (compilation
.mainTemplate.hooks as any).localVars
hookVars.tap('ReactFreshWebpackPlugin', (source) =>
Template.asString([
source,
'',
'// noop fns to prevent runtime errors during initialization',
'if (typeof self !== "undefined") {',
Template.indent('self.$RefreshReg$ = function () {};'),
Template.indent('self.$RefreshSig$ = function () {'),
Template.indent(Template.indent('return function (type) {')),
Template.indent(Template.indent(Template.indent('return type;'))),
Template.indent(Template.indent('};')),
Template.indent('};'),
'}',
])
)
}
function webpack4(compiler: Compiler) {
// Webpack 4 does not have a method to handle interception of module
// execution.
@ -16,6 +39,8 @@ function webpack4(compiler: Compiler) {
// https://github.com/webpack/webpack/blob/4c644bf1f7cb067c748a52614500e0e2182b2700/lib/MainTemplate.js#L200
compiler.hooks.compilation.tap('ReactFreshWebpackPlugin', (compilation) => {
injectRefreshFunctions(compilation)
const hookRequire: typeof compilation['mainTemplate']['hooks']['requireExtensions'] = (compilation
.mainTemplate.hooks as any).require
@ -106,6 +131,8 @@ function webpack5(compiler: Compiler) {
}
compiler.hooks.compilation.tap('ReactFreshWebpackPlugin', (compilation) => {
injectRefreshFunctions(compilation)
// @ts-ignore Exists in webpack 5
compilation.hooks.additionalTreeRuntimeRequirements.tap(
'ReactFreshWebpackPlugin',

View file

@ -13,14 +13,6 @@ declare const self: Window & RefreshRuntimeGlobals
// Hook into ReactDOM initialization
RefreshRuntime.injectIntoGlobalHook(self)
// noop fns to prevent runtime errors during initialization
self.$RefreshReg$ = function () {}
self.$RefreshSig$ = function () {
return function (type) {
return type
}
}
// Register global helpers
self.$RefreshHelpers$ = RefreshHelpers

View file

@ -0,0 +1,4 @@
const { Expensive } = require('./sharedCode')
Expensive()
self.postMessage(true)

View file

@ -0,0 +1,17 @@
export function Expensive() {
const start = performance.now()
let i = 99999
const bigArray = []
while (--i) {
bigArray.push(i)
}
const endTime = performance.now()
if (typeof window === 'undefined') {
console.log('[WORKER] Completed expensive function in', endTime - start)
} else {
console.log('[WEB] Completed expensive function in', endTime - start)
}
}

View file

@ -0,0 +1,18 @@
module.exports = {
webpack: (config, { isServer }) => {
config.module.rules.unshift({
test: /\.worker\.(js|ts|tsx)$/,
loader: 'worker-loader',
options: {
name: 'static/[hash].worker.js',
publicPath: '/_next/',
},
})
if (!isServer) {
config.output.globalObject = 'self'
}
return config
},
}

View file

@ -0,0 +1,39 @@
import * as React from 'react'
import DemoWorker from '../lib/demo.worker'
import { Expensive } from '../lib/sharedCode'
export default function Home() {
const [expensiveWebStatus, setExpensiveWebStatus] = React.useState('WAIT')
const [expensiveWorkerStatus, setExpensiveWorkerComplete] = React.useState(
'WAIT'
)
const worker = React.useRef()
React.useEffect(() => {
worker.current = new DemoWorker()
worker.current.addEventListener('message', ({ data }) => {
if (data) {
setExpensiveWorkerComplete('PASS')
}
})
worker.current.addEventListener('error', (data) => {
setExpensiveWorkerComplete('FAIL')
})
}, [worker, setExpensiveWorkerComplete])
React.useEffect(() => {
try {
Expensive()
setExpensiveWebStatus('PASS')
} catch {
setExpensiveWebStatus('FAIL')
}
}, [])
return (
<main>
<h1>$RefreshRegistry repro</h1>
<div id="web-status">Web: {expensiveWebStatus}</div>
<div id="worker-status">Worker: {expensiveWorkerStatus}</div>
</main>
)
}

View file

@ -0,0 +1,35 @@
/* eslint-env jest */
import { check, findPort, killApp, launchApp } from 'next-test-utils'
import webdriver from 'next-webdriver'
import { join } from 'path'
const appDir = join(__dirname, '../')
const context = {}
jest.setTimeout(1000 * 60 * 2)
describe('Web Workers with Fast Refresh', () => {
beforeAll(async () => {
context.appPort = await findPort()
context.server = await launchApp(appDir, context.appPort)
})
afterAll(() => {
killApp(context.server)
})
it('should pass on both client and worker', async () => {
let browser
try {
browser = await webdriver(context.appPort, '/')
await browser.waitForElementByCss('#web-status')
await check(() => browser.elementByCss('#web-status').text(), /PASS/i)
await browser.waitForElementByCss('#worker-status')
await check(() => browser.elementByCss('#worker-status').text(), /PASS/i)
} finally {
if (browser) {
await browser.close()
}
}
})
})

View file

@ -9853,7 +9853,7 @@ loader-utils@2.0.0, loader-utils@^2.0.0:
emojis-list "^3.0.0"
json5 "^2.1.2"
loader-utils@^1.4.0:
loader-utils@^1.0.0, loader-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613"
dependencies:
@ -14193,6 +14193,14 @@ schema-utils@2.6.6, schema-utils@^2.0.0, schema-utils@^2.6.1, schema-utils@^2.6.
ajv "^6.12.0"
ajv-keywords "^3.4.1"
schema-utils@^0.4.0:
version "0.4.7"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
dependencies:
ajv "^6.1.0"
ajv-keywords "^3.1.0"
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@ -16244,6 +16252,14 @@ worker-farm@^1.7.0:
dependencies:
errno "~0.1.7"
worker-loader@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"
integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==
dependencies:
loader-utils "^1.0.0"
schema-utils "^0.4.0"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"