前文中分析了 create react app 中关于 Webpack 的配置,本文将关注 Babel 在 create react app 中是如何配置的。
我们在yarn eject
后的项目根目录的 package.json 文件中可以看到如下节点:
{
...
"babel": {
"presets": [
"react-app"
]
}
}
由此可知,create react app 已经将 Babel 有关的配置打包成预制件来使用的,我们可以直接在 node_modules 找到对应的预制件。如下所示:
看到这些文件, Babel 配置貌似也区分环境了,看来以前还是很傻很天真。
creat.js分析
presets组
对于 @babel/preset-env
这个插件预设,之前的 Babel 配置中已经使用过,它区别于 babel-preset-latest
,不会进行多余的转换,可以根据配置,按需加载插件。默认行为和 babel-preset-latest
相同。而在 create react app 中,对于 @babel/preset-env
的使用,区分了测试与开发,产品环境。
presets: [
isEnvTest && [
// 测试环境,根据目标机器的Node版来转换
require('@babel/preset-env').default,
{
targets: {
node: 'current',
},
},
],
(isEnvProduction || isEnvDevelopment) && [
// Latest stable ECMAScript features
require('@babel/preset-env').default,
{
// 在入口文件根据browserlist定义来选择polyfills插入
useBuiltIns: 'entry',
// 指定corejs版本消除控制台警告
corejs: 3,
// 指定ES6模块转换类型,false为不转换
modules: false,
// 排除@babel/plugin-transform-typeof-symbol插件
exclude: ['transform-typeof-symbol'],
},
],
...
].filter(Boolean),
测试要求执行速度快,避免无效或无意义的转换,这里直接指定了机器 nodejs 版本,保持最新版本就能减少无效的转换,新版本的 nodejs 已经逐渐支持新语法。而开发,生成环境,这里使用的是在入口处添加,我们之前使用的按需添加的方式,似乎那样更加优雅。
presets: [
...
[
// 转换react 预制
require('@babel/preset-react').default,
{
// 开启利于开发环境的插件,例如@ babel / plugin-transform-react-jsx-self和@ babel / plugin-transform-react-jsx-source。
development: isEnvDevelopment || isEnvTest,
// 将使用本机内置而不是尝试对任何需要的插件进行polyfill行为。
useBuiltIns: true,
},
],
// 转换typescript 预制
isTypeScriptEnabled && [require('@babel/preset-typescript').default],
].filter(Boolean),
@babel/preset-react
预制件中开启了开发环境的一些插件支持。
plugins组
由于不考虑追加 flow 的支持,相关插件功能分析就直接略过。
babel-plugin-macros
这东西简单来说就是 babel 插件的一个简单写法,往常使用某一插件,需要在对应的 babel 配置中追加,如果使用由 Babel宏 实现的宏,我们只需要配置中增加 babel-plugin-macros 的支持后,就可以任意将宏插件安装到我们的开发环境依赖中,无需任何其他配置。官网介绍中描述了以下优点:
- 只需要一个入口配置 babel-plugin-macros,之后所有项目中使用的 macros 都可以无配置任意使用。
- 类似 create-react-app 之类的工具已经默认支持了,无需任何其他配置。
- 宏的使用更加明确,你需要明确导入才能使用对应的宏,而插件配置以后你就可以使用,例如类似 console.scope 这样的插件可能会误导你认为是浏览器自带函数。
- 宏更安全,更容易编写,因为直接接收AST节点来做处理。
- 使用插件你没配置的话,那无法在编译时发现错误;相反,使用宏,如果你没配置 babel-plugin-macros,编译时就会报错。
@babel/plugin-transform-destructuring
开启数组及对象解构语法支持。
@babel/plugin-proposal-decorators
开启装饰器语法支持。该插件必须放在 @babel/plugin-proposal-class-properties'
之前。
@babel/plugin-transform-runtime
避免多次编译出helper函数。
babel-plugin-transform-react-remove-prop-types
产品环境移除 Proptype 定义。
优化配置
我们将修改 babel.config.js
以根据 NODE_ENV
变量生成对应环境的配置。
const getPresets = env => {
const isEnvDevelopment = env === 'development';
const isEnvTest = env === 'test';
return [
[
"@babel/preset-env",
!isEnvTest ?
// 开发,产品环境
{
// 配置如何处理 polyfills
// "usage" | "entry" | false, defaults to false.
// usage 目前是个实验性的用法,在具体使用的文件中导入具体被使用的polyfills
useBuiltIns: "usage",
// 指定corejs版本为2.x
corejs: 2,
// 指定ES6模块转换类型,false为不转换
modules: false,
// 排除@babel/plugin-transform-typeof-symbol插件
exclude: ['transform-typeof-symbol'],
} :
// 测试环境
{
targets: {
node: 'current',
},
},
],
[
"@babel/preset-react",
{
// 开启利于开发环境的插件,例如@babel/plugin-transform-react-jsx-self和@babel/plugin-transform-react-jsx-source。
development: isEnvDevelopment || isEnvTest,
// 将使用本机内置而不是尝试对任何需要的插件进行polyfill行为.
useBuiltIns: true,
},
],
["@babel/preset-typescript"]
];
}
const getPlugins = () => {
return [
// 开启实验性的babel宏插件,宏插件使用是免配置,对应包需要安装到开发环境依赖中
['babel-plugin-macros'],
// 支持装饰器
['@babel/plugin-proposal-decorators', false],
["@babel/plugin-syntax-dynamic-import"],
["@babel/plugin-proposal-class-properties", { loose: true }],
];
};
module.exports = function (api) {
// 基于NODE_ENV执行缓存,如果NODE_ENV发生变化,则重新获取配置更新缓存
api.cache.using(() => process.env.NODE_ENV);
return {
presets: getPresets(process.env.NODE_ENV),
plugins: getPlugins()
};
};
增加了对 Babel 宏及装饰器语法支持,而对于 polyfill 的处理方式无变化。这样再次编译时,Babel 会根据不同环境适用对应的插件或加载不同配置。
这里我们试试最新添加的 Babel 宏,选择安装 reactive.macro
包。更多宏包请查看 Awesome babel macros。
About.tsx
import * as React from "react";
import { state, bind } from "reactive.macro";
import styled from "styled-components";
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
const About: React.SFC = () => {
const a = state(1);
const b = state(2);
return (
<Title>
<Wrapper>About</Wrapper>
<div>
<input type="number" value={bind(a)} />
<button onClick={b => b += 1}>b+</button>
<p>
{a} + {b} = {a + b}
</p>
</div>
</Title>
);
};
export default About;
无任何多余配置完美执行。