Skip to content
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

.babelrc与babel.config.js配置文件的区别 #100

Open
willson-wang opened this issue Sep 7, 2021 · 0 comments
Open

.babelrc与babel.config.js配置文件的区别 #100

willson-wang opened this issue Sep 7, 2021 · 0 comments

Comments

@willson-wang
Copy link
Owner

目录

背景

有一个webpack单页项目,然后根据之前对babel的了解,在根目录下建了一个.babelrc文件,然后在babel-loader通过exclues排除node_modules目录,且配置开启babel-loader缓存相关的配置参数,其它babel的presets与plugin则通过根目录下的.babelrc文件来进行配置,如下所示

/* config.module.rule('script') */
{
  test: /\.[tj]sx?$/i,
  exclude: [
  /(node_modules|bower_components)/
  ],
  use: [
    /* config.module.rule('script').use('1') */
    {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true
      }
    }
  ]
},
// 项目根路径下.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "debug": false,
        "useBuiltIns": false, 
      },
    ],
    ["@babel/preset-react"],
    ["@babel/preset-typescript"],
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": false,
        "helpers": true,
        "regenerator": true,
      },
    ],
  ],
}

最开始babel能够有效的对文件做处理,然后突然有一个需求,添加了公司内部的一个npm包,然后这个npm包只输出了ts文件,所以需要将node_modules下的这个包通过babel-loader处理,根据之前的经验,只需要在excludes那里通过增则排除下需要处理的npm包即可,如下所示

/* config.module.rule('script') */
{
  test: /\.[tj]sx?$/i,
-  exclude: [
-  /(node_modules|bower_components)/
-  ],
+	 exclude: [
+   	/(node_modules\/(?!(inner-package)\/)|bower_components)/
+   ],
  use: [
    /* config.module.rule('script').use('1') */
    {
      loader: 'babel-loader',
      options: {
        cacheDirectory: true
      }
    }
  ]
},

实际运行的结果就是babel-loader对node_modules下的文件做处理了,但是babel相关的插件没有对npm包内的文件生效

由于时间紧迫只给了一个,临时方案就是是在babel-loader那里对这个npm包单独处理,传入presets与plugin,如下所示

chain.module
      .rule('script_node_modules')
      .test(/\.[tj]sx?$/i)
      .include
      .add(path.join(__dirname, '../node_modules/inner-package'))
      .end()
      .use('babel')
      .loader('babel-loader')
      .options({
        babelrc: false,
        presets: [
          ['@babel/preset-env'],
          [
            "@babel/preset-react"
          ],
          [
            "@babel/preset-typescript"
          ]
        ],
        "plugins": [
          [
            "@babel/plugin-transform-runtime",
            {
              "corejs": false,
              "helpers": true,
              "regenerator": true
            }
          ]
        ]
      })

虽然这次问题临时解决了,但是有一个问题困扰着我,babel不是会一直遍历上级目录,一直找到有babel配置的目录为止吗?那么这次为什么没有生效?

两类配置文件

于是带着疑问又重新去看了babel的官方文档,有关配置的内容在这里Config Files

配置文件类型:

babel提供了两种配置文件类型,可以二者结合使用,也可以只用单独的一种

  • 项目级配置文件
    • babel.config.json files, with the different extensions (.js, .cjs, .mjs)
  • 相对于文件路径的配置文件
    • .babelrc.json files, with the different extensions (.babelrc, .js, .cjs, .mjs)
    • package.json files with a "babel" key

项目级配置文件

  • babel7中提供的一个新概念
  • babel提供了一个root根目录的概念
  • babel会尝试在根目录下找babel.config.js及其它扩展名文件
  • 用户可以通过configFile来指定项目级配置文件,通过设置false来禁用项目级配置文件

优点

  • 配置文件与需要处理的实践文件是分离的,所以使用范围非常广
  • 通过项目级配置文件可以轻松处理node_modules下或者软链的文件

