Skip to content

Commit

Permalink
Add browser polyfils for Node.js modules (webpack 5 backwards compat) (
Browse files Browse the repository at this point in the history
…#16022)

This adds the following Node.js core polyfills only when the import is used:

- `path`
- `stream`
- `vm`
- `crypto`
- `buffer`

Fixes #15948

We'll have a separate issue about adding warnings for the usage of these modules in the browser, some polyfills like crypto are quite heavy and generally not needed for most applications (included accidentally through node_modules).
  • Loading branch information
timneutkens authored Aug 10, 2020
1 parent 394e240 commit 843d584
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 7 deletions.
11 changes: 10 additions & 1 deletion packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,16 @@ export default async function getBaseWebpackConfig(
'next/router': 'next/dist/client/router.js',
'next/config': 'next/dist/next-server/lib/runtime-config.js',
'next/dynamic': 'next/dist/next-server/lib/dynamic.js',
next: NEXT_PROJECT_ROOT,
...(isServer
? {}
: {
stream: 'stream-browserify',
path: 'path-browserify',
crypto: 'crypto-browserify',
buffer: 'buffer',
vm: 'vm-browserify',
next: NEXT_PROJECT_ROOT,
}),
[PAGES_DIR_ALIAS]: pagesDir,
[DOT_NEXT_ALIAS]: distDir,
...getOptimizedAliases(isServer),
Expand Down
4 changes: 4 additions & 0 deletions packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"buffer": "5.6.0",
"cacache": "13.0.1",
"chokidar": "2.1.8",
"crypto-browserify": "3.12.0",
"css-loader": "3.5.3",
"cssnano-simple": "1.0.6",
"find-cache-dir": "3.3.1",
Expand All @@ -97,6 +98,7 @@
"native-url": "0.3.4",
"neo-async": "2.6.1",
"node-html-parser": "^1.2.19",
"path-browserify": "1.0.1",
"pnp-webpack-plugin": "1.6.4",
"postcss": "7.0.32",
"process": "0.11.10",
Expand All @@ -107,9 +109,11 @@
"resolve-url-loader": "3.1.1",
"sass-loader": "8.0.2",
"schema-utils": "2.6.6",
"stream-browserify": "3.0.0",
"style-loader": "1.2.1",
"styled-jsx": "3.3.0",
"use-subscription": "1.4.1",
"vm-browserify": "1.1.2",
"watchpack": "2.0.0-beta.13",
"web-vitals": "0.2.1",
"webpack": "4.44.1",
Expand Down
48 changes: 48 additions & 0 deletions test/integration/basic/pages/node-browser-polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Writable } from 'stream'
import path from 'path'
import crypto from 'crypto'
import { Buffer } from 'buffer'
import vm from 'vm'
import { useEffect, useState } from 'react'

export default function NodeBrowserPolyfillPage() {
const [state, setState] = useState({})
useEffect(() => {
let closedStream = false

const writable = new Writable({
write(_chunk, _encoding, callback) {
callback()
},
})

writable.on('finish', () => {
closedStream = true
})

writable.end()

setState({
path: path.join('/hello/world', 'test.txt'),
hash: crypto.createHash('sha256').update('hello world').digest('hex'),
buffer: Buffer.from('hello world').toString('utf8'),
vm: vm.runInNewContext('a + 5', { a: 100 }),
stream: closedStream,
})
}, [])

useEffect(() => {
if (state.vm) {
window.didRender = true
}
}, [state])

return (
<>
<div
id="node-browser-polyfills"
dangerouslySetInnerHTML={{ __html: JSON.stringify(state) }}
></div>
</>
)
}
19 changes: 19 additions & 0 deletions test/integration/basic/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { join } from 'path'
import { renderViaHTTP, findPort, launchApp, killApp } from 'next-test-utils'
import webdriver from 'next-webdriver'

// test suits
import hmr from './hmr'
Expand Down Expand Up @@ -33,6 +34,24 @@ describe('Basic Features', () => {
})
afterAll(() => killApp(context.server))

it('should polyfill Node.js modules', async () => {
const browser = await webdriver(context.appPort, '/node-browser-polyfills')
await browser.waitForCondition('window.didRender')

const data = await browser
.waitForElementByCss('#node-browser-polyfills')
.text()
const parsedData = JSON.parse(data)

expect(parsedData.vm).toBe(105)
expect(parsedData.hash).toBe(
'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'
)
expect(parsedData.path).toBe('/hello/world/test.txt')
expect(parsedData.buffer).toBe('hello world')
expect(parsedData.stream).toBe(true)
})

dynamic(context, (p, q) => renderViaHTTP(context.appPort, p, q))
hmr(context, (p, q) => renderViaHTTP(context.appPort, p, q))
errorRecovery(context, (p, q) => renderViaHTTP(context.appPort, p, q))
Expand Down
48 changes: 48 additions & 0 deletions test/integration/production/pages/node-browser-polyfills.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Writable } from 'stream'
import path from 'path'
import crypto from 'crypto'
import { Buffer } from 'buffer'
import vm from 'vm'
import { useEffect, useState } from 'react'

