20250725-TypeScript_和构建工具(Vite、Webpack等)中构建目标详解

原文摘要

在前端项目中,常常会在 TypeScript、Vite、Webpack、Babel 等工具中看到类似如下配置项:

"target": "es2020"

这些配置中的 "es5""es6""es2020""esnext" 是 ECMAScript 的不同版本标识,决定了构建产物所使用的语法标准。本篇文章将系统梳理这些标准的区别,并深入讲解:

  • 各版本的差异
  • tsconfig.json 中的 target 与构建工具中 target 的关系
  • 如何配置 polyfill 以支持旧浏览器

各版本 ECMAScript 的语法差异

构建目标等价标准发布年份主要新特性
es5ECMAScript 52009严格模式、getter/setter、Array 方法如 forEachmap
es6es2015ECMAScript 62015let/const、箭头函数、模块导入导出、Promise、类、解构
es2016ECMAScript 72016Array.prototype.includes、指数运算符 **
es2017ECMAScript 82017async/awaitObject.entries/values
es2018ECMAScript 92018异步迭代器、命名捕获组、Promise.finally
es2019ECMAScript 102019flatMap、可选 catch 参数、Object.fromEntries
es2020ECMAScript 112020可选链 ?.、空值合并 ??BigIntPromise.allSettled
es2021ECMAScript 122021逻辑赋值运算符、字符串 replaceAllWeakRef
es2022ECMAScript 132022类字段、顶级 await、.at() 方法
esnext最新提案--表示“构建时支持的最新语法”,可能包含实验特性

TypeScript tsconfig.json 中的 target

{
  "compilerOptions": {
    "target": "es2020"
  }
}

target 控制 TypeScript 输出的 JavaScript 使用的 语法级别

  • ✅ 是否保留 async/await
  • ✅ 是否保留箭头函数
  • ✅ 是否使用模块语法 import/export
  • ❌ 不影响内置 API,如 Promise.allSettled 是否存在(需另加 polyfill)

构建工具中的 target(Vite、esbuild、Webpack)

Vite / esbuild 示例

export default defineConfig({
  build: {
    target: 'es2020'
  }
})
  • 控制构建产物保留的语法,如 ?.??import.meta
  • 不会自动插入 polyfill(如 Promise.allSettledBigInt

Webpack + Babel 示例

presets: [
  ['@babel/preset-env', {
    targets: '> 0.25%, not dead',
    useBuiltIns: 'usage',
    corejs: 3
  }]
]
  • Babel 会根据目标环境:
    • 降级语法
    • 自动插入 polyfill(结合 core-js

TypeScript target 与构建工具 target 的关系

项目TypeScript target构建工具 target
控制语法降级✅ 是✅ 是
控制 API 支持❌ 否❌ 否(需 polyfill)
控制代码压缩语法❌ 否✅ 是(如 Terser)
控制 tree-shaking❌ 否✅ 是(现代语法更利于优化)

推荐配置建议

TypeScript 的 target 不要高于构建工具的 target

否则可能出现:

  • 构建工具无法解析新语法,导致构建失败或运行时报错。
  • esbuild 不会再对降级后的语法进行优化,导致文件体积变大。
场景tsconfig.json 中 target构建工具中 target
兼容 IE11es5es5
支持现代浏览器es2020es2020
Electron/Node.js 环境es2022es2022
尽可能现代(实验)esnextesnext

Vite 和 esbuild 中如何引入 polyfill?

构建工具默认不插入 polyfill

Vite 使用 esbuild,只负责语法转换,不会自动处理以下情况:

Promise.allSettled([...]);
Object.fromEntries(...);
BigInt(123);

方法一:手动引入 core-js polyfill(推荐)

// main.ts
import 'core-js/features/promise/all-settled';
import 'core-js/features/object/from-entries';
import 'core-js/features/array/flat-map';

适用于只 polyfill 项目实际用到的部分,构建体积更小、控制更精细

方法二:通过 Babel 插入 polyfill(兼容性优先)

// babel.config.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}

并在 vite.config.ts 中接入 Babel 插件:

import legacy from '@vitejs/plugin-legacy';

export default defineConfig({
  plugins: [
    legacy({
      targets: ['defaults', 'not IE 11']
    })
  ]
});

方法三:使用 polyfill.io CDN(公网项目)

<script src="https://polyfill.io/v3/polyfill.min.js?features=default,Promise.allSettled"></script>

特点:

  • 根据浏览器 User-Agent 自动加载缺失的 polyfill
  • 不适用于内网、Electron、App 内嵌 WebView 等场景

polyfill 方案对比总结

方法优点缺点适用场景
手动引入 core-js控制精细、体积小需手动维护大多数项目
Babel 插入 polyfill自动处理、完整兼容构建慢、体积大兼容性要求高的公共项目
polyfill.io CDN动态按需加载依赖网络、不适合私有部署对公网用户兼容老设备

总结

  • target 配置决定构建产物所使用的 JS 语法版本。
  • tsconfig.jsontarget 控制 TypeScript 输出的语法;构建工具(Vite/Webpack)的 target 控制最终产物保留哪些语法。
  • 若需要兼容低版本浏览器,还需手动或通过 Babel 插入 polyfill。
  • 建议保持 TypeScript 的 target ≤ 构建工具的 target
  • 在现代浏览器为主的项目中,推荐设置为 es2020

原文链接

进一步信息揣测

  • target配置的隐藏风险:设置esnext可能导致生产环境报错,因其包含未标准化的实验性语法(如装饰器提案曾多次变更),需搭配@babel/plugin-proposal-*插件使用,否则线上崩溃风险极高。
  • TypeScript的target陷阱:即使target设为es5,TypeScript仍可能输出ES6+语法(如class),需额外配置"downlevelIteration": true才能完全降级,否则旧浏览器会报错。
  • Babel的useBuiltIns潜规则usage模式看似智能,但实际可能漏转译node_modules内的第三方库代码,导致低版本浏览器白屏,需手动在Webpack中配置include覆盖所有依赖。
  • Vite的语法保留黑盒:Vite默认不处理语法降级(如?.),即使target: 'es5'也仅影响模块格式,必须显式引入@vitejs/plugin-legacy才能兼容旧浏览器。
  • Core-js版本暗坑corejs: 3才能支持ES2020+的API(如Promise.allSettled),但许多教程仍用已废弃的corejs: 2,导致项目上线后部分功能异常。
  • 浏览器列表的玄学匹配targets: '> 0.25%'可能包含某些特定区域浏览器(如中国UC浏览器),需结合not ie <= 11等条件精确控制,否则polyfill体积会暴增。
  • Webpack的target链式反应:当同时使用ts-loaderbabel-loader时,若两者target不一致(如TS输出ES2020而Babel转ES5),会导致重复编译或转译遗漏,最佳实践是让TS输出ESNext并由Babel统一处理。
  • ESM与CJS的隐形冲突target: 'es2020'生成的纯ESM代码在旧版Node.js(<12)中直接运行会报错,需通过Webpack额外输出CJS格式或配置type: "module"
  • 构建工具的默认行为差异:Webpack默认会拆分polyfill到单独chunk,而Vite需要手动配置@vitejs/plugin-legacypolyfills选项,否则所有polyfill会打包进主文件。
  • 动态导入的兼容性盲区:即使配置target: 'es5',动态导入(import())仍需额外@babel/plugin-syntax-dynamic-import插件支持,否则Webpack会直接报错。