缺点

  • 它依赖于工作目录,如果工作目录不是 monorepo 根目录,在 monorepos 中使用会更痛苦。

相对于文件路径配置

  • babel会从正在编译的filename所在目录,开始搜索目录中的.babelrc及其它相关拓展名的配置文件
  • 可以针对某个或者某些文件来配置单独的babel配置,但是这个配置会与项目级的配置文件合并
  • 搜索目录范围是从filename所在目录一直到最近一个含有package.json文件的上级目录
  • filename的路径必须要在babelrcRoots内才会进行babelrc配置文件搜索

所以到这里我们就知道我们最开始的问题在哪里了,在我们项目根目录使用的是相对于文件路径配置而不是全局配置,所以src目录下的文件都能够读取到根目录下的.babelrc配置,但是node_modules下的项目就无法读取到根目录下的.babelrc配置文件了

配置项详解

  • babelrc

    参数类型:Boolean 默认值: true

    当该配置选项为 true 的时候,允许加载 babelrc 的配置文件,配合 babelrcRoots 可以加载子包的 babel 配置,当为 false 的时候就完全禁止加载 babelrc 配置,整个项目只会有 babel.config.js 全局配置

  • babelrcRoots

    参数类型:boolean | MatchPattern | MatchPattern[] 默认参数 opts.root babel 默认只会使用项目根节点的.babelrc 配置文件,如果需要使用子包的.babelrc 可以配置该参数允许加载子包的

  • root

    参数类型:String 默认值:process.cwd();

    用来确定当前 babel 执行环境的的根目录位置

  • rootMode

    参数类型:"root" | "upward" | "upward-optional" 默认: "root" 这个配置选项,和 root 配置选项是关联的,定义了 babel 如何选择项目的根目录,通过传入不同参数可以选择 babel 不同的处理 root 值,以获得最终项目的根目录

    • "root" 传递原 root 值不变

    • "upward" 让 babel 从 root 的上级目录查找 babel.config.js 全局配置,如果没有会报错

    • "upward-optional" 让 babel 从 root 的上级目录查找 babel.config.js ,如果没有找到就回退到当前目录作为 babel 的根目录

      当 babel 构建的项目是 monorepo 结构的项目,需要基于每个子包运行构建测试的时候,babel 的执行环境在子包的环境,无法加载babel.config.js 全局配置,这个时候可以加入 roomMode: 'upward'参数,让 babel 从 root 上一级找全局配置合并当前 babelrc 的配置来构建当前项目目录的内容

  • plugins

    参数类型: String[] | [String, Options][] 默认值:[]

    该选项配置包含了,当 babel 启动的时候要激活的 babel 插件数组

  • presets

    参数类型: String[] | [String, Options][] 默认值:[]

    预设本质就是插件的集合,该配置表示 babel 处理文件要激活的预设插件数组.

  • extends

    参数类型:String

    该选项参数不能放置在 presets 选项配置上. 该选项允许继承其他配置文件的配置,extends 选项在当前配置文件配置范围将会合并继承 extends 指向的配置文件的相同配置范围的配置内容,当前配置文件覆盖在继承文件配置之上

  • overrides

    参数类型:Options[]

    该选项参数不允许放置在嵌套的 overrides 配置和 env 配置里. 该选项参数允许用户提供一个覆盖当前配置文件的配置内容的配置数组.使用实例

    module.exports = {
      overrides: [
        {
          exclude: ['./lib/*', './utils/*'],
          test: /^.+\.js$/,
          compact: true,
        },
      ],
    };

    此功能经常配置 "test", "include", "exclude" 选项一起使用,可以提供 babel 覆盖当前全局配置的条件

  • test

    参数类型:MatchPattern | Array<MatchPattern>

    该选项配置是一个匹配规则或者规则列表,如果 babel 当前编译的文件和配置规则都不符合,则 babel 编译的时候会将该文件忽略,该选项经常配合 overrides 一起使用,让 babel 做配置文件条件输出,而且该选项可以放在配置文件的任何位置

  • include

    该选项配置是"test"选项的的别名

  • exclude

    参数类型:MatchPattern | Array<MatchPattern>

    与 include 配置相反,该配置表示的是当任意一个的匹配规则列表符合匹配当前编译文件的,babel 都会忽略该文件,该选项经常配合 overrides 一起使用,让 babel 做配置文件条件输出,而且该选项还可以放在配置文件的任何位置

    注意:test/include/exclude 配置文件会在 babel 准备合并配置之前就预先考虑到 test 选项,所以在 babel 切换不同的配置文件加载选项的时候,该选项已经被提前设置好了

  • ignore

    参数类型:Array<MatchPattern>该选项参数不允许放置在"presets"配置内 功能与"exclude"类似,当 babel 编译的时候匹配到任意符合匹配规则列表的内容时,会立即中止当前所有的 babel 处理。完全禁用 babel 的其他处理

  • sourceMaps / sourceMap

    参数类型:boolean | "inline" | "both" 默认值:false

    babel 创建 sourceMap, + true 为编译文件创建一个 sourceMap + inline 将 map 作为 data:url 直接内嵌到文件里 + both 既创建 map 也内嵌 注意:该配置选项我自己实验的时候 只能在命令行中使用,在配置文件里配置无效,而@babel/cli 会将 map 写入到 .map后缀的格式文件内

  • sourceType

    参数类型:"script" | "module" | "unambiguous" 默认值:"module"

    该选项配置参数主要引导 babel 的文件解析是否转换 import 或者 require,babel 默认处理模块是 es 模块,默认使用 import

    • script 使用 ECMAScript Script 语法解析文件。不允许 import/ export 语句,文件不是严格模式。
    • module 使用 ECMAScript Module 语法解析文件。并且允许 import/ export 语句,文件是严格模式。
    • unambiguous 如果当前文件有 import/export 则视为"module", 否则将视为 "script" unambiguous 在类型未知的上下文中非常有用,但它可能导致错误匹配,因为 module 文件可能并没有使用 import/ export 语法。
  • compact

    参数类型:Boolean | "auto" 默认值:"auto"

    该配置选项引导 babel 是否开启紧凑模式,紧凑模式会省略所有可选的换行符和空格. 当配置选项是”auto“的时候,babel 会根据文件的字符数判断,当字符长度 code.length > 50,000 时 会开启紧凑模式

  • minified

    参数类型:Boolean 默认值:false

    该配置选项为 ture 的时候, 相当于 compact:true 基础上,还省略了块级作用域末尾的分号,以及其他很多地方省略,babel 尽可能的压缩代码,比 compact 更短的输出

