diff --git a/packages/next/build/swc/options.js b/packages/next/build/swc/options.js index 08ac0e5334e90..9547bfc0a4de2 100644 --- a/packages/next/build/swc/options.js +++ b/packages/next/build/swc/options.js @@ -192,6 +192,7 @@ export function getLoaderSWCOptions({ hasReactRefresh, nextConfig, jsConfig, + supportedBrowsers, // This is not passed yet as "paths" resolving is handled by webpack currently. // resolvedBaseUrl, }) { @@ -242,6 +243,13 @@ export function getLoaderSWCOptions({ isServer, pagesDir, isPageFile, + ...(supportedBrowsers && supportedBrowsers.length > 0 + ? { + env: { + targets: supportedBrowsers, + }, + } + : {}), } } } diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index c1d8986edd0d9..eb2c76523fe69 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -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' @@ -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 = (x: T | false) => x is T @@ -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 || @@ -466,6 +483,9 @@ export default async function getBaseWebpackConfig( fileReading: config.experimental.swcFileReading, nextConfig: config, jsConfig, + supportedBrowsers: config.experimental.browsersListForSwc + ? supportedBrowsers + : undefined, }, } : { @@ -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 = { diff --git a/packages/next/build/webpack/loaders/next-swc-loader.js b/packages/next/build/webpack/loaders/next-swc-loader.js index f027c09b6e12c..f19b8039ebc22 100644 --- a/packages/next/build/webpack/loaders/next-swc-loader.js +++ b/packages/next/build/webpack/loaders/next-swc-loader.js @@ -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({ @@ -49,6 +55,7 @@ async function loaderTransform(parentTrace, source, inputSourceMap) { hasReactRefresh, nextConfig, jsConfig, + supportedBrowsers, }) const programmaticOptions = { diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 126dcdb9901a9..ca01d6194fb26 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -79,6 +79,8 @@ export interface NextJsWebpackConfig { } export interface ExperimentalConfig { + legacyBrowsers?: boolean + browsersListForSwc?: boolean manualClientBasePath?: boolean newNextLinkBehavior?: boolean disablePostcssPresetEnv?: boolean @@ -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( diff --git a/packages/next/shared/lib/constants.ts b/packages/next/shared/lib/constants.ts index 8c17e406a3e76..714a997dec5d3 100644 --- a/packages/next/shared/lib/constants.ts +++ b/packages/next/shared/lib/constants.ts @@ -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 diff --git a/test/e2e/browserslist/app/pages/index.js b/test/e2e/browserslist/app/pages/index.js new file mode 100644 index 0000000000000..5a2db92580e64 --- /dev/null +++ b/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

Hello World

+ } +} + +export default function Page() { + useEffect(() => { + ;(async () => { + console.log(helloWorld) + })() + }, []) + return ( + <> + {helloWorld} + + + ) +} diff --git a/test/e2e/browserslist/browserslist.test.ts b/test/e2e/browserslist/browserslist.test.ts new file mode 100644 index 0000000000000..cbd93eee1dda3 --- /dev/null +++ b/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) + }) +}) diff --git a/test/e2e/browserslist/legacybrowsers-false.test.ts b/test/e2e/browserslist/legacybrowsers-false.test.ts new file mode 100644 index 0000000000000..023ba6db26dab --- /dev/null +++ b/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 with legacyBrowsers: false correctly', 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) + }) +}) diff --git a/test/e2e/browserslist/legacybrowsers-true.test.ts b/test/e2e/browserslist/legacybrowsers-true.test.ts new file mode 100644 index 0000000000000..ec397bafcb256 --- /dev/null +++ b/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) + }) +})