du blog
Hello, welcome to my blog
babel笔记(三)-options
created: Jan 19 21updated: Mar 15 21

babel的 options 非常多, 并且很多的使用方式也不同, 在这里尽量阐述每个options的作用.

options可以通过多种方式传递给babel, 当直接传递给babel的时候可以传递一个object,当使用包装器的时候,可以使用配置文件传递参数

如果通过@babel/cli传递选项,您将需要对名称进行kebab大小写处理. example:

1 npx babel --root-mode upward file.js # 相当于传递rootMode配置选项 2

Primary options

这些选项仅作为编程使用的时候才能传递,因此它们通常在包装babel的工具中使用,或者直接调用 babel.transform. babel的集成用户如 babel-loader 或@babel/register 不太可能用到这些

cwd

type: string

default: peocess.cwd()

编程参数中所有路径的相对工作目录.

caller

type: object

1 interface CallerData { 2 name: string; 3 supportsStaticESM?: boolean; 4 supportsDynamicImport?: boolean; 5 supportsTopLevelAwait?: boolean; 6 supportsExportNamespaceFrom?: boolean; 7 } 8

name为调用者的标识, 其他参数传递给babel,以供配置文件、预设、插件使用.

首先给配置文件使用,这是官方的demo

1 function isBabelRegister(caller) { 2 return !!(caller && caller.name === "@babel/register"); 3 } 4 5 module.exports = function(api) { 6 const isRegister = api.caller(isBabelRegister); 7 8 return { 9 // ... 10 }; 11 }; 12

给配置文件使用的话应该就不局限于以上的几个属性了, 这里我做了一下测试

test.js:

1 const babelCore = require('@babel/core'); 2 const code = ` 3 const test = 1; 4 export default test; 5 class Person{} 6 ` 7 const obj = babelCore.transformSync(code, { 8 caller: { 9 name: "my-custom-tool", 10 transform: true 11 }, 12 }) 13 console.log(obj, '-------obj') 14

babel.config.js

1 module.exports = (api) => { 2 const cb = (caller) => { 3 console.log(caller) 4 } 5 const isTransform = api.caller(cb) 6 return { 7 ... 8 } 9 } 10

output:

1{ name: 'my-custom-tool', transform: true } 2

接下来是给预设使用

preset-env的参数module文档上已经说明根据caller来确定是否转换es模块和模块功能(例如:import())

那么我们实验一下

config:

1 module.exports = (api) => { 2 const cb = (caller) => { 3 console.log(caller) 4 } 5 const isTransform = api.caller(cb) 6 return { 7 "presets": [ 8 [ 9 "@babel/preset-env", 10 { 11 "targets": { 12 "chrome": "50", 13 "ie":11 14 }, 15 "bugfixes":true, 16 "useBuiltIns": "usage", 17 "corejs":{ 18 "version":3.8, 19 "proposals":true 20 } 21 } 22 ] 23 ], 24 } 25 } 26

这里加入了preset-env的配置

还是使用上边例子的test.js

输出:

1{ 2 metadata: {}, 3 options: { 4 parserOpts: { 5 sourceType: 'module', 6 sourceFileName: undefined, 7 plugins: [Array] 8 }, 9 caller: { name: 'my-custom-tool', transform: true }, 10 cloneInputAst: true, 11 babelrc: false, 12 configFile: false, 13 passPerPreset: false, 14 envName: 'development', 15 cwd: '/Users/**/Documents/my/babel', 16 root: '/Users/**/Documents/my/babel', 17 plugins: [ 18 [Plugin], [Plugin], [Plugin], [Plugin], 19 [Plugin], [Plugin], [Plugin], [Plugin], 20 [Plugin], [Plugin], [Plugin], [Plugin], 21 [Plugin], [Plugin], [Plugin], [Plugin], 22 [Plugin], [Plugin], [Plugin], [Plugin], 23 [Plugin], [Plugin], [Plugin], [Plugin], 24 [Plugin], [Plugin], [Plugin], [Plugin], 25 [Plugin], [Plugin], [Plugin], [Plugin], 26 [Plugin], [Plugin], [Plugin], [Plugin], 27 [Plugin], [Plugin] 28 ], 29 presets: [], 30 generatorOpts: { 31 filename: undefined, 32 auxiliaryCommentBefore: undefined, 33 auxiliaryCommentAfter: undefined, 34 retainLines: undefined, 35 comments: true, 36 shouldPrintComment: undefined, 37 compact: 'auto', 38 minified: undefined, 39 sourceMaps: false, 40 sourceRoot: undefined, 41 sourceFileName: 'unknown' 42 } 43 }, 44 ast: null, 45 code: '"use strict";\n' + 46 '\n' + 47 'Object.defineProperty(exports, "__esModule", {\n' + 48 ' value: true\n' + 49 '});\n' + 50 'exports.default = void 0;\n' + 51 '\n' + 52 'function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }\n' + 53 '\n' + 54 'var test = 1;\n' + 55 'var _default = test;\n' + 56 'exports.default = _default;\n' + 57 '\n' + 58 'var Person = function Person() {\n' + 59 ' _classCallCheck(this, Person);\n' + 60 '};', 61 map: null, 62 sourceType: 'module' 63} -------obj 64

可以看到使用preset-env的时候 es模块是默认被转换成为commonjs的, 这里我们加入 supportsStaticESM 参数

test.js

1 const obj = babelCore.transformSync(code, { 2 caller: { 3 name: "my-custom-tool", 4+ supportsStaticESM: true 5 }, 6 }) 7

output:

1 code: 'function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }\n' + 2 '\n' + 3 'var test = 1;\n' + 4 'export default test;\n' + 5 '\n' + 6 'var Person = function Person() {\n' + 7 ' _classCallCheck(this, Person);\n' + 8 '};', 9

可以看到 此时的es模块没有被转换

至于插件 目前没有发现跟这个参数相关的

此外将 supportsStaticESM 设为true之后 babel会给出提示: 只有将ES模块转换为AMD、CommonJS或SystemJS时才支持动态导入。只有解析器插件将被启用。

不过现在模块转换和动态导入一般由webpack支持 这里也没有再做过多深入研究

filename

type:string

与当前正在编译的代码关联的文件名(如果有)文件名是可选的,但是当文件名未知时,部分babel功能不可用,因为一部分选项依赖于文件名来实现其功能。

可能遇到的三种情况:

1.文件名公开给插件。有些插件可能需要文件名的存在。

2.'test'、'exculde'、'ignore'等配置需要使用filename进行匹配

3.如果省略此选项,babel将表现为 babelrc:false

第一个场景目前没有见到有插件需要

第二种情况:

我在之前的配置基础上加上了ignore

babel.config.json

1 ignore:[ 2 'test.js' 3 ] 4

执行test文件发现是没有变化的,依旧可以输出转换结果

如果我加上filename的配置,

test.js

1 const filename = 'test.js' 2 const babelCore = require('@babel/core'); 3 4 const code = ` 5 const test = 1; 6 export default test; 7 class Person{} 8 ` 9 const obj = babelCore.transformSync(code, { 10 caller: { 11 name: "my-custom-tool", 12 supportsStaticESM: true 13 }, 14 filename 15 }) 16 console.log(obj, '-------obj') 17

则输出结果直接为null

1 { name: 'my-custom-tool', supportsStaticESM: true } 2 null -------obj 3

第三种情况:

目前这是我的目录结构:

1 .babelrc.js 2 .babel.config.js 3 test.js 4

而在不加filename的情况下.babelrc.js是一直不生效的

filenameRelative

type: string

Default: path.relative(opts.cwd, opts.filename) 如果传递filename

用作sourceFileName选项的默认值,并用作AMD / UMD / SystemJS模块转换的文件名

code

type:boolean

default: true

像上边使用trtransformSync的例子中,默认是返回 code 和 map 属性的, 在某些情况下可能需要多次调用babel, 为了避免重复转换, 设置 ast:true 来直接获取ast会更方便

注:这里的map应该指的是sourceMap, 但是像上边的例子中,返回的map为null,需要手动指定sourceMaps:true

example:

1 const filename = 'test2.js' 2 const babelCore = require('@babel/core'); 3 4 const code = ` 5 const test = 1; 6 export default test; 7 class Person{} 8 ` 9 const obj = babelCore.transformSync(code, { 10 caller: { 11 name: "my-custom-tool", 12 supportsStaticESM: true 13 }, 14+ sourceMaps:true, 15 filename 16 }) 17 console.log(obj, '-------obj') 18

output:

1 code:... 2 map: { 3 version: 3, 4 sources: [ 'test2.js' ], 5 names: [ 'test', 'Person' ], 6 mappings: ';;;;;;;;;AACA,IAAMA,IAAI,GAAG,CAAb;eACeA,I;;;IACTC,M', 7 sourcesContent: [ '\nconst test = 1; \nexport default test;\nclass Person{}\n' ] 8 }, 9 sourceType: 'script' 10

这样的话才返回了sourceMap相关的东西

ast

Type: boolean

Default: false

这个配置与上述的code相对应,这里直接上官网的demo:

1 const filename = "example.js"; 2 const source = fs.readFileSync(filename, "utf8"); 3 4 // Load and compile file normally, but skip code generation. 5 const { ast } = babel.transformSync(source, { filename, ast: true, code: false }); 6 7 // Minify the file in a second pass and generate the output code here. 8 const { code, map } = babel.transformFromAstSync(ast, source, { 9 filename, 10 presets: ["minify"], 11 babelrc: false, 12 configFile: false, 13 }); 14

cloneInputAst

Type: boolean

Default: true

Added in v7.11.0

默认情况下 babel.transformFromAst 会克隆传入的 ast 以免数据发生变化, 如果传入的 ast 在其它地方没有使用,则可以置为false以提高性能

config loading options

由于可能存在多种类型的配置文件,因此加载配置可能会有一些复杂. 而且这些配置文件可能具有各种各样的嵌套配置对象.

root

type: string

default: opts.cwd

placement: 只能在编程形式中使用

"rootMode"查找项目根文件夹的初始路径.通常在这两种情况下使用:

1.检查"configFile"(babel.config,json)的基本目录.

2.作为BabelrcRoots的默认值.

rootMode

type: "root" | "upward" | "upward-optional"

default: root

placement: 只能在编程形式中使用

Added in: v7.1.0

这个选项与"root"结合在一起,定义了babel怎么通过"root"查找最终的项目根

注意: 在babel 7.8.0版本支持了 babel.config.json, 在旧的7版本中只支持 babel.config.js

root: 仅将"root"作为项目根.

upward: 从"root"向上查找包含 babel.config.json 文件的目录, 如果不存在则抛出错误.

upward-optional: 从"root"向上查找包含 babel.config.json 文件的目录, 如果不存在返回"root".

"root"是默认的, 因为它避免意外加载外部babel.config.json的风险.

使用 monorepo 项目结构则有可能使用 "upward", 在子包中进行编译,向上查找根目录的babel.config.json.

envName

Type: string

Default: process.env.BABEL_ENV || process.env.NODE_ENV || "development"

placement: 只能在编程形式中使用

配置加载配置过程中使用的变量名. 解析出来的环境变量值用作匹配 "env" 的配置,也可以通过 api.env() 在配置文件中获取当前环境.

configFile

type:string

Default: path.resolve(opts.root, "babel.config.json")

placement: 只能在编程形式中使用

默认搜索一个babel.config.json的文件,但是可以传递任何js或者json5的文件

这个选项不会影响babelrc.json的加载,也就是说如果传递了一个babelrc.json的文件, 则会导致相同的配置会被加载两次,建议命名时不以babelrc命名

babelrc

type: boolean

default: 只要指定了filename选项,则默认为true

placement: 可以以编程的形式和配置文件的形式配置该选项, 编程形式会覆盖配置文件形式

true: 将启用对相对于"filename"的babelrc.json的搜索

注意: babelrc.json 只有在"filename"与babelrcRoots匹配的情况下才会被加载

babelrcRoots

type: boolean | MatchPattern | Array

default: opts.root

placement: 可以以编程的形式和配置文件的形式配置该选项, 编程形式会覆盖配置文件形式

默认情况下,babel只会在root目录下搜索 babelrc.json, 因为babel不知道是否要加载给定的 babelrc.json 或者是否已经安装了插件和预设,因为要编译的文件可能位于node_modules中, 或者已经被链接到项目中

使用此选项,可以告诉babel其他应该视为"根"的列表

example, 希望每个子包 都拥有自己的配置

1 babelrcRoots: [ 2 // Keep the root as a root 3 ".", 4 5 // Also consider monorepo packages "root" and load their .babelrc.json files. 6 "./packages/*" 7 ] 8

Plugin and Preset options

plugins

type: Array<pluginEntry | plugin>

default: []

处理文件时要使用的插件数组, 在嵌套结构中(env overrides) 参见合并.

注意: 这个选项接收babel本身的插件实例, 但是不建议直接使用, 如果要创建一个插件或者预设的持久表示, 请使用babel.createConfigItem()

这个babel.createConfigItem没有明白是干什么的

presets

Type: Array

Default: []

处理文件时要使用的预设数组, 在嵌套结构中(env overrides) 参见合并.

注意: 预设的格式和插件一样, 除了名字要求是"preset-"而不是"plugin" 并且预设不能是插件的实例

passPerPreset

这个已经废弃了

Config Merging options

extends

Type: string

Placement: Not allowed inside of presets

配置文件可以扩展其他配置文件, 在当前配置文件的基础上合并.

env

Type: { [envKey: string]: Options }

placement: 不能嵌套在另一个env中

envKey的值与envName环境变量的值匹配的时候启用的整套配置选项

注意: 该配置会在配置文件之上合并

overrides

Type: Array

placement: 不能嵌套在另一个overrides或者env中

允许用户提供一组选项, 该选项将合并到当前配置中, 最好将该选项与 "test"、 "include"、 "exclude"结合使用, 以提供覆盖的条件.

1 overrides: [{ 2 test: "./vendor/large.min.js", 3 compact: true, 4 }], 5

test

Type: MatchPattern | Array

如果不匹配,则当前配置在合并中被忽略,该选项在overrides中使用最有用

注意: 不会影响之前的编程选项和evn等

include

同test

exclude

与test相反

ignore

Type: Array

placement: 不能再预设中使用

如果有匹配,则babel则停止当前构建的(对于匹配文件)

1 ignore: [ 2 "./lib", 3 ] 4

例如: 不对lib目录进行转译.

only

Type: Array

placement: 不能再预设中使用

与ignore类似

1 only: [ 2 "./src", 3 ] 4

例如: 只对src下的文件进行转译

Source Map options

inputSourceMap

type: boolean |sourceMap

default: true

如果文件本身包含一个//# sourceMappingURL=..., 则 true 会尝试从文件本身加载sourceMap, 如果找不到sourceMap文件或者sourceMap文件无法被加载、解析, 则会被丢弃

如果提供了一个对象, 则它被认为是sourceMap本身

sourceMaps

type: boolean | "inline" | "both"

default: false

这个选项分为两种情况

1.编程情况下:

true: 会在返回结果中生成一个map字段 字段值为sourceMap

example:

1 { 2 map: { 3 version: 3, 4 sources: [ 'test2.js' ], 5 names: [ 'test', 'Person' ], 6 mappings: ';;AACA,IAAMA,IAAI,GAAG,CAAb;AACA,eAAeA,IAAf;;IACMC,M', 7 sourcesContent: [ '\nconst test = 1; \nexport default test;\nclass Person{}\n' ] 8 }, 9 } 10

"inline": 生成的sourceMap会作为数据添加在返回的代码末尾, 不会出现在返回的结果中

example:

1 code: 'function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }\n' + 2 '\n' + 3 'var test = 1;\n' + 4 'export default test;\n' + 5 '\n' + 6 'var Person = function Person() {\n' + 7 ' _classCallCheck(this, Person);\n' + 8 '};\n' + 9 '//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QyLmpzIl0sIm5hbWVzIjpbInRlc3QiLCJQZXJzb24iXSwibWFwcGluZ3MiOiI7O0FBQ0EsSUFBTUEsSUFBSSxHQUFHLENBQWI7QUFDQSxlQUFlQSxJQUFmOztJQUNNQyxNIiwic291cmNlc0NvbnRlbnQiOlsiXG5jb25zdCB0ZXN0ID0gMTsgXG5leHBvcnQgZGVmYXVsdCB0ZXN0O1xuY2xhc3MgUGVyc29ue31cbiJdfQ==', 10

"both": 会在生成的代码结尾加上sourceMap, 也会在返回的对象中添加map字段 为sourceMap

2.命令行使用情况下:

true: 将会写入.map的sourceMap文件

example

transform result:

1 index.js 2 index.js.map 3

"ineline": 将会直接写入转换后的文件

"both": 会写入转换后的文件 也会生成.map文件

sourceMap

sourceMaps的同义词

sourceFileName

type: string

default: path.basename(opts.filenameRelative) 当不可用时 为 unknown

用于返回sourceMap对象中的文件名称

example:

1 const babelCore = require('@babel/core'); 2 sourceFileName = 'hello.js' 3 const code = ` 4 const test = 1; 5 export default test; 6 class Person{} 7 ` 8 const obj = babelCore.transformSync(code, { 9 caller: { 10 name: "my-custom-tool", 11 supportsStaticESM: true 12 }, 13 sourceFileName, 14 sourceMaps:true, 15 }) 16 console.log(obj, '-------obj') 17

output:

1 { 2 ... 3 map: { 4 version: 3, 5 sources: [ 'hello.js' ], 6 names: [ 'test', 'Person' ], 7 mappings: ';;AACA,IAAMA,IAAI,GAAG,CAAb;AACA,eAAeA,IAAf;;IACMC,M', 8 sourcesContent: [ '\nconst test = 1; \nexport default test;\nclass Person{}\n' ] 9 }, 10 } 11

sourceRoot

type:string

在生成的map对象中会生成sourceRoot字段

Misc options

sourceType

Type: "script" | "module" | "unambiguous"

default: module

script: 使用ECMAScript脚本语法 解析文件,不允许 import/export 等导入导出语法 并且文件不在严格模式下

module: 使用ECMAScript 模块语法 解析文件,允许 import/export 等导入导出语法 文件自动在严格模式下

unambiguous: 如果存在 import/export 等导入导出语法 则将文件视为模块, 否则视为脚本

unambiguous在类型未知的文件中会非常有用, 但是可能导致错误匹配, 因为不是呀import/export 等导入导出语法的文件是有效的

该选项非常重要, 因为他会影响对于文件的解析, 以及对于import/require的转换

例如: @babel/transform-runtime依赖于当前选项决定是使用 import 还是 require

这里我扒了一下tarnsform-runtime的源码, 看到了这两行代码, 至于transform-runtime的代码可以去查看我的另一篇博客

1 const helpersDir = 2 esModules &amp;&amp; file.path.node.sourceType === "module" 3 ? "helpers/esm" 4 : "helpers"; 5

@babel/preset-env 的 useBuilIns 选项也执行相同的操作.

(@babel/preset-env 的module选项auto 会默认启用将ES插件转换为commonjs的插件 )

设置正确的sourceType选项很重要, 错误的类型会导致babel将import语句插入到本应是commonjs的文件

注意: 该选项不会影响到 .mjs 结尾的文件, 目前它们是硬编码为ES模块文件

highlightCode

type: boolean

default: true

在Babel错误消息的代码片段中突出标记,使它们更容易阅读。

wrapPluginVisitorMethod

Type: (key: string, nodeType: string, fn: Function) => Function

允许用户在每个访问着上加装一个包装器, 以便babel在执行插件时,检查访问者进程

key: 标示正在执行的插件

nodeType: 表示当前正在访问的AST类型

fn: 是访问函数本身

用户可以返回替换函数

这个选项应该是对于插件转译的一个包装

ParserOpts

type: {}

要传递给解析器的参数对象

generatorOpts

type: {}

要传递给生成器的参数对象

Code Generator options

retainLines

Type: boolean

Default: false

生成的代码尽量保持与原始文件同一行上, 这个是为了在不使用sourceMap时排错用的

compact

Type: boolean | "auto"

Default: "auto"

auto 将通过评估代码长度来设置 code.length > 500_000

在紧凑模式下生成代码时, 所以可选的换行和空格符将被忽略

minified

Type: boolean

Default: false

包含 compat 为true, 省略分号, 在可能的情况下删除 new Foo()的(), 可能输出较短的文字版本

auxiliaryCommentBefore

type:string

指定注释,插入到原始文件代码之前

注意:原始文件中存在和不存在的定义可能会有些丑陋,因此不建议使用此选项。 如果您需要以某种方式注释代码,则最好使用Babel插件进行注释。

auxiliaryCommentAfter

type:string

指定注释,插入到原始文件代码之后

comments

type:boolean

default: true

如果没有给出函数,则为shouldPrintComment提供一个默认注释状态

shouldPrintComment

Type: (value: string) => boolean

Default without minified: (val) => opts.comments || /@license|@preserve/.test(val)

Default with minified: () => opts.comments

返回值确定给定的注释是否包含在输出的代码中

Advanced Usage

有关更多代码生成器选项,请参见生成器选项。

AMD / UMD / SystemJS module options

这个选项类就忽略了 平时都使用webpack 也不太会用到

Options Concepts

options一些概念的解释

MatchPattern

Type: string | RegExp | (filename: string | void, context: { caller: { name: string } | void, envName: string, dirname: string ) => boolean

匹配模式, 在使用test, ignore等匹配的时候可能会用到

Merging

合并options的一些规则(env, overrides)

parserOpts 对象被合并,而不是替换, 与顶级options合并相同

generatorOpts 对象被合并,而不是替换, 与顶级options合并相同

preset/plugin根据自身id替换

Plugin/Preset merging

example:

1 plugins: [ 2 './other', 3 ['./plug', { thing: true, field1: true }] 4 ], 5 overrides: [{ 6 plugins: [ 7 ['./plug', { thing: false, field2: true }], 8 ] 9 }] 10

结果配置:

1 plugins: [ 2 './other', 3 ['./plug', { thing: false, field2: true }], 4 ], 5

插件和预设的标识为id+name

1 plugins: [ 2 ['./plug', {one: true}], 3 ['./plug', {two: true}] 4 ] 5

这种情况前边的会被后边的覆盖

如果想要实例化两个插件,则需要为两个插件提供不同的名称

1 plugins: [ 2 ['./plug', {one: true}, "first-instance-name"], 3 ['./plug', {two: true}, "second-instance-name"] 4 ] 5

Plugin/Preset entries

插件和预设可以有几种不同的结构:

EntryTarget 单独的插件

[EntryTarget, EntryOptions] 插件和options

[EntryTarget, EntryOptions, string] 插件和options 和名称

ConfigItem 一个由babel.createConfigItem()创建的插件配置项。

EntryTarget

Type: string | {} | Function

一个插件和预设可以有两种不同的格式

string: 一个插件的路径或者标识符

{} |函数: 一个实际的插件/预设对象或函数,如 require('...')

EntryOptions

Type: undefined | {} | false

选项在执行时将会被传递给插件或者预设, undefined会被认为是空对象

false将会禁用, 这个在插件执行顺序中比较重要:

1 plugins: [ 2 'one', 3 ['two', false], 4 'three', 5 ], 6 overrides: [{ 7 test: "./src", 8 plugins: [ 9 'two', 10 ] 11 }] 12

对于src文件夹 将会启用two插件 并且在1 和 2 插件之间执行

Name Normalization

默认情况下 babel具有 babel-plugin- 和 babel-preset- 的前缀, 为了避免重复,Babel有一个名称规范化阶段,在加载项目时将自动添加这些前缀。这可以归结为几个主要规则

这里就不贴规则了 直接上例子

最后 目前所使用版本为 7.12.10