配置文件读取规则

我们从各个插件中是进一步看,babel配置的文件的读取

babel-loader原理(8.2.2)

  • 通过@babel/core内提供的loadPartialConfigAsync || loadPartialConfig方法查找babel配置文件
  • 调用babel.transform(code, options)方法,options是上一步找到babel配置文件
  • 返回code与map继续后续工作,babel的工作就做完了

@rollup/plugin-babel原理(5.3.0)

  1. babel先于rollup处理代码,这种方式会传filename参数

    • 通过@babel/core内提供的loadPartialConfigAsync || loadPartialConfig方法查找babel配置文件

    • 调用babel.transformAsync(code, options)方法,,options是上一步找到babel配置文件

    • 返回code与map继续后续工作,babel的工作就做完了

  2. babel后于rollup处理代码,这种方式不会传filename参数

    • 通过@babel/core内提供的loadPartialConfigAsync || loadPartialConfig方法查找babel配置文件

    • 调用babel.transformAsync(code, options)方法,,options是上一步找到babel配置文件

    • 返回code与map继续后续工作,babel的工作就做完了

我们从上面看到都是调用了babel/core中提供的loadPartialConfigAsync || loadPartialConfig这两个方法来读取项目中的babel配置,而这两个方法在内部都调用的是loadPrivatePartialConfig这个方法,从源码(7.15.0)中一起看下具体的实现

