Skip to content

Commit

Permalink
[Experimental] Nomodule polyfills chunk (vercel#10212)
Browse files Browse the repository at this point in the history
* Polyfill Promise in polyfills chunk

* Override promise polyfill to use built-in

* Update sizes

* Update polyfills

* Test

* Add dep

* Use iife

* Unscope

* Revert "Unscope"

This reverts commit ab26bce.

* trigger

* Remove unused code

* Set helpers to true

* Update yarn.lock

* Fix test

* Update polyfills size

* Add comment

* Add back comment

* Put polyfills optimization under experimental flag

* Fix filename

* bring back promise for backwards compat until experimental feature is landed

* fix resolve alias check

* correct loader

* fix logic branches

* adjust !!

* adjust cache key

* Conditionally branch polyfill

* fix promise polyfill branching

* Re-add runtime

* fix base object

* fix yarn lock

* Add cache key

* correctly set caller

* add basic test

* Increment h=>i

* increment to j just in case

Co-authored-by: Joe Haddad <timer150@gmail.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
3 people authored and chibicode committed Feb 11, 2020
1 parent 06be851 commit dec7dbd
Show file tree
Hide file tree
Showing 12 changed files with 1,519 additions and 78 deletions.
19 changes: 19 additions & 0 deletions packages/next-polyfill-nomodule/package.json
@@ -0,0 +1,19 @@
{
"name": "@next/polyfill-nomodule",
"version": "0.0.0",
"description": "A polyfill for non-dead, nomodule browsers.",
"main": "dist/polyfill-nomodule.js",
"license": "MIT",
"scripts": {
"prepublish": "microbundle src/index.js -f iife --no-sourcemap --external none",
"build": "microbundle watch src/index.js -f iife --no-sourcemap --external none"
},
"devDependencies": {
"core-js": "3.6.4",
"microbundle": "0.11.0",
"object-assign": "4.1.1",
"promise-polyfill": "8.1.3",
"url-polyfill": "1.1.7",
"whatwg-fetch": "3.0.0"
}
}
67 changes: 67 additions & 0 deletions packages/next-polyfill-nomodule/src/index.js
@@ -0,0 +1,67 @@
import 'core-js/modules/es6.array.copy-within'
import 'core-js/modules/es6.array.fill'
import 'core-js/modules/es6.array.find'
import 'core-js/modules/es6.array.find-index'
import 'core-js/modules/es7.array.flat-map'
import 'core-js/modules/es6.array.from'
import 'core-js/modules/es7.array.includes'
import 'core-js/modules/es6.array.iterator'
import 'core-js/modules/es6.array.of'
import 'core-js/modules/es6.array.species'
import 'core-js/modules/es6.function.has-instance'
import 'core-js/modules/es6.map'
import 'core-js/modules/es6.number.constructor'
import 'core-js/modules/es6.number.epsilon'
import 'core-js/modules/es6.number.is-finite'
import 'core-js/modules/es6.number.is-integer'
import 'core-js/modules/es6.number.is-nan'
import 'core-js/modules/es6.number.is-safe-integer'
import 'core-js/modules/es6.number.max-safe-integer'
import 'core-js/modules/es6.number.min-safe-integer'
import 'core-js/modules/es7.object.entries'
import 'core-js/modules/es7.object.get-own-property-descriptors'
import 'core-js/modules/es6.object.is'
import 'core-js/modules/es7.object.values'
import 'core-js/modules/es6.reflect.apply'
import 'core-js/modules/es6.reflect.construct'
import 'core-js/modules/es6.reflect.define-property'
import 'core-js/modules/es6.reflect.delete-property'
import 'core-js/modules/es6.reflect.get'
import 'core-js/modules/es6.reflect.get-own-property-descriptor'
import 'core-js/modules/es6.reflect.get-prototype-of'
import 'core-js/modules/es6.reflect.has'
import 'core-js/modules/es6.reflect.is-extensible'
import 'core-js/modules/es6.reflect.own-keys'
import 'core-js/modules/es6.reflect.prevent-extensions'
import 'core-js/modules/es6.reflect.set'
import 'core-js/modules/es6.reflect.set-prototype-of'
import 'core-js/modules/es6.regexp.constructor'
import 'core-js/modules/es6.regexp.flags'
import 'core-js/modules/es6.regexp.match'
import 'core-js/modules/es6.regexp.replace'
import 'core-js/modules/es6.regexp.split'
import 'core-js/modules/es6.regexp.search'
import 'core-js/modules/es6.set'
import 'core-js/modules/es6.symbol'
import 'core-js/modules/es7.symbol.async-iterator'
import 'core-js/modules/es6.string.code-point-at'
import 'core-js/modules/es6.string.ends-with'
import 'core-js/modules/es6.string.from-code-point'
import 'core-js/modules/es6.string.includes'
import 'core-js/modules/es6.string.iterator'
import 'core-js/modules/es7.string.pad-start'
import 'core-js/modules/es7.string.pad-end'
import 'core-js/modules/es6.string.raw'
import 'core-js/modules/es6.string.repeat'
import 'core-js/modules/es6.string.starts-with'
import 'core-js/modules/es7.string.trim-left'
import 'core-js/modules/es7.string.trim-right'
import 'core-js/modules/es6.weak-map'
import 'core-js/modules/es6.weak-set'

// Specialized Packages:
import 'promise-polyfill/src/polyfill'
import 'whatwg-fetch'
import 'url-polyfill'
import assign from 'object-assign'
Object.assign = assign
5 changes: 4 additions & 1 deletion packages/next/build/babel/preset.ts
Expand Up @@ -64,6 +64,9 @@ module.exports = (
const supportsESM = api.caller(supportsStaticESM)
const isServer = api.caller((caller: any) => !!caller && caller.isServer)
const isModern = api.caller((caller: any) => !!caller && caller.isModern)
const isPolyfillsOptimization = api.caller(
(caller: any) => !!caller && caller.polyfillsOptimization
)
const isLaxModern =
isModern ||
(options['preset-env']?.targets &&
Expand Down Expand Up @@ -152,7 +155,7 @@ module.exports = (
!isServer && [
require('@babel/plugin-transform-runtime'),
{
corejs: 2,
corejs: isPolyfillsOptimization ? false : 2,
helpers: true,
regenerator: true,
useESModules: supportsESM && presetEnvConfig.modules !== 'commonjs',
Expand Down
98 changes: 68 additions & 30 deletions packages/next/build/webpack-config.ts
Expand Up @@ -56,7 +56,10 @@ const escapePathVariables = (value: any) => {
: value
}

function getOptimizedAliases(isServer: boolean): { [pkg: string]: string } {
function getOptimizedAliases(
isServer: boolean,
polyfillsOptimization: boolean
): { [pkg: string]: string } {
if (isServer) {
return {}
}
Expand All @@ -65,33 +68,48 @@ function getOptimizedAliases(isServer: boolean): { [pkg: string]: string } {
const stubObjectAssign = path.join(__dirname, 'polyfills', 'object-assign.js')

const shimAssign = path.join(__dirname, 'polyfills', 'object.assign')
return {
return Object.assign(
{},
// Polyfill: Window#fetch
__next_polyfill__fetch: require.resolve('whatwg-fetch'),
unfetch$: stubWindowFetch,
'isomorphic-unfetch$': stubWindowFetch,
'whatwg-fetch$': path.join(
__dirname,
'polyfills',
'fetch',
'whatwg-fetch.js'
),

// Polyfill: Object.assign
__next_polyfill__object_assign: require.resolve('object-assign'),
'object-assign$': stubObjectAssign,
'@babel/runtime-corejs2/core-js/object/assign': stubObjectAssign,

// Stub Package: object.assign
'object.assign/auto': path.join(shimAssign, 'auto.js'),
'object.assign/implementation': path.join(shimAssign, 'implementation.js'),
'object.assign$': path.join(shimAssign, 'index.js'),
'object.assign/polyfill': path.join(shimAssign, 'polyfill.js'),
'object.assign/shim': path.join(shimAssign, 'shim.js'),

// Replace: full URL polyfill with platform-based polyfill
// url: require.resolve('native-url'),
}
polyfillsOptimization
? undefined
: {
__next_polyfill__fetch: require.resolve('whatwg-fetch'),
},
{
unfetch$: stubWindowFetch,
'isomorphic-unfetch$': stubWindowFetch,
'whatwg-fetch$': path.join(
__dirname,
'polyfills',
'fetch',
'whatwg-fetch.js'
),
},
polyfillsOptimization
? undefined
: {
// Polyfill: Object.assign
__next_polyfill__object_assign: require.resolve('object-assign'),
'@babel/runtime-corejs2/core-js/object/assign': stubObjectAssign,
},
{
'object-assign$': stubObjectAssign,

// Stub Package: object.assign
'object.assign/auto': path.join(shimAssign, 'auto.js'),
'object.assign/implementation': path.join(
shimAssign,
'implementation.js'
),
'object.assign$': path.join(shimAssign, 'index.js'),
'object.assign/polyfill': path.join(shimAssign, 'polyfill.js'),
'object.assign/shim': path.join(shimAssign, 'shim.js'),

// Replace: full URL polyfill with platform-based polyfill
// url: require.resolve('native-url'),
}
)
}

export default async function getBaseWebpackConfig(
Expand Down Expand Up @@ -145,6 +163,7 @@ export default async function getBaseWebpackConfig(
babelPresetPlugins,
hasModern: !!config.experimental.modern,
development: dev,
polyfillsOptimization: !!config.experimental.polyfillsOptimization,
},
},
// Backwards compat
Expand Down Expand Up @@ -191,7 +210,9 @@ export default async function getBaseWebpackConfig(
),
[CLIENT_STATIC_FILES_RUNTIME_POLYFILLS]: path.join(
NEXT_PROJECT_ROOT_DIST_CLIENT,
'polyfills.js'
config.experimental.polyfillsOptimization
? 'polyfills-nomodule.js'
: 'polyfills.js'
),
}
: undefined
Expand Down Expand Up @@ -241,7 +262,17 @@ export default async function getBaseWebpackConfig(
next: NEXT_PROJECT_ROOT,
[PAGES_DIR_ALIAS]: pagesDir,
[DOT_NEXT_ALIAS]: distDir,
...getOptimizedAliases(isServer),
...getOptimizedAliases(
isServer,
!!config.experimental.polyfillsOptimization
),

// Temporary to allow runtime-corejs2 to be stubbed in experimental polyfillsOptimization
...(config.experimental.polyfillsOptimization
? {
'@babel/runtime-corejs2': '@babel/runtime',
}
: undefined),
},
mainFields: isServer ? ['main', 'module'] : ['browser', 'module', 'main'],
plugins: [PnpWebpackPlugin],
Expand Down Expand Up @@ -485,8 +516,12 @@ export default async function getBaseWebpackConfig(
if (
!res.match(/next[/\\]dist[/\\]next-server[/\\]/) &&
(res.match(/[/\\]next[/\\]dist[/\\]/) ||
// This is the @babel/plugin-transform-runtime "helpers: true" option
res.match(/node_modules[/\\]@babel[/\\]runtime[/\\]/) ||
res.match(/node_modules[/\\]@babel[/\\]runtime-corejs2[/\\]/))
(!config.experimental.polyfillsOptimization &&
res.match(
/node_modules[/\\]@babel[/\\]runtime-corejs2[/\\]/
)))
) {
return callback()
}
Expand Down Expand Up @@ -684,6 +719,9 @@ export default async function getBaseWebpackConfig(
'process.env.__NEXT_MODERN_BUILD': JSON.stringify(
config.experimental.modern && !dev
),
'process.env.__NEXT_POLYFILLS_OPTIMIZATION': JSON.stringify(
!!config.experimental.polyfillsOptimization
),
'process.env.__NEXT_GRANULAR_CHUNKS': JSON.stringify(
config.experimental.granularChunks && !dev
),
Expand Down
9 changes: 7 additions & 2 deletions packages/next/build/webpack/loaders/next-babel-loader.js
Expand Up @@ -2,9 +2,9 @@ import babelLoader from 'babel-loader'
import { basename, join } from 'path'
import hash from 'string-hash'

// increment 'e' to invalidate cache
// increment 'j' to invalidate cache
// eslint-disable-next-line no-useless-concat
const cacheKey = 'babel-cache-' + 'h' + '-'
const cacheKey = 'babel-cache-' + 'j' + '-'
const nextBabelPreset = require('../../babel/preset')

const getModernOptions = (babelOptions = {}) => {
Expand Down Expand Up @@ -59,6 +59,7 @@ module.exports = babelLoader.custom(babel => {
hasModern: opts.hasModern,
babelPresetPlugins: opts.babelPresetPlugins,
development: opts.development,
polyfillsOptimization: opts.polyfillsOptimization,
}
const filename = join(opts.cwd, 'noop.js')
const loader = Object.assign(
Expand All @@ -71,6 +72,7 @@ module.exports = babelLoader.custom(babel => {
(opts.isServer ? '-server' : '') +
(opts.isModern ? '-modern' : '') +
(opts.hasModern ? '-has-modern' : '') +
(opts.polyfillsOptimization ? '-new-polyfills' : '') +
(opts.development ? '-development' : '-production') +
JSON.stringify(
babel.loadPartialConfig({
Expand All @@ -93,6 +95,7 @@ module.exports = babelLoader.custom(babel => {
delete loader.hasModern
delete loader.pagesDir
delete loader.babelPresetPlugins
delete loader.polyfillsOptimization
delete loader.development
return { loader, custom }
},
Expand All @@ -107,6 +110,7 @@ module.exports = babelLoader.custom(babel => {
pagesDir,
babelPresetPlugins,
development,
polyfillsOptimization,
},
}
) {
Expand All @@ -130,6 +134,7 @@ module.exports = babelLoader.custom(babel => {

options.caller.isServer = isServer
options.caller.isModern = isModern
options.caller.polyfillsOptimization = polyfillsOptimization
options.caller.isDev = development

options.plugins = options.plugins || []
Expand Down
21 changes: 14 additions & 7 deletions packages/next/client/index.js
Expand Up @@ -14,13 +14,20 @@ import { isDynamicRoute } from '../next-server/lib/router/utils/is-dynamic'

/// <reference types="react-dom/experimental" />

// Polyfill Promise globally
// This is needed because Webpack's dynamic loading(common chunks) code
// depends on Promise.
// So, we need to polyfill it.
// See: https://webpack.js.org/guides/code-splitting/#dynamic-imports
if (!window.Promise) {
window.Promise = require('@babel/runtime-corejs2/core-js/promise')
if (process.env.__NEXT_POLYFILLS_OPTIMIZATION) {
if (!('finally' in Promise.prototype)) {
// eslint-disable-next-line no-extend-native
Promise.prototype.finally = require('finally-polyfill')
}
} else {
// Polyfill Promise globally
// This is needed because Webpack's dynamic loading(common chunks) code
// depends on Promise.
// So, we need to polyfill it.
// See: https://webpack.js.org/guides/code-splitting/#dynamic-imports
if (!self.Promise) {
self.Promise = require('@babel/runtime-corejs2/core-js/promise')
}
}

const data = JSON.parse(document.getElementById('__NEXT_DATA__').textContent)
Expand Down
1 change: 1 addition & 0 deletions packages/next/client/polyfills-nomodule.js
@@ -0,0 +1 @@
import '@next/polyfill-nomodule'
2 changes: 2 additions & 0 deletions packages/next/package.json
Expand Up @@ -73,6 +73,7 @@
"@babel/preset-typescript": "7.7.2",
"@babel/runtime": "7.7.2",
"@babel/runtime-corejs2": "7.7.2",
"@next/polyfill-nomodule": "0.0.0",
"amphtml-validator": "1.0.23",
"async-retry": "1.2.3",
"async-sema": "3.0.0",
Expand All @@ -96,6 +97,7 @@
"escape-string-regexp": "2.0.0",
"etag": "1.8.1",
"file-loader": "4.2.0",
"finally-polyfill": "0.1.0",
"find-up": "4.0.0",
"fork-ts-checker-webpack-plugin": "3.1.1",
"fresh": "0.5.2",
Expand Down
5 changes: 5 additions & 0 deletions test/integration/polyfilling-minimal/next.config.js
@@ -0,0 +1,5 @@
module.exports = {
experimental: {
polyfillsOptimization: true,
},
}
1 change: 1 addition & 0 deletions test/integration/polyfilling-minimal/pages/index.js
@@ -0,0 +1 @@
export default () => <p>hi</p>
23 changes: 23 additions & 0 deletions test/integration/polyfilling-minimal/test/index.test.js
@@ -0,0 +1,23 @@
/* eslint-env jest */
/* global jasmine */
import { remove } from 'fs-extra'
import { nextBuild } from 'next-test-utils'
import { join } from 'path'

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1

const appDir = join(__dirname, '../')

describe('Polyfilling (minimal)', () => {
beforeAll(async () => {
await remove(join(appDir, '.next'))
})

it('should compile successfully', async () => {
const { code, stdout } = await nextBuild(appDir, [], {
stdout: true,
})
expect(code).toBe(0)
expect(stdout).toMatch(/Compiled successfully/)
})
})

0 comments on commit dec7dbd

Please sign in to comment.