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 @@ -186,6 +186,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 @@ -236,6 +237,13 @@ export function getLoaderSWCOptions({
isServer,
pagesDir,
isPageFile,
...(supportedBrowsers && supportedBrowsers.length > 0
? {
env: {
targets: supportedBrowsers,
},
}
: {}),
}
}
}
31 changes: 24 additions & 7 deletions packages/next/build/webpack-config.ts
Expand Up @@ -23,6 +23,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 @@ -60,16 +61,30 @@ const watchOptions = Object.freeze({

function getSupportedBrowsers(
dir: string,
isDevelopment: boolean
isDevelopment: boolean,
config: NextConfigComplete
): string[] | undefined {
let browsers: any
try {
browsers = browserslist.loadConfig({
path: dir,
env: isDevelopment ? 'development' : 'production',
})
// Running `browserslist` resolves `extends` and other config features into a list of browsers
browsers = browserslist(
browserslist.loadConfig({
path: dir,
env: isDevelopment ? 'development' : 'production',
})
)
} 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 @@ -334,7 +349,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 @@ -446,6 +462,7 @@ export default async function getBaseWebpackConfig(
fileReading: config.experimental.swcFileReading,
nextConfig: config,
jsConfig,
supportedBrowsers,
},
}
: {
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
3 changes: 3 additions & 0 deletions packages/next/server/config-shared.ts
Expand Up @@ -78,6 +78,7 @@ export interface NextJsWebpackConfig {
}

export interface ExperimentalConfig {
legacyBrowsers?: boolean
newNextLinkBehavior?: boolean
disablePostcssPresetEnv?: boolean
swcMinify?: boolean
Expand Down Expand Up @@ -467,6 +468,8 @@ export const defaultConfig: NextConfig = {
staticPageGenerationTimeout: 60,
swcMinify: false,
experimental: {
// TODO: change default in next major release (current v12.1.5)
legacyBrowsers: true,
// 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 @@ -25,6 +25,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',
]

// server/middleware-flight-manifest.js
export const MIDDLEWARE_FLIGHT_MANIFEST = 'middleware-flight-manifest'
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 (
<p>
{helloWorld}
<MyComp />
</p>
)
}
45 changes: 45 additions & 0 deletions test/e2e/browserslist/browserslist.test.ts
@@ -0,0 +1,45 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
import fetch from 'node-fetch'
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',
},
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 source = next.url + src
const code = await fetch(source).then((res) => res.text())
ijjk marked this conversation as resolved.
Show resolved Hide resolved

expect(code.includes('async()=>{console.log(')).toBe(true)
finished = true
}
})
)
expect(finished).toBe(true)
})
})
51 changes: 51 additions & 0 deletions test/e2e/browserslist/legacybrowsers-false.test.ts
@@ -0,0 +1,51 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
import fetch from 'node-fetch'
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')),
'next.config.js': `
module.exports = {
experimental: {
legacyBrowsers: false
}
}
`,
},
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 source = next.url + src
const code = await fetch(source).then((res) => res.text())

expect(code.includes('async()=>{console.log(')).toBe(true)
finished = true
}
})
)
expect(finished).toBe(true)
})
})
44 changes: 44 additions & 0 deletions test/e2e/browserslist/legacybrowsers-true.test.ts
@@ -0,0 +1,44 @@
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { renderViaHTTP } from 'next-test-utils'
import path from 'path'
import cheerio from 'cheerio'
import fetch from 'node-fetch'
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')),
},
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 source = next.url + src
const code = await fetch(source).then((res) => res.text())

expect(code.includes('async()=>{console.log(')).toBe(false)
finished = true
}
})
)
expect(finished).toBe(true)
})
})