以下源码分析的过程中省略了部分代码,只保留关键实现

读取配置项入口

function* loadPrivatePartialConfig(inputOpts) {

  const args = inputOpts ? (0, _options.validate)("arguments", inputOpts) : {};
  const {
    cwd = ".",
    root: rootDir = ".",
    rootMode = "root",
  } = args;

  // cwd
  const absoluteCwd = _path().resolve(cwd);
	
  // 关键是这一步,这一步会通过absoluteCwd、root、rootMode这三个参数来决定babel的执行根目录root是哪个目录
  const absoluteRootDir = resolveRootMode(_path().resolve(absoluteCwd, rootDir), rootMode);
  ...
}

cwd默认值为 process.cwd()
rootDir(root参数的别名)默认值为 opts.cwd

所以当我们cwd与root都没有传值的时候,传入不同的rootMode参数有下面三种结果

  1. rootMode='root' => 最终的context.root=process.cwd路径
  2. rootMode='upward' => 最终的context.root=最先找到babel.config.[js]配置的目录,然后当成项目根目录,如果一直没找到会报错
  3. rootMode='upward-optional' => 最终的context.root=最先找到babel.config.[js]配置的目录,然后当成项目根目录,如果没找到又会回到rootDir
function resolveRootMode(rootDir, rootMode) {
  switch (rootMode) {
    case "root":
      return rootDir;

    case "upward-optional":
      {
        const upwardRootDir = (0, _files.findConfigUpwards)(rootDir);
        return upwardRootDir === null ? rootDir : upwardRootDir;
      }

    case "upward":
      {
        const upwardRootDir = (0, _files.findConfigUpwards)(rootDir);
        if (upwardRootDir !== null) return upwardRootDir;
        throw Object.assign(new Error(`Babel ...`);
      }
  }
}
function findConfigUpwards(rootDir) {
  let dirname = rootDir;

  for (;;) {
    for (const filename of ROOT_CONFIG_FILENAMES) {
      // 在当前目录查找babel.config.[js|json|mjs|cjs]
      if (_fs().existsSync(_path().join(dirname, filename))) {
        return dirname;
      }
    }

    // 当前目录没找到,读取上一级目录,会一直遍历到能够找到babel.config.[js]的上级目录为止,然后返回目录
    const nextDir = _path().dirname(dirname);

    if (dirname === nextDir) break;
    dirname = nextDir;
  }

  return null;
}

再接着看loadPrivatePartialConfig方法后面的实现

function* loadPrivatePartialConfig(inputOpts) {
	...
  const filename = typeof args.filename === "string" ? _path().resolve(cwd, args.filename) : undefined;
  // 创建一个上下文contex,这个上下文有cwd、root这几个参数
  const context = {
    filename,
    cwd: absoluteCwd,
    root: absoluteRootDir,
  };
  // 然后将上下文传入到buildRootChain方法内
  const configChain = yield* (0, _configChain.buildRootChain)(args, context);
}

来看buildRootChain的内部实现

function* buildRootChain(opts, context) {
  let configFile;
	
  if (typeof opts.configFile === "string") {
    // 根据传入的configFile参数读取项目级配置文件
    configFile = yield* (0, _files.loadConfig)(opts.configFile, context.cwd, context.envName, context.caller);
  } else if (opts.configFile !== false) {
    // 如果不为false则从当前root目录内找项目级配置文件
    configFile = yield* (0, _files.findRootConfig)(context.root, context.envName, context.caller);
  }
  ...
}

项目级配置文件的名称

const ROOT_CONFIG_FILENAMES = ["babel.config.js", "babel.config.cjs", "babel.config.mjs", "babel.config.json"];
exports.ROOT_CONFIG_FILENAMES = ROOT_CONFIG_FILENAMES;

看到这里我们就知道项目配置文件读取步骤

  1. configFile是不是字符串,如果是,则直接读取configFile指定的babel配置文件
  2. 如果configFile是false,则不在项目内查找babel.config.[js|json|cjs|mjs]配置文件
  3. 如果configFile不等于false,则在根据context.root字段来查找项目内的babel.config.[js|json|cjs|mjs]配置文件,如果有重复的项目级配置文件会报错

所以这里会有三个参数configFile、root、rootMode影响到项目级配置文件也就是babel.config.js配置文件的查找,小结如下

  1. configFile只有是不为false的时候,才会读取babel.config.[js]
  2. 在没有直接指定configFile的路径时,自动查找context.root目录下的babel.config.[js]

继续往下看buildRootChain方法

function* buildRootChain(opts, context) {
  // 从opts中获取传入的babelrc与babelrcRoot配置项
  let {
    babelrc,
    babelrcRoots
  } = opts;
  let babelrcRootsDirectory = context.cwd;


  let ignoreFile, babelrcFile;
  let isIgnored = false;
  const fileChain = emptyChain();

  // 判断是否允许加载babelrc配置文件,只有babelrc这个配置项不为false及传入了filename才会去找babelrc配置文件
  if ((babelrc === true || babelrc === undefined) && typeof context.filename === "string") {
    // 通过findPackageData方法找到当前文件最近的package.json文件,然后拿到package.json数据
    const pkgData = yield* (0, _files.findPackageData)(context.filename);

    // 如果有package.json且允许在package.json的目录内查找.babelrc文件,才会进入查找逻辑
    if (pkgData && babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory)) {

      // 查找离filename最近的.babelrc文件
      ({
        ignore: ignoreFile,
        config: babelrcFile
      } = yield* (0, _files.findRelativeConfig)(pkgData, context.envName, context.caller));


      if (babelrcFile && !isIgnored) {
        const validatedFile = validateBabelrcFile(babelrcFile);
        const result = yield* loadFileChain(validatedFile, context, undefined, babelrcLogger);

        if (!result) {
          isIgnored = true;
        } else {
          mergeChain(fileChain, result);
        }
      }
    }
  }
}

