前文中分析了 create react app 中關(guān)于 Webpack 的配置,本文將關(guān)注 Babel 在 create react app 中是如何配置的。
我們?cè)?code>yarn eject 后的項(xiàng)目根目錄的 package.json 文件中可以看到如下節(jié)點(diǎn):
{
...
"babel": {
"presets": [
"react-app"
]
}
}
由此可知,create react app 已經(jīng)將 Babel 有關(guān)的配置打包成預(yù)制件來(lái)使用的,我們可以直接在 node_modules 找到對(duì)應(yīng)的預(yù)制件。如下所示:
看到這些文件, Babel 配置貌似也區(qū)分環(huán)境了,看來(lái)以前還是很傻很天真。
creat.js分析
presets組
對(duì)于 @babel/preset-env
這個(gè)插件預(yù)設(shè),之前的 Babel 配置中已經(jīng)使用過(guò),它區(qū)別于 babel-preset-latest
,不會(huì)進(jìn)行多余的轉(zhuǎn)換,可以根據(jù)配置,按需加載插件。默認(rèn)行為和 babel-preset-latest
相同。而在 create react app 中,對(duì)于 @babel/preset-env
的使用,區(qū)分了測(cè)試與開(kāi)發(fā),產(chǎn)品環(huán)境。
presets: [
isEnvTest && [
// 測(cè)試環(huán)境,根據(jù)目標(biāo)機(jī)器的Node版來(lái)轉(zhuǎn)換
require('@babel/preset-env').default,
{
targets: {
node: 'current',
},
},
],
(isEnvProduction || isEnvDevelopment) && [
// Latest stable ECMAScript features
require('@babel/preset-env').default,
{
// 在入口文件根據(jù)browserlist定義來(lái)選擇polyfills插入
useBuiltIns: 'entry',
// 指定corejs版本消除控制臺(tái)警告
corejs: 3,
// 指定ES6模塊轉(zhuǎn)換類(lèi)型,false為不轉(zhuǎn)換
modules: false,
// 排除@babel/plugin-transform-typeof-symbol插件
exclude: ['transform-typeof-symbol'],
},
],
...
].filter(Boolean),
測(cè)試要求執(zhí)行速度快,避免無(wú)效或無(wú)意義的轉(zhuǎn)換,這里直接指定了機(jī)器 nodejs 版本,保持最新版本就能減少無(wú)效的轉(zhuǎn)換,新版本的 nodejs 已經(jīng)逐漸支持新語(yǔ)法。而開(kāi)發(fā),生成環(huán)境,這里使用的是在入口處添加,我們之前使用的按需添加的方式,似乎那樣更加優(yōu)雅。
presets: [
...
[
// 轉(zhuǎn)換react 預(yù)制
require('@babel/preset-react').default,
{
// 開(kāi)啟利于開(kāi)發(fā)環(huán)境的插件,例如@ babel / plugin-transform-react-jsx-self和@ babel / plugin-transform-react-jsx-source。
development: isEnvDevelopment || isEnvTest,
// 將使用本機(jī)內(nèi)置而不是嘗試對(duì)任何需要的插件進(jìn)行polyfill行為。
useBuiltIns: true,
},
],
// 轉(zhuǎn)換typescript 預(yù)制
isTypeScriptEnabled && [require('@babel/preset-typescript').default],
].filter(Boolean),
@babel/preset-react
預(yù)制件中開(kāi)啟了開(kāi)發(fā)環(huán)境的一些插件支持。
plugins組
由于不考慮追加 flow 的支持,相關(guān)插件功能分析就直接略過(guò)。
babel-plugin-macros
這東西簡(jiǎn)單來(lái)說(shuō)就是 babel 插件的一個(gè)簡(jiǎn)單寫(xiě)法,往常使用某一插件,需要在對(duì)應(yīng)的 babel 配置中追加,如果使用由 Babel宏 實(shí)現(xiàn)的宏,我們只需要配置中增加 babel-plugin-macros 的支持后,就可以任意將宏插件安裝到我們的開(kāi)發(fā)環(huán)境依賴(lài)中,無(wú)需任何其他配置。官網(wǎng)介紹中描述了以下優(yōu)點(diǎn):
- 只需要一個(gè)入口配置 babel-plugin-macros,之后所有項(xiàng)目中使用的 macros 都可以無(wú)配置任意使用。
- 類(lèi)似 create-react-app 之類(lèi)的工具已經(jīng)默認(rèn)支持了,無(wú)需任何其他配置。
- 宏的使用更加明確,你需要明確導(dǎo)入才能使用對(duì)應(yīng)的宏,而插件配置以后你就可以使用,例如類(lèi)似 console.scope 這樣的插件可能會(huì)誤導(dǎo)你認(rèn)為是瀏覽器自帶函數(shù)。
- 宏更安全,更容易編寫(xiě),因?yàn)橹苯咏邮誂ST節(jié)點(diǎn)來(lái)做處理。
- 使用插件你沒(méi)配置的話(huà),那無(wú)法在編譯時(shí)發(fā)現(xiàn)錯(cuò)誤;相反,使用宏,如果你沒(méi)配置 babel-plugin-macros,編譯時(shí)就會(huì)報(bào)錯(cuò)。
@babel/plugin-transform-destructuring
開(kāi)啟數(shù)組及對(duì)象解構(gòu)語(yǔ)法支持。
@babel/plugin-proposal-decorators
開(kāi)啟裝飾器語(yǔ)法支持。該插件必須放在 @babel/plugin-proposal-class-properties'
之前。
@babel/plugin-transform-runtime
避免多次編譯出helper函數(shù)。
babel-plugin-transform-react-remove-prop-types
產(chǎn)品環(huán)境移除 Proptype 定義。
優(yōu)化配置
我們將修改 babel.config.js
以根據(jù) NODE_ENV
變量生成對(duì)應(yīng)環(huán)境的配置。
const getPresets = env => {
const isEnvDevelopment = env === 'development';
const isEnvTest = env === 'test';
return [
[
"@babel/preset-env",
!isEnvTest ?
// 開(kāi)發(fā),產(chǎn)品環(huán)境
{
// 配置如何處理 polyfills
// "usage" | "entry" | false, defaults to false.
// usage 目前是個(gè)實(shí)驗(yàn)性的用法,在具體使用的文件中導(dǎo)入具體被使用的polyfills
useBuiltIns: "usage",
// 指定corejs版本為2.x
corejs: 2,
// 指定ES6模塊轉(zhuǎn)換類(lèi)型,false為不轉(zhuǎn)換
modules: false,
// 排除@babel/plugin-transform-typeof-symbol插件
exclude: ['transform-typeof-symbol'],
} :
// 測(cè)試環(huán)境
{
targets: {
node: 'current',
},
},
],
[
"@babel/preset-react",
{
// 開(kāi)啟利于開(kāi)發(fā)環(huán)境的插件,例如@babel/plugin-transform-react-jsx-self和@babel/plugin-transform-react-jsx-source。
development: isEnvDevelopment || isEnvTest,
// 將使用本機(jī)內(nèi)置而不是嘗試對(duì)任何需要的插件進(jìn)行polyfill行為.
useBuiltIns: true,
},
],
["@babel/preset-typescript"]
];
}
const getPlugins = () => {
return [
// 開(kāi)啟實(shí)驗(yàn)性的babel宏插件,宏插件使用是免配置,對(duì)應(yīng)包需要安裝到開(kāi)發(fā)環(huán)境依賴(lài)中
['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執(zhí)行緩存,如果NODE_ENV發(fā)生變化,則重新獲取配置更新緩存
api.cache.using(() => process.env.NODE_ENV);
return {
presets: getPresets(process.env.NODE_ENV),
plugins: getPlugins()
};
};
增加了對(duì) Babel 宏及裝飾器語(yǔ)法支持,而對(duì)于 polyfill 的處理方式無(wú)變化。這樣再次編譯時(shí),Babel 會(huì)根據(jù)不同環(huán)境適用對(duì)應(yīng)的插件或加載不同配置。
這里我們?cè)囋囎钚绿砑拥?Babel 宏,選擇安裝 reactive.macro
包。更多宏包請(qǐng)查看 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;
無(wú)任何多余配置完美執(zhí)行。