12-腳手架create react app源碼分析(2)

前文中分析了 create react app 中關(guān)于 Webpack 的配置,本文將關(guān)注 Babelcreate 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í)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。