Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
27 changes: 27 additions & 0 deletions packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

Expand Down Expand Up @@ -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',
Expand Down
8 changes: 0 additions & 8 deletions packages/react-refresh-utils/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions test/integration/worker-loader/lib/demo.worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const { Expensive } = require('./sharedCode')

Expensive()
self.postMessage(true)
17 changes: 17 additions & 0 deletions test/integration/worker-loader/lib/sharedCode.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
18 changes: 18 additions & 0 deletions test/integration/worker-loader/next.config.js
Original file line number Diff line number Diff line change
@@ -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
},
}
39 changes: 39 additions & 0 deletions test/integration/worker-loader/pages/index.js
Original file line number Diff line number Diff line change
@@ -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>
)
}
35 changes: 35 additions & 0 deletions test/integration/worker-loader/test/index.test.js
Original file line number Diff line number Diff line change
@@ -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()
}
}
})
})
18 changes: 17 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down