Skip to content

adding options for esmodules and commonjs build targets (#5216) #6505

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions packages/babel-preset-react-app/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,47 @@ Make sure you have a `tsconfig.json` file at the root directory. You can also us
"presets": [["react-app", { "flow": false, "typescript": true }]]
}
```

## Usage within NPM packages

If you are creating an NPM package that contains a React component you can use the options for `commonjs` and `esmodules` to create proper builds for `lib`, `es` and `dist` folders. The configuration example below will work for most common cases but will not be suitable to all projects. Similar setups are used by popular NPM packages such as [react-redux](https://github.com/reduxjs/react-redux) and [react-router](https://github.com/ReactTraining/react-router/tree/master/packages/react-router).

### `babel.config.js`

When building for `lib`, `es` folders you want to set the `absoluteRuntime` to false. When building for the `dist` folder, you also want to disable helpers (because Rollup manages helpers automatically).

Note that it is recommended to set `NODE_ENV` environment variable to "production" when building an NPM package. Setting `NODE_ENV` to "development" will put the `@babel/preset-react` plugin into development mode, which is undesirable for a published NPM package.

```js
const { NODE_ENV, MODULES_ENV } = process.env;

const isEnvTest = NODE_ENV === 'test';
if (!isEnvTest) {
// force production mode for package builds
process.env.NODE_ENV = 'production';
}

const useCommonJS = isEnvTest || MODULES_ENV === 'commonjs';
const useESModules = MODULES_ENV === 'esmodules';

