想象这样一个场景:你住在城里,过年回老家跟爷爷奶奶聊天。你嘴里蹦出的"YYDS"“绝绝子”“我 emo 了”,爷爷奶奶听得一头雾水。于是你请来一位翻译官——他不急着开口,而是先把你的话拆成“主语-谓语-宾语”的结构表,标出哪些是网络新词、哪些是新句式;再对照一本“新旧词汇对照表”逐处替换;最后用爷爷奶奶听得懂的普通话完整复述一遍。这位翻译官,就是 Babel。 但光翻译还不够。当你讲到“我用手机付了钱”时,爷爷奶奶根本没听说过“手机”这个东西。翻译得再准确,认知的空白仍然无法填平。所以你还得从包里掏出一个真手机,递到他们手上,教他们用。这个“直接递工具”的动作,就是 polyfill——语法翻译不了的,就干脆把缺失的能力补上。 更妙的是,现场还有一位戴金丝眼镜的“校对员”(TypeScript 编译器 tsc)。他在你开口前先检查原稿有没有逻辑矛盾、搭配不当;而 Babel 这位翻译官,只负责把话说清楚,不做对错判断。一个管安全,一个管兼容,各司其职,配合默契。
掌握 Babel 转译与 polyfill 策略,是前端工程师搭建跨浏览器工程化体系的核心能力。它能让你放心使用最新语法与 API,同时保障产品在老旧环境中的稳定性,是每个现代前端团队的必修课。
目录
可视化演示
动态演示Babel转译的核心机制,通过全局/局部/动态/静态四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Babel转译:Babe Babel核心架构与转译流程 AST抽象语法树的结构与遍历机 核心机制可视化 Babel转译整体架构与关键组件
一、转译原理:翻译官的三步走
翻译官接手任务后的第一件事,绝不是开口说话。他会把你的话在脑子里拆成“主语-谓语-宾语”的结构表,标出哪个词是新词、哪个句是新句式;然后对照“新旧词汇对照表”逐处替换;最后把调整好的句子用爷爷奶奶熟悉的语序说出来。整个过程分三步:拆结构、做替换、开口说。
Babel 也是同样的三步,命名为解析(Parse)、转换(Transform)、生成(Generate)。你写下的 const greet = () => '你好',在 Babel 眼里不是一行字符串,而是一个等待被拆解、被改写、被重新打印的对象。
解析阶段由 @babel/parser 完成:先把源码切成一串词牌(Token),再按 ECMAScript 语法规范组装成 AST。转换阶段是 Babel 的核心工作区,各类插件在这棵树上找到需要改写的节点并就地替换。生成阶段由 @babel/generator 把改写后的树重新打印成代码字符串,并可产出 sourcemap。值得区分的是,Babel 属于“转译”而非“编译”:编译把高级语言降级为低级语言,Babel 始终在 JavaScript 内部迁移版本,语言不变,只换“口音”。
所以翻译官永远不评价你的话有没有道理,只负责把意思换成爷爷奶奶听得懂的腔调。Babel 也从不检查代码逻辑对错,它只在语法层面保证“人人都能听懂”——这就是语法转译与程序编译的本质区别。
- 源码入口翻译官拿到的原稿,就是新方言写成的 JavaScript 源码。
- 解析成骨架词法分析与语法分析把原稿拆成词牌、组装成语法树。
- 转换改写插件在语法树各节点上进行替换,把新句式改成老句式。
- 生成输出把改写后的骨架打印成完整句子,交还给浏览器这位“听众”。
二、AST 语法树:句子的骨架
翻译官要拆结构,总得先把句子画成一张“成分表”:主语是谁、谓语是什么、宾语在哪里。这张表不是原话,却完整保留了原话的逻辑骨架。代码世界里的这张表,就是 AST(Abstract Syntax Tree,抽象语法树)。
从一行源码到一棵树,要经过两次加工。第一次是词法分析(Tokenizer):把字符串按最小有效单元切成词牌,比如关键字 const、标识符 greet、符号 =、括号。第二次是语法分析(Parser):按语言规范把这些词牌组织成一棵有父子关系的树。树的根节点是 Program,代表整个文件;每个变量声明、函数定义、表达式,都对应树上的一个子树。
AST 里的每个节点都带一个 type 字段,比如 VariableDeclaration(变量声明)、ArrowFunctionExpression(箭头函数表达式)。Babel 插件之所以能精准“动手术”,正是因为代码被表达成可以随意增删改的树——你要改哪句话,就找到那句话对应的节点,连同它的子节点一起替换。
有了这张成分表,翻译官在任何一个关节上动手都不会牵一发而乱全身。AST 就是 Babel 的“手术台”——所有插件的魔法,都发生在这棵树上。
- 分词(词法分析)把源码字符串按最小有效单元切成词牌序列,如同把一句话切成一个个词语。
- 建树(语法分析)语法分析器按语言规范把词牌组织成带父子关系的一棵树。
- 语句子节点Program 根节点下挂着一组 statement 子树,每条语句一个分支。
三、插件机制:手术刀与访问者
一个大项目里,翻译官不可能一个人掌握所有新旧句式的对照。于是他背后站着一支翻译军团:有人专攻“网络热词→普通话”,有人专攻“倒装句→正常语序”,还有人专攻“文绉绉→大白话”。每位翻译上岗时只回答一个问题:你看到哪种句式时才会动手?——这就是插件的“访问者声明”。
在 Babel 里,一个插件本质上就是一个包含 visitor 对象的模块。visitor 对象里声明插件关心的节点类型,比如写 ArrowFunctionExpression(path) { ... },意思就是“当我遍历到一个箭头函数节点时,请调用我”。
Babel 遍历 AST 时采用深度优先方式:每进入一个节点触发 enter 钩子,离开时触发 exit 钩子。回调里拿到的 path 就是一把“手术钳”——既能读取当前节点,也能通过 replaceWith、remove、insertAfter 等方法进行增删改造。多个插件按顺序配合执行:先执行 plugins 数组,再执行 presets 数组,preset 内部按倒序执行。这套机制让社区涌现出几百个“各管一段”的专项翻译。
翻译军团里的每一位成员只认自己负责的句式,各司其职、互不干扰。这正是 Babel 插件化设计的精髓——把几百个插件叠加起来,你就得到了一支能翻译任何“新方言”的队伍。
- 声明关注节点插件在 visitor 对象里写上自己要处理的节点类型名,比如箭头函数。
- 遍历触发Babel 深度优先遍历 AST,进入该类型节点时调用 enter 钩子。
- 节点替换调用 path.replaceWith 把旧节点换成普通函数节点,完成语法转换。
四、Preset 配置:按客人名单请翻译
翻译军团人数虽多,但请哪些人到场,得看客人名单。如果爷爷奶奶平时只看戏曲、不看脱口秀,那“脱口秀热词翻译”就不必请来。Babel 里的“请人策略”就是 preset(预置集),“客人名单”则是 browserslist 的 targets。
@babel/preset-env 是官方推荐的情景化预置集。它的聪明之处在于不写死转换规则,而是先读取 targets(比如 "ie": "11" 或 "chrome": "58"),再自动算出目标浏览器还不支持哪些语法,只启用对应的转换插件。换句话说,preset-env 是“按浏览器裁剪插件”的智能打包者。
配置通常在 babel.config.json 中完成:presets 数组里写上 ["@babel/preset-env", { "targets": { "ie": "11" } }]。如果你把 targets 设成现代浏览器,很多新语法其实不需要转换,最终产物体积更小、运行更快;只有老旧浏览器才需要加载较重的转换链。这就是“按需启用”带来的工程收益。
像按客人名单请翻译一样:客人全是本地人就一个翻译都不请,有外国客人就只请对应语种。preset-env 用 targets 这张名单,精确控制了每次构建的“翻译阵容”。
- 候选全集preset-env 内部预置了一整套语法转换插件,构成全家桶。
- 目标清单targets 声明浏览器兼容范围,如 chrome 58 以上、ie 11。
- 差集裁剪用全集减去目标已支持的语法,剩下的插件才真正启用。
五、Polyfill 策略:缺什么补什么
有些东西,光靠翻译无法解决。当你说“我用手机付了钱”,爷爷奶奶的问题不是“付钱”听不懂,而是他们根本不知道“手机”是什么。翻译得再准,认知空白的硬伤仍在。你能做的最后一招:从包里掏出一个真手机,递到他们手上,教他们用。
Babel 也面临同样的困境。箭头函数、模板字符串、解构赋值属于“语法”,语法树里看得见摸得着,翻译官可以改写。但 Promise、Array.includes、Object.assign 是“API“——它们不是写法问题,而是运行时的能力缺口。IE11 根本不存在 Promise 这个对象,Babel 转译语法时也”看不到”它。这时就要用 polyfill(垫片):把标准 API 的实现代码直接注入浏览器。
最常用的垫片库是 core-js。配合 preset-env 的 useBuiltIns: "usage" 配置,Babel 会自动分析代码里用到了哪些 API、目标浏览器缺了哪些,然后只把缺的那些垫片 import 进来——用多少补多少。另一个关键插件 @babel/plugin-transform-runtime 负责两件事:把转译时内联的辅助函数统一抽到公共模块,避免每个文件重复打包;同时以运行时方式引入 core-js 垫片,避免污染全局对象。
polyfill 就是那个“直接递手机”的动作——语法翻译不了,就干脆把工具递到浏览器手上。翻译官解决“说不说得明白”,垫片解决“有没有这个能力”。
- 查支持表targets 决定目标浏览器缺失哪些 API,形成“缺口清单”。
- 缺失判断代码中用到的特性不在支持表里,就判定需要垫片。
- 按需注入core-js 配合
useBuiltIns: "usage"只在用到处引入对应模块。
六、TypeScript 对比:校对员与翻译官
翻译现场,又来了一位戴金丝眼镜的校对员。他对原稿极其苛刻:主语缺失、动宾搭配不当、前后矛盾,全部用红笔圈出来。改完错后,他也能把稿子翻成普通话。而真正的翻译官到场后只做一件事——把稿子翻出来,一个字都不校对。
校对员就是 TypeScript 编译器 tsc(TypeScript Compiler)。它的完整职责是"type check + transpile":先做类型检查,再把 TS 转成 JS。而 Babel 加上 @babel/preset-typescript 后,只把类型注解剥掉、做语法转译,对类型错误完全不闻不问。
于是工程界形成了经典分工:用 Babel 做转译,因为它快、插件生态丰富、与 preset-env 无缝配合;再单独跑 tsc --noEmit 做类型检查,只输出诊断报告、不产出文件。两者的定位差异可以这样记:tsc 负责“确保原稿没病”,Babel 负责“确保说出口大家听得懂”。如果你的 TS 代码根本不过 tsc,Babel 照样能转译成功——但这个“成功”是危险的,类型安全全凭自觉。
校对员保证原稿没毛病,翻译官保证说出来的话大家听得懂。少了谁都不行——这就是 TypeScript 与 Babel 在工程里“一个管安全、一个管兼容”的默契。
- tsc 双职责tsc 先做类型检查、再做语法转译,把 TS 编译成 JS。
- Babel 单职责@babel/preset-typescript 只剥离类型注解,不检查类型对错。
- 工程协作用
tsc --noEmit单独校验类型,Babel 负责输出 JS,各司其职。
七、工程化应用:流水线上的翻译系统
真实项目里,翻译官不会被拎到每个页面上手动工作。他会住进一条自动化流水线:代码从提交那一刻起,自动经过质检、翻译、打包,最后统一送到浏览器这个“听众”面前。这条流水线,就是前端工程化构建系统。
最经典的组合是 webpack + babel-loader。webpack 把项目看成一张模块依赖图,loader 则是每个模块的“入口翻译官”:babel-loader 接收模块源码字符串,调用 Babel 完成转译,再把转换后的字符串交还给 webpack 打包。配置时只需在 module.rules 里声明 test: /\.(js|ts)$/ 和 use: 'babel-loader'。
为了让调试体验不打折,Babel 会在生成阶段产出 sourcemap。浏览器借助这份映射,能把 bundle 里的代码行号还原到原始 TS 源码——断点打在“原稿”上,而不是“译稿”上。性能方面还有几个常用技巧:给 babel-loader 开 cacheDirectory 缓存转译结果(文件没变就不重复翻译)、用 exclude 跳过 node_modules、把浏览器清单集中维护在 .browserslistrc 里,让 preset-env 与其他工具共用一份“客人名单”。
流水线上的每个环节各司其职:质检员查稿件、翻译官翻译、传送带打包。在整套前端工程化体系里,Babel 始终是那个最不起眼却最可靠的翻译官——你几乎感觉不到它,但它默默保证了每一位“爷爷奶奶”都能听懂你的代码。
- 模块翻译webpack 的 module.rules 把每个模块交给 babel-loader 转译成目标语法。
- 打包合并webpack 按照依赖图把所有模块合并成浏览器可执行的 bundle。
- 源码映射sourcemap 记录转换前后行列对应关系,调试时断点打在原始 TS 上。
AI 应用场景
场景一:老后台系统的浏览器兼容改造
阶段:构建流水线中的编译转换阶段(类似模型部署前的兼容适配)
解决的问题:一个运营五年的后台系统仍运行在 IE11 上,新代码刚用上可选链 ?.、空值合并 ?? 等 ES2020 语法,直接上线就会白屏。逐行手改成本极高,团队需要一个“一键翻译”方案。用 Babel 的 preset-env 按 targets 自动裁剪语法转换插件,再用 core-js 按需注入 API 垫片,一条构建配置就解决了全量兼容。
具体案例:某金融公司后台管理项目在 webpack 的 babel-loader 上配置 preset-env 的 targets 为 ie 11,并开启 useBuiltIns: 'usage' + core-js@3。改造后 IE11 不再白屏,bundle 体积仅增加约 8%,团队零手工改动完成了历史债清理。
场景二:团队代码库渐进式语法升级
阶段:增量迁移阶段(类似模型迭代中的灰度发布)
解决的问题:团队积累了海量旧风格代码,全量重写风险极高。利用 Babel 的插件开关逐项开启新语法特性,让新旧代码在构建层共存,实现“边写新语法、边保兼容”的平滑过渡,而不是推倒重来。
具体案例:某中台团队从 ES5 风格升级到现代写法,先在构建层用 preset-env 兜底兼容,再引导开发者在新增代码中使用可选链与空值合并。每次上线都经过 Babel 转译验证,三个月完成了 80% 代码的风格迁移。
场景三:跨端框架的多端编译分发
阶段:编译分发阶段(类似模型蒸馏到不同终端)
解决的问题:同一套业务代码要跑在微信小程序、H5、Node 等多种环境,各端语法与 API 支持度参差不齐。通过定制 Babel 插件改写组件树的 AST,将标准 JSX + TS 源码转译为各端运行时 API,配合按需 polyfill 保证 API 层行为一致。
具体案例:Taro 等跨端框架底层依赖 Babel 插件体系,开发者在组件里写标准 JSX + TS,框架通过 Babel 把组件树静态语法树改写成各端可识别的调用,真正实现“一次开发,多端运行”。
Python 演示
# -*- coding: utf-8 -*-"""Mini Babel: transpile arrow function to normal function and show polyfill decision.""" import re TOKEN_RE = re.compile(r'const|=>|\(|\)|\{|\}|[A-Za-z_]\w*|[0-9]+|[,;=+]') def tokenize(src): """Cut source string into token list.""" return TOKEN_RE.findall(src) class Parser: """Mini parser: token stream -> AST.""" def __init__(self, tokens): self.tokens = tokens self.pos = 0 def peek(self): return self.tokens[self.pos] if self.pos < len(self.tokens) else None def take(self): tok = self.peek() self.pos += 1 return tok def parse_program(self): stmts = [] while self.peek(): stmts.append(self.parse_var()) return {"type": "Program", "body": stmts} def parse_var(self): self.take() # eat const name = self.take() self.take() # eat = if self.peek() == '(': return {"type": "ArrowFunc", "name": name, "body": self.parse_arrow()} return {"type": "Const", "name": name, "value": self.take()} def parse_arrow(self): params = [] self.take() # eat ( while self.peek() and self.peek() != ')': tok = self.take() if tok != ',': params.append(tok) self.take() # eat ) self.take() # eat => parts = [] while self.peek() and self.peek() != ';': parts.append(self.take()) self.take() # eat ; return {"params": params, "value": ' '.join(parts)} def transform(ast): """Plugin: rewrite ArrowFunc node into normal function assignment.""" for node in ast["body"]: if node["type"] == "ArrowFunc": params = ', '.join(node["body"]["params"]) node["type"] = "Const" node["value"] = "function(%s) { return %s; }" % (params, node["body"]["value"]) return ast def generate(ast): """Print AST back into code string.""" return "\n".join("var %s = %s;" % (n["name"], n["value"]) for n in ast["body"]) SUPPORT = {"ie11": set(), "chrome80": {"Promise", "Array.prototype.includes"}} def need_polyfill(feature, browser): """Decision: only add polyfill if the browser is missing the feature.""" return feature not in SUPPORT[browser] def main(): src = "const add = (a, b) => a + b;" ast = Parser(tokenize(src)).parse_program() print("source:", src) print("output:", generate(transform(ast))) print("polyfill:", {f: need_polyfill(f, "ie11") for f in ["Promise", "includes"]}) if __name__ == "__main__": main()
return TOKEN_RE.findall(src)正则分词器,等价于 Babel 的词法分析阶段,把源码切成词牌序列return self.tokens[self.pos] if self.pos < len(self.tokens) else None偷看当前词牌但不消费,这是语法分析器的核心操作while self.peek():循环解析每一条语句,直到词牌流耗尽,对应 Program 节点的 body 数组if self.peek() == '(':遇到左括号就识别为箭头函数声明,对应 Babel 对节点类型的判断node["type"] = "Const"插件最核心的一步:改写节点类型,旧节点“箭头函数”变成新节点“普通赋值”params = ', '.join(node["body"]["params"])把参数列表拼成字符串,为生成普通函数做准备"var %s = %s;" % (n["name"], n["value"])代码生成器:把 AST 打印回目标代码字符串return feature not in SUPPORT[browser]polyfill 决策:目标浏览器缺失该特性才返回 Trueif __name__ == "__main__":可执行入口,直接运行这段脚本就能看到完整转译效果
常见误区
误区很多初学者以为配好 Babel 后,Promise、Array.includes 这些新 API 也会被自动转译或补齐。结果项目上线,照样在 IE 里报 "Promise is not defined",一脸茫然。
正解Babel 只负责“语法”转换,比如箭头函数、模板字符串这种写法上的事;Promise 是“运行时能力”,语法解析根本找不到它,必须靠 polyfill(core-js)在代码执行前把函数补进全局。用比喻说:Babel 是翻译官,polyfill 是递手机——翻译官变不出手机,得另请人递。
为何常见:两者都出现在 babel.config.json 里,preset-env 的配置项同时包含 targets 和 useBuiltIns,初学者很容易误以为它们是一套机制。
误区从其他语言转前端的人常会以为,Babel 把 TS 转成 JS 的同时,也会像编译器一样报出类型错误。
正解@babel/preset-typescript 做的事情只有一个:把类型注解剥掉。它从不读类型、不推断类型、不检查对错。类型检查必须靠 tsc --noEmit 独立完成。比喻:校对员(tsc)看原稿挑错,翻译官(Babel)只管翻译,翻译官不负责内容有没有逻辑错误。
为何常见:tsc 本身既能检查也能转译,新人容易把“转译工具”和“类型检查工具”混成一个概念。
误区认为 preset-env 是一个无脑的“全家桶转译器”,代码转得越旧、越稳,兼容性就越好。
正解preset-env 会读取 targets(如 "chrome": "80"),只对目标浏览器不支持的语法做转换。如果目标全是现代浏览器,很多转换根本不会发生。每多一次转换,代码体积和运行开销都会增加。比喻:客人名单决定请哪些翻译,不是每次把所有翻译都喊到场。
为何常见:早期 Babel 时代确实流行 ["es2015", "es2016", "es2017"] 这种全量转译配置,老教程留下的惯性思维。
误区为了追求兼容性,直接把 core-js 全量引入,觉得所有 API 都有了,再也不用担心浏览器报错。
正解全量 polyfill 会让 bundle 增加上百 KB,还会污染全局对象。正确做法是用 useBuiltIns: "usage" + core-js@3 按需注入——只引入代码里用到且目标浏览器缺失的 API;或用 transform-runtime 以模块形式按需注入。比喻:不能把整个五金店都背在身上,只需要把家里缺的那把锤子装上。
为何常见:“缺什么补什么”需要理解浏览器特性检测和构建时分析,初学者为省事选择先全量引入、跑通再说,结果把优化留成了技术债。
面试题
Q1. Babel 转译的三个阶段的正确顺序是?
- A.解析→转换→生成← 正确答案
- B.转换→解析→生成
- C.生成→解析→转换
- D.解析→生成→转换
答案:A。Babel 先解析出语法树,再转换节点,最后生成代码。就像翻译官先拆骨架、再换词、最后开口说话,顺序不能颠倒。B 把转换提前了,D 把生成提前了,都不符合工作流程。
Q2. AST 在 Babel 中的作用是什么?
- A.源码的树状结构骨架← 正确答案
- B.浏览器渲染的接口
- C.打包资源的工具
- D.样式表的简写
答案:A。AST 即抽象语法树,把源码表达成带父子关系的树,Babel 的插件都在树上做改写。B 是渲染引擎的职责,C 是打包工具的职责,D 是 CSS 的缩写,都与 AST 无关。
Q3. 下面哪一类问题无法靠 Babel 语法转换解决?
- A.箭头函数转普通函数
- B.Promise 等新 API 方法← 正确答案
- C.模板字符串拼接
- D.解构赋值展开
答案:B。Babel 擅长转换“写法”。箭头函数、模板字符串、解构赋值都是语法,可以改写。Promise 是运行时 API,语法树里没有它,只能靠 polyfill 补齐——就像“手机”无法靠翻译解释,得直接递工具。
Q4. preset-env 决定启用哪些转换插件的依据是?
- A.插件的安装数量
- B.浏览器目标支持情况← 正确答案
- C.源码的总行数
- D.机器的构建速度
答案:B。preset-env 的核心是 targets:先声明要兼容的浏览器清单,再从候选插件全集里减去目标浏览器已经支持的语法,剩下的才启用。其他三项都与插件选择无关。
Q5. 使用 core-js 实现按需 polyfill 的正确做法是?
- A.useBuiltIns 与 core-js← 正确答案
- B.全量引入所有垫片
- C.手动复制到每个文件
- D.关闭 Babel 转译
答案:A。将 preset-env 的 useBuiltIns 设为 "usage" 并指定 core-js 版本,Babel 会分析代码中用到的 API,结合 targets 只引入缺失的垫片。手动复制不可维护,全量引入体积爆炸,关闭转译直接失去兼容能力。
Q6. 关于 Babel 与 tsc 的关系,哪种说法正确?
- A.Babel 能替代 tsc 全部功能
- B.Babel 转译 tsc 做类型检查← 正确答案
- C.tsc 只打包不检查类型
- D.两者必须同时使用
答案:B。tsc 做了“校对+翻译”两件事,Babel 只做翻译。工程上常用 Babel 转译 + tsc --noEmit 检查。A 错在 Babel 不做类型检查,C 完全相反,D 过于绝对——只用 tsc 也能完成编译。
Q7. transform-runtime 插件的主要作用是?
- A.避免辅助函数重复注入← 正确答案
- B.显著加快编译速度
- C.自动删除源码映射
- D.合并所有 node 模块
答案:A。Babel 转译时会注入辅助函数(helper),每个文件各来一份就会大量重复。transform-runtime 让它们以模块形式统一引用,同时避免全局污染。B、C、D 都不是它的职责范围。
Q8. 关于 Babel 插件与 preset,哪些说法正确?
- A.preset 是插件的有序集合← 正确答案
- B.插件只能单独逐个使用
- C.preset-env 按目标裁剪插件← 正确答案
- D.一个项目只能有一个插件
答案:AC。preset 本质是插件数组,preset-env 会按 targets 做差集裁剪,因此 A、C 正确。B、D 错误:多个插件可以自由组合,preset 也可以多个并存,工程中常见 preset-env 加 transform-runtime 的组合。
Q9. 以下哪些属于 Babel 工作流程中的中间产物或过程?
- A.tokens 词牌序列← 正确答案
- B.AST 抽象语法树← 正确答案
- C.bundle 打包文件
- D.类型诊断报告
答案:AB。词法分析产出的 tokens 和语法分析产出的 AST 都是 Babel 的内部中间产物,插件在 AST 上工作。bundle 是 webpack 等打包器的产物,类型诊断报告是 tsc 的产物,二者都不属于 Babel 转译流程。
Q10. 在 TS + polyfill 的工程化链路中,各工具职责哪些正确?
- A.tsc 负责类型检查← 正确答案
- B.Babel 负责语法转译← 正确答案
- C.core-js 提供运行时垫片← 正确答案
- D.webpack 完全替代编译器
答案:ABC。这条链路各司其职:tsc 做类型校对、Babel 做语法翻译、core-js 补 API 缺口,对应“校对员、翻译官、递工具的人”三个角色。D 错在 webpack 是模块打包器,它依赖 loader 完成转编译,并不能替代编译器。