export default function NodeBrowserPolyfillPage() {
const [state, setState] = useState({})
useEffect(() => {
let closedStream = false

const writable = new Writable({
write(_chunk, _encoding, callback) {
callback()
},
})

writable.on('finish', () => {
closedStream = true
})

writable.end()

setState({
path: path.join('/hello/world', 'test.txt'),
hash: crypto.createHash('sha256').update('hello world').digest('hex'),
buffer: Buffer.from('hello world').toString('utf8'),
vm: vm.runInNewContext('a + 5', { a: 100 }),
stream: closedStream,
})
}, [])

useEffect(() => {
if (state.vm) {
window.didRender = true
}
}, [state])

return (
<>
<div
id="node-browser-polyfills"
dangerouslySetInnerHTML={{ __html: JSON.stringify(state) }}
></div>
</>
)
}
18 changes: 18 additions & 0 deletions test/integration/production/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,24 @@ describe('Production Usage', () => {
})
}

it('should polyfill Node.js modules', async () => {
const browser = await webdriver(appPort, '/node-browser-polyfills')
await browser.waitForCondition('window.didRender')

const data = await browser
.waitForElementByCss('#node-browser-polyfills')
.text()
const parsedData = JSON.parse(data)

expect(parsedData.vm).toBe(105)
expect(parsedData.hash).toBe(
'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'
)
expect(parsedData.path).toBe('/hello/world/test.txt')
expect(parsedData.buffer).toBe('hello world')
expect(parsedData.stream).toBe(true)
})