根据filename向上寻找package.json文件所在目录的逻辑

function* findPackageData(filepath) {
  let pkg = null;
  const directories = [];
  let isPackage = true;

  let dirname = _path().dirname(filepath);

	// 从filename所在目录开始向上找,一直找到有package.json的文件目录为止
  while (!pkg && _path().basename(dirname) !== "node_modules") {
    directories.push(dirname);
    pkg = yield* readConfigPackage(_path().join(dirname, PACKAGE_FILENAME));

    const nextLoc = _path().dirname(dirname);

    if (dirname === nextLoc) {
      isPackage = false;
      break;
    }

    dirname = nextLoc;
  }

  return {
    filepath,
    directories, // 保存当前文件到有package.json文件的所有目录,后面会在这个目录内查找是否有babelrc文件
  };
}

判断是否符合查找babelrc配置文件的条件

function babelrcLoadEnabled(context, pkgData, babelrcRoots, babelrcRootsDirectory) {
  // 如果babelrcRoots直接设置为false 则不查找babelrc文件了,如果为true则会进入查找babelrc的过程
  if (typeof babelrcRoots === "boolean") return babelrcRoots;
  const absoluteRoot = context.root;

  // 如果没有设置babelrcRoots,则判断当前babel的root目录是否在当前文件的directories目录内,如果在则继续查找babelrc,如果不在则不进行查找,比如lerna项目babelrcRoots默认是没有设置的,子包内的不会主动找.babelrc文件,除非配置了babelrcRoots的值
  if (babelrcRoots === undefined) {
    return pkgData.directories.indexOf(absoluteRoot) !== -1;
  }

  let babelrcPatterns = babelrcRoots;

  if (!Array.isArray(babelrcPatterns)) {
    babelrcPatterns = [babelrcPatterns];
  }

  babelrcPatterns = babelrcPatterns.map(pat => {
    return typeof pat === "string" ? _path().resolve(babelrcRootsDirectory, pat) : pat;
  });

  if (babelrcPatterns.length === 1 && babelrcPatterns[0] === absoluteRoot) {
    return pkgData.directories.indexOf(absoluteRoot) !== -1;
  }

  // 当配置了babelrcRoots之后,确定子包的babelrc查找目录,确保子包可以使用babelrc文件
  return babelrcPatterns.some(pat => {
    if (typeof pat === "string") {
      pat = (0, _patternToRegex.default)(pat, babelrcRootsDirectory);
    }

    return pkgData.directories.some(directory => {
      return matchPattern(pat, babelrcRootsDirectory, directory, context);
    });
  });
}

