Skip to content

Commit

Permalink
Add targets config for preset-env
Browse files Browse the repository at this point in the history
Add preset-env config to support polyfilling with core-js@3
Add Babel plugins (temporarily) for nullish coalescing and optional chaining, as Webpack can't currently handle these untranspiled
Add BrowserSupport docs

Closes #550
  • Loading branch information
insin committed May 19, 2020
1 parent d028177 commit 524a642
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 18 deletions.
39 changes: 29 additions & 10 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,43 @@
## Breaking Changes

- Node.js 8 is no longer supported; Node.js 10.13.0 is now the minimum required version, as per many of nwb's dependencies.
- Removed support for deprecated `babel.stage` and `webpack.uglify` config.

**Browser Support**

- Removed default polyfills for `Promise`, `fetch()` and `Object.assign()` and deprecated `polyfill` config.
- If you need to support older browsers, you will now need to provide the necessary polyfills yourself in your app.
- The [react-app-polyfill](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#react-app-polyfill) module provides polyfills for IE 9-11, and for stable language features - its instructions also work for apps using nwb.
- Deprecated using a string for [`webpack.autoprefixer` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#autoprefixer-object) to configure supported browsers - this will no longer do anything and should be moved to the new [`browsers` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object).

If you need to support older browsers, you will now need to include the necessary polyfills in your app - see the new [Browser Support docs](https://github.com/insin/nwb/blob/master/docs/BrowserSupport.md#browser-support) for details on polyfilling and suggested modules it's to provide them.

If this change affects your app, a quick fix is to use [react-app-polyfill](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill)'s IE11 polyfill, which is equivalent to what nwb's default polyfill used to be:

```js
import 'react-app-polyfill/ie11'
```

- For apps and quick commands, `@babel/preset-env` is now configured to [only transpile the necessary ECMAScript 2015+ for supported browsers](https://github.com/insin/nwb/blob/master/docs/BrowserSupport.md#default-browser-support).

When running a development server, this defaults to the most recent version of Chrome, Firefox or Safari, so you _may_ need to adjust [`browsers.development` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object) if you're using an out of date browser and you **will** need to adjust it if you're developing with an older browser supported by your app.

- Default browser configuration for Autoprefixer when building an app has changed from [`>1%, last 4 versions, Firefox ESR, not ie < 9`](https://browserl.ist/?q=%3E1%25%2C+last+4+versions%2C+Firefox+ESR%2C+not+ie+%3C+9) to [`>0.2%, not dead, not op_mini all`](https://browserl.ist/?q=%3E0.2%25%2C+not+dead%2C+not+op_mini+all).
- copy-webpack-plugin v6.0.0 [has breaking changes to its options](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md#600-2020-05-15) which you should read if you've configured the [`webpack.copy` option](https://github.com/insin/nwb/blob/master/docs/Configuration.md#copy-array--object).

When running a development server, the default browser configuration has changed to [`last 1 chrome version, last 1 firefox version, last 1 safari version`](https://browserl.ist/?q=last+1+chrome+version%2C+last+1+firefox+version%2C+last+1+safari+version).

**Configuration**

- Deprecated using a string for [`webpack.autoprefixer` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#autoprefixer-object) to configure supported browsers - this will no longer do anything and should be moved to the new [`browsers` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object).
- Removed support for `babel.stage` and `webpack.uglify` config deprecated in nwb v0.24.0.
- copy-webpack-plugin v6.0.0 [has breaking changes to its options](https://github.com/webpack-contrib/copy-webpack-plugin/blob/master/CHANGELOG.md#600-2020-05-15) which you should read if you're using [`webpack.copy` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#copy-array--object).

In particular, the `ignore` option in a copy pattern must now be put inside the new `globOptions` option.
- file-loader v6.0.0 [changed its default hashing algorithm](https://github.com/webpack-contrib/file-loader/blob/master/CHANGELOG.md#600-2020-03-17).

## Added
**Dependencies**

- Added top-level [`browsers` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object) to configure supported browsers. This supports using separate browserslist configuration for development and production.
- file-loader v6.0.0 [changed its default hashing algorithm](https://github.com/webpack-contrib/file-loader/blob/master/CHANGELOG.md#600-2020-03-17) so hashes in output filenames will change after updating to this release, even if their contents haven't changed.

## Changed
## Added

- Default browser configuration for Autoprefixer when running the development server for an app has changed from [`>1%, last 4 versions, Firefox ESR, not ie < 9`](https://browserl.ist/?q=%3E1%25%2C+last+4+versions%2C+Firefox+ESR%2C+not+ie+%3C+9) to [`last 1 chrome version, last 1 firefox version, last 1 safari version`](https://browserl.ist/?q=last+1+chrome+version%2C+last+1+firefox+version%2C+last+1+safari+version).
- Added top-level [`browsers` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object) to configure supported browsers. This supports using separate browserslist queries for development and production.
- Added [Browser Support docs](https://github.com/insin/nwb/blob/master/docs/BrowserSupport.md#browser-support), with a section on [polyfilling missing language features](https://github.com/insin/nwb/blob/master/docs/BrowserSupport.md#polyfilling-missing-language-features).

## Dependencies

Expand Down
81 changes: 81 additions & 0 deletions docs/BrowserSupport.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
## Browser Support

nwb's default configuration supports modern browsers. Support for Internet Explorer 9, 10 and 11 requires [polyfills](#supporting-internet-explorer).

### Default Browser Support

nwb uses the following [Browserslist](https://github.com/browserslist/browserslist#browserslist-) queries by default:

- [`last 1 chrome version, last 1 firefox version, last 1 safari version`](https://browserl.ist/?q=last+1+chrome+version%2C+last+1+firefox+version%2C+last+1+safari+version) for development, when running the development server with `nwb serve` (or quick commands such as `npm react run`)
- [`>0.2%, not dead, not op_mini all`](https://browserl.ist/?q=%3E0.2%25%2C+not+dead%2C+not+op_mini+all) for production, when creating a build with `nwb build` (or quick commands such as `npm react build`)

> Use the links above to check which browsers and versions these queries currently resolve to.
These are used to configure:

- [`@babel/preset-env`'s `targets` option](https://babeljs.io/docs/en/babel-preset-env#targets), so it only transpiles the necessary ECMAScript 2015+ for supported browsers.
- [Autoprefixer's `overrideBrowserslist` option](https://github.com/postcss/autoprefixer#options), so it only includes the necessary CSS prefixes for supported browsers.

### Configuring Browser Support

If your app needs to support more (or fewer!) browsers, you can tweak browser support settings using [`browsers` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object).

Broadening the range of supported browsers will ensure your app works for everone who needs to use it, while narrowing the range may help decrease your bundle sizes, if less code needs to be transpiled and fewer Babel helpers need to be imported.

For example, IE9 is considered a "dead" browser in Browserslist queries, so if you needed to support it, you could specifically enable it in [`browsers.production` config](https://github.com/insin/nwb/blob/master/docs/Configuration.md#browsers-string--arraystring--object) like so:

```js
module.exports = {
browsers {
production: '>0.2%, not dead, not op_mini all, ie 9'
}
}
```

You can see that [IE 9 has now been added](https://browserl.ist/?q=%3E0.2%25%2C+not+dead%2C+not+op_mini+all%2C+ie+9) to the list of supported browsers.

## Polyfilling Missing Language Features

### Supporting Internet Explorer

[react-app-polyfill](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#react-app-polyfill) provides convenient collection of polyfills for IE9 and IE11.

If you need to support Internet Explorer, install react-app-polyfill and import the appropriate polyfill entry point as the first thing in your app's entry point (usually `src/index.js`):

```
npm install react-app-polyfill
```
```js
import 'react-app-polyfill/ie11'
```

See [react-app-polyfill's Supporting Internet Explorer docs](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#supporting-internet-explorer) for more details.

### Manual Polyfilling

If there are specific language features missing from one of your supported browsers, you can polyfill them manually by installing [core-js](https://github.com/zloirock/core-js#core-js) and importing the appropriate polyfills at the top of your app's entry point (usually `src/index.js`).

e.g. if you want to use [`Object.values()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values) in your app, but one of your target browsers doesn't support it:

```
npm install core-js
```
```js
import 'core-js/features/object/values'
```

### Automatic Polyfilling

nwb configures `@babel/preset-env`'s [`useBuiltins: 'entry'` option](https://babeljs.io/docs/en/next/babel-preset-env), which will look for a core-js entry point import in your code and replace it with a specific list of polyfill imports to cover the range of supported browsers. See the [core-js docs for an example of this feature in action](https://github.com/zloirock/core-js#babelpreset-env).

To make use of this, import a core-js entry point at the top of your app's entry point (usually `src/index.js`):

```js
import 'core-js/stable'
```

react-app-polyfill also provides [an entry point for polyfilling stable language features using core-js](https://github.com/facebook/create-react-app/tree/master/packages/react-app-polyfill#polyfilling-other-language-features), so nwb's Babel config supports transpiling react-app-polyfill to allow `@babel/preset-env` to do its thing:

```js
import 'react-app-polyfill/stable'
```
2 changes: 2 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ If configured, it must be one of the following:

Configures [Browserslist](https://github.com/browserslist/browserslist#browserslist-) queries specifying the range of browsers your app supports.

This is used to configure [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targets) (so it only transpiles the necessary ECMAScript 2015+ for your target browsers) and [Autoprefixer](https://github.com/postcss/autoprefixer#browsers) (for managing vendor prefixes in your CSS).

If you don't configure this, nwb's default configuration is:

- `'last 1 chrome version, last 1 firefox version, last 1 safari version'` for development (when running a development server with `nwb serve`)
Expand Down
2 changes: 1 addition & 1 deletion docs/Features.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
- **A toolkit, not a boilerplate.**
- Uses [Webpack 4](https://webpack.js.org/), [Babel 7](https://babeljs.io/) and [Karma 5](https://karma-runner.github.io/).
- Provides tooling for [React](https://facebook.github.io/react/) apps and components, [Preact](https://preactjs.com/) apps, [Inferno](https://infernojs.org/) apps, and vanilla JS web apps and npm modules.
- Use modern JavaScript features, and JSX.
- Use modern JavaScript features and JSX.
- Use proposed JavaScript features now.
- Import CSS (and font resources) and images to be managed by Webpack.
- [Autoprefixed](https://github.com/postcss/autoprefixer#autoprefixer-) CSS, so you don't need to write browser prefixes; you can also configure your own [PostCSS](https://postcss.org/) plugins.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@

"@babel/cli": "7.8.4",
"@babel/core": "7.9.6",
"@babel/plugin-proposal-nullish-coalescing-operator": "7.8.3",
"@babel/plugin-proposal-optional-chaining": "7.9.0",
"@babel/plugin-syntax-dynamic-import": "7.8.3",
"@babel/plugin-syntax-jsx": "7.8.3",
"@babel/plugin-transform-react-constant-elements": "7.9.0",
Expand Down
17 changes: 17 additions & 0 deletions src/appCommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import runSeries from 'run-series'
import merge from 'webpack-merge'

import cleanApp from './commands/clean-app'
import {DEFAULT_BROWSERS_DEV, DEFAULT_BROWSERS_PROD} from './constants'
import {directoryExists, install} from './utils'
import webpackBuild from './webpackBuild'
import webpackServer from './webpackServer'
Expand Down Expand Up @@ -58,6 +59,14 @@ export function createBuildConfig(args: Object, extra: Object = {}) {
let filenamePattern = production ? '[name].[chunkhash:8].js' : '[name].js'

let config: Object = {
babel: {
env: {
targets: DEFAULT_BROWSERS_PROD,
useBuiltIns: 'entry',
corejs: 3,
exclude: ['transform-typeof-symbol'],
},
},
devtool: 'source-map',
entry: {
app: [entry],
Expand Down Expand Up @@ -94,6 +103,14 @@ export function createServeConfig(args: Object, ...extra: Object[]) {
let dist = path.resolve(args._[2] || 'dist')

let config: Object = {
babel: {
env: {
targets: DEFAULT_BROWSERS_DEV,
useBuiltIns: 'entry',
corejs: 3,
exclude: ['transform-typeof-symbol'],
},
},
entry: [entry],
output: {
path: dist,
Expand Down
7 changes: 7 additions & 0 deletions src/commands/build-demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from 'path'

import runSeries from 'run-series'

import {DEFAULT_BROWSERS_PROD} from '../constants'
import {directoryExists} from '../utils'
import webpackBuild from '../webpackBuild'
import cleanDemo from './clean-demo'
Expand All @@ -15,6 +16,12 @@ function getCommandConfig(args) {

let config = {
babel: {
env: {
targets: DEFAULT_BROWSERS_PROD,
useBuiltIns: 'entry',
corejs: 3,
exclude: ['transform-typeof-symbol'],
},
presets: ['react'],
},
devtool: 'source-map',
Expand Down
7 changes: 7 additions & 0 deletions src/commands/serve-react-demo.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'path'

import {DEFAULT_BROWSERS_DEV} from '../constants'
import {directoryExists} from '../utils'
import webpackServer from '../webpackServer'

Expand All @@ -13,6 +14,12 @@ export default function serveReactDemo(args, cb) {

let config = {
babel: {
env: {
targets: DEFAULT_BROWSERS_DEV,
useBuiltIns: 'entry',
corejs: 3,
exclude: ['transform-typeof-symbol'],
},
presets: ['react'],
},
entry: [path.resolve('demo/src/index.js')],
Expand Down
41 changes: 37 additions & 4 deletions src/createBabelConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ type BuildOptions = {
webpack?: boolean,
};

type BrowserOptions = {
development?: string | string[],
production?: string | string[],
}

type UserOptions = {
cherryPick?: string | string[],
config?: (BabelConfig) => BabelConfig,
Expand All @@ -40,11 +45,13 @@ type UserOptions = {
export default function createBabelConfig(
buildConfig: BuildOptions = {},
userConfig: UserOptions = {},
userConfigPath: string = ''
userConfigPath: string = '',
userConfigBrowsers: BrowserOptions = {},
): BabelConfig {
let {
absoluteRuntime,
commonJSInterop,
env: buildEnv = {},
modules = false,
plugins: buildPlugins = [],
presets: buildPresets,
Expand All @@ -57,7 +64,7 @@ export default function createBabelConfig(
let {
cherryPick,
config: userConfigFunction,
env = {},
env: userEnv = {},
loose,
plugins: userPlugins = [],
presets: userPresets,
Expand All @@ -69,15 +76,41 @@ export default function createBabelConfig(
} = userConfig

let presets: BabelPluginConfig[] = []
let plugins: BabelPluginConfig[] = []
let plugins: BabelPluginConfig[] = [
// XXX Webpack can't currently handle untranspiled ?. and ?? syntax
// See https://github.com/webpack/webpack/issues/10227#issuecomment-588409413
require.resolve('@babel/plugin-proposal-optional-chaining'),
require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'),
]

// Default to loose mode unless explicitly configured
if (typeof loose === 'undefined') {
loose = true
}

// Build config controls whether or not we set browser targets. Users can
// override this using `browsers` or `babel.env.targets` config.
let userTargets = {}
if (buildEnv.targets) {
let targets = userConfigBrowsers && (
process.env.NODE_ENV === 'production'
? userConfigBrowsers.production
: userConfigBrowsers.development
)
if (targets) {
userTargets.targets = targets
}
}
presets.push(
[require.resolve('@babel/preset-env'), {loose, modules, ...env}]
[require.resolve('@babel/preset-env'), {
loose,
...buildEnv,
modules,
// Targets config from top-level browsers config if present
...userTargets,
// The user gets a last go at all the env options
...userEnv
}]
)

// Additional build presets
Expand Down
8 changes: 6 additions & 2 deletions src/createWebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ export function createRules(
createRule('babel', {
test: /\.js$/,
loader: require.resolve('babel-loader'),
exclude: /node_modules/,
exclude: /node_modules[\\/](?!react-app-polyfill)/,
options: {
// Don't look for .babelrc files
babelrc: false,
Expand Down Expand Up @@ -591,6 +591,8 @@ export function createPlugins(
}

function createDefaultPostCSSPlugins(userWebpackConfig) {
// Users can override browser versions for Autoprefixer using `browsers` or
// `webpack.autoprefixer.overrideBrowserslist` config.
let overrideBrowserslist = process.env.NODE_ENV === 'production'
? (userWebpackConfig.browsers && userWebpackConfig.browsers.production) ||
DEFAULT_BROWSERS_PROD
Expand Down Expand Up @@ -699,7 +701,9 @@ export default function createWebpackConfig(
}

// Generate config for babel-loader and set it as loader config for the build
buildRulesConfig.babel = {options: createBabelConfig(buildBabelConfig, userConfig.babel, userConfig.path)}
buildRulesConfig.babel = {
options: createBabelConfig(buildBabelConfig, userConfig.babel, userConfig.path, userConfig.browsers)
}

let webpackConfig = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
Expand Down
13 changes: 13 additions & 0 deletions src/quickCommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import runSeries from 'run-series'
import merge from 'webpack-merge'

import cleanApp from './commands/clean-app'
import {DEFAULT_BROWSERS_DEV, DEFAULT_BROWSERS_PROD} from './constants'
import {UserError} from './errors'
import {install} from './utils'
import webpackBuild from './webpackBuild'
Expand Down Expand Up @@ -80,6 +81,12 @@ export function createBuildConfig(args: Object, options: QuickConfigOptions) {

let config: Object = {
babel: {
env: {
targets: DEFAULT_BROWSERS_PROD,
useBuiltIns: 'entry',
corejs: 3,
exclude: ['transform-typeof-symbol'],
},
proposals: {
all: true
}
Expand Down Expand Up @@ -140,6 +147,12 @@ export function createServeConfig(args: Object, options: QuickConfigOptions) {

let config: Object = {
babel: {
env: {
targets: DEFAULT_BROWSERS_DEV,
useBuiltIns: 'entry',
corejs: 3,
exclude: ['transform-typeof-symbol'],
},
proposals: {
all: true
}
Expand Down

0 comments on commit 524a642

Please sign in to comment.