it('should allow etag header support', async () => {
const url = `http://localhost:${appPort}/`
const etag = (await fetch(url)).headers.get('ETag')
Expand Down
48 changes: 42 additions & 6 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4121,7 +4121,7 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"

[email protected], browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6, browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.13.0, browserslist@^4.3.6, browserslist@^4.6.4, browserslist@^4.8.3, browserslist@^4.8.5:
[email protected], browserslist@^4.0.0, browserslist@^4.11.1, browserslist@^4.13.0, browserslist@^4.3.6, browserslist@^4.6.4, browserslist@^4.8.3, browserslist@^4.8.5:
version "4.13.0"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.13.0.tgz#42556cba011e1b0a2775b611cba6a8eca18e940d"
integrity sha512-MINatJ5ZNrLnQ6blGvePd/QOz9Xtu+Ne+x29iQSCHfkU5BugKVJwZKn/iiL8UbpIpa3JhviKjz+XxMo0m2caFQ==
Expand All @@ -4131,6 +4131,14 @@ [email protected], browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7
escalade "^3.0.1"
node-releases "^1.1.58"

browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
version "1.7.7"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9"
integrity sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=
dependencies:
caniuse-db "^1.0.30000639"
electron-to-chromium "^1.2.7"

[email protected]:
version "1.4.0"
resolved "https://registry.yarnpkg.com/browserstack-local/-/browserstack-local-1.4.0.tgz#d979cac056f57b9af159b3bcd7fdc09b4354537c"
Expand Down Expand Up @@ -4460,11 +4468,21 @@ caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634:
version "1.0.30001023"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001023.tgz#f856f71af16a5a44e81f1fcefc1673912a43da72"

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001019, caniuse-lite@^1.0.30001020, caniuse-lite@^1.0.30001093:
caniuse-db@^1.0.30000639:
version "1.0.30001112"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30001112.tgz#89cb249402af2ebf2c23a5585d1bd78cb5e38732"
integrity sha512-Xrn3lVEIsvAAUmFVHKKComfyCRM59NfuV3EwCXqs2XLgOxAqYgrfEs0vDk+Dk7KctlAMh6CH/CSd1srJt503ZA==

caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001019, caniuse-lite@^1.0.30001020:
version "1.0.30001066"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz#0a8a58a10108f2b9bf38e7b65c237b12fd9c5f04"
integrity sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==

caniuse-lite@^1.0.30001093:
version "1.0.30001112"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001112.tgz#0fffc3b934ff56ff0548c37bc9dad7d882bcf672"
integrity sha512-J05RTQlqsatidif/38aN3PGULCLrg8OYQOlJUKbeYVzC2mGZkZLIztwRlB3MtrfLmawUmjFlNJvy/uhwniIe1Q==

[email protected]:
version "1.0.0"
resolved "https://registry.yarnpkg.com/capitalize/-/capitalize-1.0.0.tgz#dc802c580aee101929020d2ca14b4ca8a0ae44be"
Expand Down Expand Up @@ -5376,7 +5394,7 @@ cross-spawn@^7.0.0:
shebang-command "^2.0.0"
which "^2.0.1"

crypto-browserify@^3.11.0:
crypto-browserify@3.12.0, crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==
Expand Down Expand Up @@ -6190,6 +6208,11 @@ ejs@^2.6.1:
version "2.7.4"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"

electron-to-chromium@^1.2.7:
version "1.3.526"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.526.tgz#0e004899edf75afc172cce1b8189aac5dca646aa"
integrity sha512-HiroW5ZbGwgT8kCnoEO8qnGjoTPzJxduvV/Vv/wH63eo2N6Zj3xT5fmmaSPAPUM05iN9/5fIEkIg3owTtV6QZg==

electron-to-chromium@^1.3.488:
version "1.3.501"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.501.tgz#faa17a2cb0105ee30d5e1ca87eae7d8e85dd3175"
Expand Down Expand Up @@ -8108,7 +8131,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"

inherits@2, [email protected], inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
inherits@2, [email protected], inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"

Expand Down Expand Up @@ -11695,6 +11718,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"
integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==

[email protected]:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==

path-case@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/path-case/-/path-case-2.1.1.tgz#94b8037c372d3fe2906e465bb45e25d226e8eea5"
Expand Down Expand Up @@ -13337,7 +13365,7 @@ read@1, read@~1.0.1:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"

readable-stream@^3.4.0, readable-stream@^3.6.0:
readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==
Expand Down Expand Up @@ -14717,6 +14745,14 @@ stealthy-require@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"

[email protected]:
version "3.0.0"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f"
integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==
dependencies:
inherits "~2.0.4"
readable-stream "^3.5.0"

stream-browserify@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
Expand Down Expand Up @@ -15970,7 +16006,7 @@ vlq@^0.2.1, vlq@^0.2.2:
version "0.2.3"
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"

vm-browserify@^1.0.1:
vm-browserify@1.1.2, vm-browserify@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==
Expand Down

0 comments on commit 843d584

Please sign in to comment.