在packageData.directories目录内查找RELATIVE_CONFIG_FILENAMES目录内的文件

const RELATIVE_CONFIG_FILENAMES = [".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json"];

function* findRelativeConfig(packageData, envName, caller) {
  let config = null;
	// filepath = /Users/wangks/Documents/f/react/babel-demo/packages/lerna-demo2/src/index.ts
  const dirname = _path().dirname(packageData.filepath);

  // packageData.directories [
  "/Users/wangks/Documents/f/react/babel-demo/packages/lerna-demo2/src",
  "/Users/wangks/Documents/f/react/babel-demo/packages/lerna-demo2",
]
  for (const loc of packageData.directories) {
    if (!config) {
      var _packageData$pkg;

      config = yield* loadOneConfig(RELATIVE_CONFIG_FILENAMES, loc, envName, caller, ((_packageData$pkg = packageData.pkg) == null ? void 0 : _packageData$pkg.dirname) === loc ? packageToBabelConfig(packageData.pkg) : null);
    }

  }

  return {
    config
  };
}

我们来总结下babelrc配置文件的读取规则,小结如下

  1. 如果babelrc为false,则禁用.babelrc配置文件
  2. 如果filename没传,则禁不会查找.babelrc配置文件
  3. 如果babelrcRoots为false,则不会查找.babelrc配置文件
  4. 如果babelrcRoots为true,则按默认方式加载.babelrc配置文件
  5. 如果babelrcRoots为数组,则判断pkgData.directories路径是否有babelrcRoots指定的目录,如果有则允许加载.babelrc配置文件最终如果一个文件可以通过options上的babelrc与config属性来判断,当前有哪些babel文件作用在了当前的filename上

最终合并找到的babelrc与babel.config文件的内容,如果一个filename二者都有匹配到结果会如下所示

config {
  babelrc: {
    filepath: '/Users/wangks/Documents/f/react/babel-demo/packages/lerna-demo1/.babelrc',
    dirname: '/Users/wangks/Documents/f/react/babel-demo/packages/lerna-demo1',
    options: { presets: [Array] }
  },
  config: {
    filepath: '/Users/wangks/Documents/f/react/babel-demo/babel.config.js',
    dirname: '/Users/wangks/Documents/f/react/babel-demo',
    options: { presets: [Array] }
  }
}

到这里我们知道了babel是怎么去读取项目及配置文件与相对与文件的配置文件,且babel-loader还是@rollup/plugin-babel都是对babel的封装,而我们平常使用的时候,只需要知道babel是怎么读取配置文件及碰到babel的配置文件没有生效时,我们应该怎么排查就可以解决大部分问题

最后总结一下babel配置文件读取流程图,如下图所示

项目分层 (2)

快速定位配置项问题

先确认root与rootMode参数的值是什么,默认是process.cwd(),注意monorepo项目,当我们在根目录下执行yarn build or lerna run build or lerna exec -- yarn build这样的命令时候,process.cwd()的值是子包的根目录,而不是项目的根目录

