Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for browserslist and legacyBrowsers experimental option #36584

Merged
merged 11 commits into from May 17, 2022
8 changes: 8 additions & 0 deletions packages/next/build/swc/options.js
Expand Up @@ -188,6 +188,7 @@ export function getLoaderSWCOptions({
hasReactRefresh,
nextConfig,
jsConfig,
supportedBrowsers,
// This is not passed yet as "paths" resolving is handled by webpack currently.
// resolvedBaseUrl,
}) {
Expand Down Expand Up @@ -238,6 +239,13 @@ export function getLoaderSWCOptions({
isServer,
pagesDir,
isPageFile,
...(supportedBrowsers && supportedBrowsers.length > 0
? {
env: {
targets: supportedBrowsers,
},
}
: {}),
}
}
}
29 changes: 25 additions & 4 deletions packages/next/build/webpack-config.ts
Expand Up @@ -25,6 +25,7 @@ import {
REACT_LOADABLE_MANIFEST,
SERVERLESS_DIRECTORY,
SERVER_DIRECTORY,
MODERN_BROWSERSLIST_TARGET,
} from '../shared/lib/constants'
import { execOnce } from '../shared/lib/utils'
import { NextConfigComplete } from '../server/config-shared'
Expand Down Expand Up @@ -63,16 +64,31 @@ const watchOptions = Object.freeze({

function getSupportedBrowsers(
dir: string,
isDevelopment: boolean
isDevelopment: boolean,
config: NextConfigComplete
): string[] | undefined {
let browsers: any
try {
browsers = browserslist.loadConfig({
const browsersListConfig = browserslist.loadConfig({
path: dir,
env: isDevelopment ? 'development' : 'production',
})
// Running `browserslist` resolves `extends` and other config features into a list of browsers
if (browsersListConfig && browsersListConfig.length > 0) {
browsers = browserslist(browsersListConfig)
}
} catch {}
return browsers

// When user has browserslist use that target
if (browsers && browsers.length > 0) {
return browsers
}

// When user does not have browserslist use the default target
// When `experimental.legacyBrowsers: false` the modern default is used
return config.experimental.legacyBrowsers
? undefined
: MODERN_BROWSERSLIST_TARGET
}

type ExcludesFalse = <T>(x: T | false) => x is T
Expand Down Expand Up @@ -339,7 +355,8 @@ export default async function getBaseWebpackConfig(
dir,
config
)
const supportedBrowsers = await getSupportedBrowsers(dir, dev)
const supportedBrowsers = await getSupportedBrowsers(dir, dev, config)

const hasRewrites =
rewrites.beforeFiles.length > 0 ||
rewrites.afterFiles.length > 0 ||
Expand Down Expand Up @@ -466,6 +483,9 @@ export default async function getBaseWebpackConfig(
fileReading: config.experimental.swcFileReading,
nextConfig: config,
jsConfig,
supportedBrowsers: config.experimental.browsersListForSwc
? supportedBrowsers
: undefined,
},
}
: {
Expand Down Expand Up @@ -1783,6 +1803,7 @@ export default async function getBaseWebpackConfig(
relay: config.compiler?.relay,
emotion: config.experimental?.emotion,
modularizeImports: config.experimental?.modularizeImports,
legacyBrowsers: config.experimental?.legacyBrowsers,
})

const cache: any = {
Expand Down
11 changes: 9 additions & 2 deletions packages/next/build/webpack/loaders/next-swc-loader.js
Expand Up @@ -36,8 +36,14 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {

let loaderOptions = this.getOptions() || {}

const { isServer, pagesDir, hasReactRefresh, nextConfig, jsConfig } =
loaderOptions
const {
isServer,
pagesDir,
hasReactRefresh,
nextConfig,
jsConfig,
supportedBrowsers,
} = loaderOptions
const isPageFile = filename.startsWith(pagesDir)

const swcOptions = getLoaderSWCOptions({
Expand All @@ -49,6 +55,7 @@ async function loaderTransform(parentTrace, source, inputSourceMap) {
hasReactRefresh,
nextConfig,
jsConfig,
supportedBrowsers,
})

const programmaticOptions = {
Expand Down
5 changes: 5 additions & 0 deletions packages/next/server/config-shared.ts
Expand Up @@ -79,6 +79,8 @@ export interface NextJsWebpackConfig {
}

export interface ExperimentalConfig {
legacyBrowsers?: boolean
browsersListForSwc?: boolean
manualClientBasePath?: boolean
newNextLinkBehavior?: boolean
disablePostcssPresetEnv?: boolean
Expand Down Expand Up @@ -473,6 +475,9 @@ export const defaultConfig: NextConfig = {
staticPageGenerationTimeout: 60,
swcMinify: false,
experimental: {
// TODO: change default in next major release (current v12.1.5)
legacyBrowsers: true,
browsersListForSwc: false,
// TODO: change default in next major release (current v12.1.5)
newNextLinkBehavior: false,
cpus: Math.max(
Expand Down
7 changes: 7 additions & 0 deletions packages/next/shared/lib/constants.ts
Expand Up @@ -26,6 +26,13 @@ export const CLIENT_PUBLIC_FILES_PATH = 'public'
export const CLIENT_STATIC_FILES_PATH = 'static'
export const CLIENT_STATIC_FILES_RUNTIME = 'runtime'
export const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__'
export const MODERN_BROWSERSLIST_TARGET = [
'chrome 61',
'edge 16',
'firefox 60',
'opera 48',
'safari 11',
]
export const NEXT_BUILTIN_DOCUMENT = '__NEXT_BUILTIN_DOCUMENT__'

// server/middleware-flight-manifest.js
Expand Down
22 changes: 22 additions & 0 deletions test/e2e/browserslist/app/pages/index.js
@@ -0,0 +1,22 @@
import React, { useEffect } from 'react'
const helloWorld = 'hello world'

class MyComp extends React.Component {
render() {
return <h1>Hello World</h1>
}
}

export default function Page() {
useEffect(() => {
;(async () => {
console.log(helloWorld)
})()
}, [])
return (
<>
{helloWorld}
<MyComp />
</>
)
}
53 changes: 53 additions & 0 deletions test/e2e/browserslist/browserslist.test.ts
@@ -0,0 +1,53 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP, fetchViaHTTP } from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
const appDir = path.join(__dirname, 'app')

describe('Browserslist', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(path.join(appDir, 'pages')),
'.browserslistrc': 'Chrome 73',
},
nextConfig: {
experimental: {
browsersListForSwc: true,
},
},
dependencies: {},
})
})
afterAll(() => next.destroy())

it('should apply browserslist target', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)

let finished = false
await Promise.all(
$('script')
.toArray()
.map(async (el) => {
const src = $(el).attr('src')
if (!src) return
if (src.includes('/index')) {
const code = await fetchViaHTTP(next.url, src).then((res) =>
res.text()
)

const isDev = (global as any).isNextDev
expect(
code.includes(isDev ? 'async ()=>{' : 'async()=>{console.log(')
).toBe(true)
finished = true
}
})
)
expect(finished).toBe(true)
})
})
53 changes: 53 additions & 0 deletions test/e2e/browserslist/legacybrowsers-false.test.ts
@@ -0,0 +1,53 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP, fetchViaHTTP } from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
const appDir = path.join(__dirname, 'app')

describe('legacyBrowsers: false', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(path.join(appDir, 'pages')),
},
nextConfig: {
experimental: {
legacyBrowsers: false,
browsersListForSwc: true,
},
},
dependencies: {},
})
})
afterAll(() => next.destroy())

it('should apply legacyBrowsers: true by default', async () => {
timneutkens marked this conversation as resolved.
Show resolved Hide resolved
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)

let finished = false
await Promise.all(
$('script')
.toArray()
.map(async (el) => {
const src = $(el).attr('src')
if (!src) return
if (src.includes('/index')) {
const code = await fetchViaHTTP(next.url, src).then((res) =>
res.text()
)

const isDev = (global as any).isNextDev
expect(
code.includes(isDev ? 'async ()=>{' : 'async()=>{console.log(')
).toBe(true)
finished = true
}
})
)
expect(finished).toBe(true)
})
})
52 changes: 52 additions & 0 deletions test/e2e/browserslist/legacybrowsers-true.test.ts
@@ -0,0 +1,52 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP, fetchViaHTTP } from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
const appDir = path.join(__dirname, 'app')

describe('legacyBrowsers: true', () => {
let next: NextInstance

beforeAll(async () => {
next = await createNext({
files: {
pages: new FileRef(path.join(appDir, 'pages')),
},
nextConfig: {
experimental: {
browsersListForSwc: true,
},
},
dependencies: {},
})
})
afterAll(() => next.destroy())

it('should apply legacyBrowsers: true by default', async () => {
const html = await renderViaHTTP(next.url, '/')
const $ = cheerio.load(html)

let finished = false
await Promise.all(
$('script')
.toArray()
.map(async (el) => {
const src = $(el).attr('src')
if (!src) return
if (src.includes('/index')) {
const code = await fetchViaHTTP(next.url, src).then((res) =>
res.text()
)

const isDev = (global as any).isNextDev
expect(
code.includes(isDev ? 'async ()=>{' : 'async()=>{console.log(')
).toBe(false)
finished = true
}
})
)
expect(finished).toBe(true)
})
})