module.exports = {
presets: [
// for testing with jest/jsdom
useCommonJS && isEnvTest && 'babel-preset-react-app/test',
// building for lib folder
useCommonJS &&
!isEnvTest && [
'babel-preset-react-app/commonjs',
{ absoluteRuntime: false },
],
// building for es folder
useESModules && [
'babel-preset-react-app/esmodules',
{ absoluteRuntime: false },
],
// building for dist folder
!useCommonJS &&
!useESModules && ['babel-preset-react-app', { helpers: false }],
].filter(Boolean),
};
```
24 changes: 24 additions & 0 deletions packages/babel-preset-react-app/commonjs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';

const create = require('./create');

module.exports = function(api, opts) {
// This is similar to how `env` works in Babel:
// https://babeljs.io/docs/usage/babelrc/#env-option
// We are not using `env` because it’s ignored in versions > [email protected]:
// https://github.com/babel/babel/issues/4539
// https://github.com/facebook/create-react-app/issues/720
// It’s also nice that we can enforce `NODE_ENV` being specified.
const env = process.env.BABEL_ENV || process.env.NODE_ENV;
return create(
api,
Object.assign({ helpers: false }, opts, { useCommonJS: true }),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this default absolutRuntime to false?

env
);
};
55 changes: 39 additions & 16 deletions packages/babel-preset-react-app/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ module.exports = function(api, opts, env) {
var isEnvProduction = env === 'production';
var isEnvTest = env === 'test';

var useCommonJS = validateBoolOption(
'useCommonJS',
opts.useCommonJS,
isEnvTest
);
var useESModules = validateBoolOption(
'useESModules',
opts.useESModules,
isEnvDevelopment || isEnvProduction
false
);
var isFlowEnabled = validateBoolOption('flow', opts.flow, true);
var isTypeScriptEnabled = validateBoolOption(
Expand All @@ -54,6 +59,24 @@ module.exports = function(api, opts, env) {
);
}

// When building for commonjs or esmodules environments we need to choose
// different targets.
let targets;
if (useCommonJS) {
// For commonjs we target the oldest supported version of node.
// For tests we target the installed version of node.
// https://babeljs.io/docs/en/babel-preset-env#targetsnode
targets = { node: isEnvTest ? 'current' : 6 };
} else if (useESModules) {
// For esmodules we use the special esmodules target.
// https://babeljs.io/docs/en/babel-preset-env#targetsesmodules
targets = { esmodules: true };
} else {
// We want Create React App to be IE 9 compatible until React itself
// no longer works with IE 9
targets = { ie: 9 };
}

if (!isEnvDevelopment && !isEnvProduction && !isEnvTest) {
throw new Error(
'Using `babel-preset-react-app` requires that you specify `NODE_ENV` or ' +
Expand All @@ -63,35 +86,34 @@ module.exports = function(api, opts, env) {
'.'
);
}
if (useESModules && useCommonJS) {
throw new Error(
'`babel-preset-react-app` does not support setting both useESModules and useCommonJS to true.'
);
}

return {
presets: [
isEnvTest && [
// ES features necessary for user's Node version
require('@babel/preset-env').default,
{
targets: {
node: 'current',
},
targets,
},
],
(isEnvProduction || isEnvDevelopment) && [
// Latest stable ECMAScript features
require('@babel/preset-env').default,
{
// We want Create React App to be IE 9 compatible until React itself
// no longer works with IE 9
targets: {
ie: 9,
},
targets,
// Users cannot override this behavior because this Babel
// configuration is highly tuned for ES5 support
ignoreBrowserslistConfig: true,
// If users import all core-js they're probably not concerned with
// bundle size. We shouldn't rely on magic to try and shrink it.
useBuiltIns: false,
// Do not transform modules to CJS
modules: false,
// Do not transform modules to CJS (unless we're targeting commonJS)
modules: useCommonJS ? 'cjs' : false,
// Exclude transforms that make all code slower
exclude: ['transform-typeof-symbol'],
},
Expand Down Expand Up @@ -157,11 +179,12 @@ module.exports = function(api, opts, env) {
{
corejs: false,
helpers: areHelpersEnabled,
regenerator: true,
// We only need to use regenerator in environments that don't support generators.
regenerator: !(useESModules || useCommonJS),
// https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules
// We should turn this on once the lowest version of Node LTS
// supports ES Modules.
useESModules,
// We want to enable this for all builds except commonjs. This allows for smaller
// builds since it doesn't need to preserve commonjs semantics.
useESModules: !useCommonJS,
// Undocumented option that lets us encapsulate our runtime, ensuring
// the correct version is used
// https://github.com/babel/babel/blob/090c364a90fe73d36a30707fc612ce037bdbbb24/packages/babel-plugin-transform-runtime/src/index.js#L35-L42
Expand All @@ -177,7 +200,7 @@ module.exports = function(api, opts, env) {
],
// Adds syntax support for import()
require('@babel/plugin-syntax-dynamic-import').default,
isEnvTest &&
useCommonJS &&
// Transform dynamic import to require
require('babel-plugin-dynamic-import-node'),
].filter(Boolean),
Expand Down
58 changes: 43 additions & 15 deletions packages/babel-preset-react-app/dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ module.exports = function(api, opts) {
var isEnvProduction = env === 'production';
var isEnvTest = env === 'test';

var useCommonJS = validateBoolOption(
'useCommonJS',
opts.useCommonJS,
isEnvTest
);
var useESModules = validateBoolOption(
'useESModules',
opts.useESModules,
false
);
var areHelpersEnabled = validateBoolOption('helpers', opts.helpers, false);
var useAbsoluteRuntime = validateBoolOption(
'absoluteRuntime',
Expand All @@ -50,6 +60,24 @@ module.exports = function(api, opts) {
);
}

// When building for commonjs or esmodules environments we need to choose
// different targets.
let targets;
if (useCommonJS) {
// For commonjs we target the oldest supported version of node.
// For tests we target the installed version of node.
// https://babeljs.io/docs/en/babel-preset-env#targetsnode
targets = { node: isEnvTest ? 'current' : 6 };
} else if (useESModules) {
// For esmodules we use the special esmodules target.
// https://babeljs.io/docs/en/babel-preset-env#targetsesmodules
targets = { esmodules: true };
} else {
// We want Create React App to be IE 9 compatible until React itself
// no longer works with IE 9
targets = { ie: 9 };
}

if (!isEnvDevelopment && !isEnvProduction && !isEnvTest) {
throw new Error(
'Using `babel-preset-react-app` requires that you specify `NODE_ENV` or ' +
Expand All @@ -59,6 +87,11 @@ module.exports = function(api, opts) {
'.'
);
}
if (useESModules && useCommonJS) {
throw new Error(
'`babel-preset-react-app` does not support setting both useESModules and useCommonJS to true.'
);
}

return {
// Babel assumes ES Modules, which isn't safe until CommonJS
Expand All @@ -71,9 +104,7 @@ module.exports = function(api, opts) {
// ES features necessary for user's Node version
require('@babel/preset-env').default,
{
targets: {
node: 'current',
},
targets,
// Do not transform modules to CJS
modules: false,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be true for tests?

// Exclude transforms that make all code slower
Expand All @@ -84,19 +115,15 @@ module.exports = function(api, opts) {
// Latest stable ECMAScript features
require('@babel/preset-env').default,
{
// We want Create React App to be IE 9 compatible until React itself
// no longer works with IE 9
targets: {
ie: 9,
},
targets,
// Users cannot override this behavior because this Babel
// configuration is highly tuned for ES5 support
ignoreBrowserslistConfig: true,
// If users import all core-js they're probably not concerned with
// bundle size. We shouldn't rely on magic to try and shrink it.
useBuiltIns: false,
// Do not transform modules to CJS
modules: false,
// Do not transform modules to CJS (unless we're targeting commonJS)
modules: useCommonJS ? 'cjs' : false,
// Exclude transforms that make all code slower
exclude: ['transform-typeof-symbol'],
},
Expand All @@ -110,11 +137,12 @@ module.exports = function(api, opts) {
{
corejs: false,
helpers: areHelpersEnabled,
regenerator: true,
// We only need to use regenerator in environments that don't support generators.
regenerator: !(useESModules || useCommonJS),
// https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules
// We should turn this on once the lowest version of Node LTS
// supports ES Modules.
useESModules: isEnvDevelopment || isEnvProduction,
// We want to enable this for all builds except commonjs. This allows for smaller
// builds since it doesn't need to preserve commonjs semantics.
useESModules: !useCommonJS,
// Undocumented option that lets us encapsulate our runtime, ensuring
// the correct version is used
// https://github.com/babel/babel/blob/090c364a90fe73d36a30707fc612ce037bdbbb24/packages/babel-plugin-transform-runtime/src/index.js#L35-L42
Expand All @@ -123,7 +151,7 @@ module.exports = function(api, opts) {
],
// Adds syntax support for import()
require('@babel/plugin-syntax-dynamic-import').default,
isEnvTest &&
useCommonJS &&
// Transform dynamic import to require
require('babel-plugin-transform-dynamic-import').default,
].filter(Boolean),
Expand Down
24 changes: 24 additions & 0 deletions packages/babel-preset-react-app/esmodules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';

const create = require('./create');

module.exports = function(api, opts) {
// This is similar to how `env` works in Babel:
// https://babeljs.io/docs/usage/babelrc/#env-option
// We are not using `env` because it’s ignored in versions > [email protected]:
// https://github.com/babel/babel/issues/4539
// https://github.com/facebook/create-react-app/issues/720
// It’s also nice that we can enforce `NODE_ENV` being specified.
const env = process.env.BABEL_ENV || process.env.NODE_ENV;
return create(
api,
Object.assign({ helpers: false }, opts, { useESModules: true }),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this default absolutRuntime to false?

env
);
};
3 changes: 3 additions & 0 deletions packages/babel-preset-react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
"url": "https://github.com/facebook/create-react-app/issues"
},
"files": [
"commonjs.js",
"create.js",
"dependencies.js",
"dev.js",
"esmodules.js",
"index.js",
"webpack-overrides.js",
"prod.js",
Expand All @@ -35,6 +37,7 @@
"babel-loader": "8.0.5",
"babel-plugin-dynamic-import-node": "2.2.0",
"babel-plugin-macros": "2.5.0",
"babel-plugin-transform-dynamic-import": "2.1.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This plugin was missing 🤷

"babel-plugin-transform-react-remove-prop-types": "0.4.24"
}
}