image

在确定configFile参数的取值

image-20210905210231597

然后在看babelrc与babelrcRoots的值

image-20210905210121823

最终在查看,当前文件被应用的babel.config.js与.babelrc文件

image-20210905205957518

推荐实践

非menorepo项目

在项目根目录下配置一个babel.config.js文件即可,配置如下所示

// 根目录下的babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        debug: false,
        useBuiltIns: false, // https://babeljs.io/docs/en/babel-preset-env
      },
    ],
    ['@babel/preset-react'],
    ['@babel/preset-typescript'],
  ],
  plugins: [
    [
      '@babel/plugin-transform-runtime',
      {
        corejs: false,
        helpers: true,
        regenerator: true,
      },
    ],
  ],
};
// babel-loader如下配置

{
    test: /\.[tj]sx?$/i,
    exclude: [
      /(node_modules|bower_components)/
    ],
    use: [
        loader: 'babel-loader',
        options: {
            babelrc: true // 允许查找相对于文件路径的配置
        }
    ]
}

我们可以通过exclude选项来排除需要使用babel-loader处理的node_modules下的包

{
    test: /\.[tj]sx?$/i,
    exclude: [
      /(node_modules\/(?!(axios)\/)|bower_components)/
    ],
    use: [
        loader: 'babel-loader',
        options: {
            babelrc: true
        }
    ]
}

lerna menorepo项目

这里的menorepo借助了rollup来构建,所以使用了@rollup/plugin-babel插件

在项目根目录下配置一个babel.config.js文件即可,配置如下所示

// 根目录下的babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        debug: false,
        useBuiltIns: false, // https://babeljs.io/docs/en/babel-preset-env
      },
    ],
    ['@babel/preset-react'],
    ['@babel/preset-typescript'],
  ],
  plugins: [
    [
      '@babel/plugin-transform-runtime',
      {
        corejs: false,
        helpers: true,
        regenerator: true,
      },
    ],
  ],
};
// 根目录下的rollup.base.config.js

import path from 'path';
import babel from '@rollup/plugin-babel';
import commonjs from '@rollup/plugin-commonjs';
import json from '@rollup/plugin-json';
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';

export default function createConfig({input = './src/index.ts', output, tsconfig = './tsconfig.json', external = []}) {
  return {
    input,
    output,
    plugins: [
      resolve(),
      json(),
      commonjs({
        transformMixedEsModules: true,
      }),
      typescript({
        tsconfig, // 解决插件没有应用 tsconfig 内的配置
      }),
      babel({
        configFile: true,
        rootMode: 'upward', // 因为lerna run 及exec执行子包命令的时候,cwd是子包的根目录,所以这里需要通过rootMode或者root参数将目录指向到根目录,而不是子package目录
        extensions: ['.js', '.jsx', '.es6', '.es', '.mjs', '.cjs', '.ts', '.tsx'],
        babelrcRoots: [
          '.',
          './packages/*',
        ],
      }),
    ],
    external, // 不需要打入包内的第三方npm包,例如['lodash']
  }
}
// 子包目录下的rollup.config.js
import createConfig from '../../rollup.base.config';
import pkg from './package.json';

export default [createConfig({
  output: [
    {
      file: pkg.main,
      format: 'cjs',
      exports: 'named',
    },
    {
      file: pkg.module,
      format: 'es',
      exports: 'named',
    },
  ],
})];

如果子包需要使用不同的babel配置,只需要在子包的根目录或者需要处理的文件的目录下,创建一个.babelrc配置文件即可,然后在.babelrc配置文件内配置想要特殊处理的babel配置

总结

babel引入了项目级配置文件的概念,虽然解决了一部分问题,但是无形中又增加了用户的理解与使用成本,所以我们在使用的过程中,只能尽可能的去总结一些更简单的实践,以提高我们的开发效率与排错成本

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant