如何在浏览器编译 TypeScript

1,629 阅读8分钟

image.png

图片来自 Pexels

公众号:JavaScript与编程艺术

背景

为什么会有这个需求?在 GitHub 遇到一些好玩的 TS 代码想直接在浏览器运行,但是浏览器目前并不支持 TS,类型支持尚处于提案阶段。

比如这样一个好玩的在控制台播放视频的小工具 console.video.ts。 因为是 TS,无法直接复制在控制台运行,有个大胆的想法:要是GitHub 能提供一个『编译 TS』的按钮点击复制,直接转成 JS 该多好!

下面是我用浏览器插件实现的效果,GitHub 并未提供。

image.png

要是 GitHub 能有一个类似下图红框中的『Copy as JS』按钮该多好。

解决方案

在 Node.js 中编译 TS 文件大家都知道用 tsc、esbuild 或 babel,那么浏览器是否可以呢?其实浏览器也可以用这三种编译方案。

方案 1:tsc

1 浏览器安装 tsc 编译工具。

安装逻辑其实很简单就是插入一个 script 然后就可以在 window 拿到。

async function installTSCompiler() {
  const typescriptServices = 'https://cdn.jsdelivr.net/npm/typescript@4.6.3/lib/typescriptServices.js'

  return installNpmPackage(typescriptServices)
    .then(() => {
      const ts = window.ts;
      
      if (!ts) { return null }
      
      log(`ts@${ts.version} installed in your console.`);

      return ts;
    });
}

function installNpmPackage(packageSrcOrName) {
  return isURL(packageSrcOrName) ? insertScript(packageSrcOrName) : insertScript(`https://cdn.jsdelivr.net/npm/${packageSrcOrName}`)
}

2 编译 TS

使用 window.ts.transpilewindow.ts.transpileModule

比如我们有 TS 代码:

const foo = (str?: string): number => str?.length ?? 0

既有 const箭头函数又有类型以及可选操作符 ?. 和空值合并运算符 ??

const sourceCode = `const foo = (str?: string): number => str?.length ?? 0`;

// 安装
const tsc = await installTSCompiler();

// 调用 API 编译
const { outputText: js } = tsc.transpileModule(sourceCode, {
  compilerOptions: {
    target: 'esnext'
  }
});

// 输出结果
console.log('编译结果:', js)
var foo = function (str) {
  var _a; 
  return (_a = str === null || str === void 0 ? void 0 : str.length) !== null && _a !== void 0 ? _a : 0;
};

小技巧 💡ts-playground 控制台 已经安装好ts编译器,可以直接编译 ts 代码:

ts.transpile('const str: string = "Hello world"; console.log(str)')

image.png

编译结果

[Compiler] Set compiler options: > Object
index.ts:636 Using TypeScript 5.7.3
index.ts:638 Available globals:
index.ts:639 	window.ts Object
index.ts:640 	window.sandbox Object
index.ts:641 	window.playground Object
index.ts:642 	window.react Object
index.ts:643 	window.reactDOM Object

image.png

编译器已安装

方案 2:esbuild

esbuild 使用 go 编译 TS 是否会更快?我们看看

  1. 同理浏览器安装 esbuild-wasm
await installNpmPackage('esbuild-wasm')
  1. 编译

测试 TS 代码如下:

const foo = (str: string): string => {
  return 'bar';
}

开始编译:

const sourceCode = `const foo = (str: string): string => { return 'bar'; }`

console.time('esbuild.initialize')
// 注意有且仅需一次初始化
await esbuild.initialize({
  wasmURL: 'https://cdn.jsdelivr.net/npm/esbuild-wasm/esbuild.wasm',
})
console.timeEnd('esbuild.initialize') 

console.time('esbuild.transform')
const output = await esbuild.transform(sourceCode, { loader: 'ts' })
console.timeEnd('esbuild.transform')

console.log('编译结果:', output)

wasmURL 这个文件很大 0.14.38 版本 9.3M,最新版本(2024-7-12)0.23.0 11.1M

输出:

esbuild.initialize: 2686.880859375 ms

// 第一次编译 esbuild.transform: 79.719970703125 ms 
esbuild.transform: 2.425048828125 ms

编译结果:
{
    "warnings": [],
    "code": "const foo = (str) => {\n  return \"bar\";\n};\n",
    "map": ""
}

编译结果:

const foo = (str) => {
  return "bar";
};

如果网络有问题卡在 esbuild.initialize,或者耗时需要2.6s+,有种优化方案,可以将耗时优化到100ms左右。

优化 wasm 初始化性能

思路“本地化”:预先下载 cdn.jsdelivr.net/npm/esbuild… 到本地,然后在下载目录执行 http-server(如果你有 Python 可以使用 python -m SimpleHTTPServer 8000)。

npx -y http-server

修改成本地 url:

await esbuild.initialize({
  wasmURL: 'http://localhost/esbuild.wasm',
})

可以看到从 2s 变成了 100ms 左右:

esbuild.initialize: 158.7490234375 ms

方案 3:Babel

1 同理浏览器安装 babel 编译器 @babel/standalone

await installNpmPackage('@babel/standalone')

2 编译

const output = Babel.transform(`const foo = (str?: string): number => str?.length ?? 0`, {
    "babelrc": false,
    "filename": "repl.ts",
    "sourceMap": false,
    "presets": [
        [
            "stage-3",
            {
                "decoratorsVersion": "2023-11",
                "pipelineProposal": "minimal"
            }
        ],
        [
            "typescript",
            {
                "allowDeclareFields": true,
                "allowNamespaces": true
            }
        ]
    ],
    "plugins": [],
    "sourceType": "module",
    "assumptions": {}
});

console.log('编译结果:', output.code)

编译结果:

const foo = str => {
  var _str$length;
  return (_str$length = str === null || str === void 0 ? void 0 : str.length) !== null && _str$length !== void 0 ? _str$length : 0;
};

箭头函数尚不支持编译(需要加载 @babel/plugin-transform-arrow-functions)

const foo = str => {
  return 'bar';
};

比较

1. 可用性

esbuild 有两个问题:

  1. 初始化的 esbuild.wasm 文件太大 9.3M,网络不好几乎等同于该方案不可用。
  2. 因为要初始化加载 wasmURL,而该链接的 host 不属于 github 的 content-security-policy host 之一,故没法在有 csp 的网站上运行比如 github 或 npm。当然你本地是可以的哈。

tsc 和 babel 方案没有类似问题,能在任意网站顺利编译。但都是同步编译,存在阻塞主线程的可能性。但该体验问题倒不是很大,因为单文件 300行以内几乎十几毫秒内可以编译完成,其次因为用户目的很明确,愿意等待。

其他问题: babel 和 esbuild 在浏览器端都有无法编译的语法。

  • esbuild 不支持 bigint
  • bable 不支持装饰器 二者都都会检测重名。

所以从实用性角度来说优先选择 tsc

2. 性能

image.png

来自 www.developerway.com/posts/three…

性能测试,执行 4-5 次,取平均值。预计 esbuild > babel > tsc。

测试样例代码(由 AI 生成):

// 示例:使用ESNext语法的异步函数和现代JavaScript特性

// 1-10: 顶级等待
async function initializeApp() {
  const appConfig = await fetch('/config.json').then(res => res.json());
  console.log('App initialized with:', appConfig);
}

initializeApp();

// 11-20: 空值合并运算符
const config = {
  // ...config properties
  timeout: undefined,
};

const effectiveTimeout = config.timeout ?? 5000;
console.log(`Effective timeout is set to ${effectiveTimeout}ms`);

// 21-30: 可选链
const database = {
  // ...database properties
  config: {
    // ...nested config
  },
};

const maxConnections = database?.config?.maxConnections ?? 10;
console.log(`Max connections set to ${maxConnections}`);

// 31-40: 空值合并运算符和可选链
const user = {
  // ...user properties
  details: {
    // ...details properties
  },
};

const username = user.details?.name ?? 'Guest';
console.log(`Welcome, ${username}!`);

// 41-50: Promise.allSettled (ES2020)
Promise.allSettled([
  fetch('/api/data1'),
  fetch('/api/data2'),
]).then(results => {
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Data ${index + 1} fetched successfully`);
    }
  });
});

// 51-60: Logical Assignment Operators (Proposal)
const isDataLoaded = false;
isDataLoaded ||= fetchData().then(data => {
  console.log('Data loaded:', data);
  return data;
});

// 61-70: Private Fields (Stage 3 Proposal)
class User {
  #username;
  constructor(username) {
    this.#username = username;
  }

  get username() {
    return this.#username;
  }
}

const user1 = new User('Alice');
console.log(user1.username);

// 71-80: Private Methods (Stage 3 Proposal)
class Counter {
  #count = 0;

  increment() {
    this.#count++;
  }

  get value() {
    return this.#count;
  }
}

const counter = new Counter();
counter.increment();
console.log(counter.value);

// 81-90: Class Fields (Stage 3 Proposal)
class Rectangle {
  width = 10;
  height = 5;

  get area() {
    return this.width * this.height;
  }
}

const rectangle = new Rectangle();
console.log(`Area: ${rectangle.area}`);

// 91-100: GlobalThis (ES2020)
console.log('globalThis is', globalThis);

// TypeScript高级语法示例

// 1. 类型注解
let age: number = 25;

// 2. 接口
interface IUser {
  name: string;
  age: number;
}

// 3. 类和类型别名
class Person implements IUser {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}
type User2 = {
  name: string;
  age: number;
};

// 4. 枚举
enum Color {Red, Green, Blue}

// 5. 泛型
function identity<T>(arg: T): T {
  return arg;
}

// 6. 箭头函数
const sum = (a: number, b: number) => a + b;

// 7. 模板字符串
const name = "TypeScript";
console.log(`Hello, ${name}!`);

// 8. 解构赋值
const [first, second] = [1, 2];

// 9. 默认参数值
function greet(name: string = "Guest") {
  console.log(`Hello, ${name}!`);
}

// 10. 剩余参数和扩展运算符
function restArgs(...args: string[]) {}
let arr = [1, 2, ...[3, 4]];

// 11. Promise 和 async/await
async function fetchData() {
  let data = await Promise.resolve("Data");
  console.log(data);
}

// 12. 可选链 (?.) 和 空值合并 (??)
const value = obj?.property ?? "default";

// 13. BigInt - esbuild 浏览器并不支持
const bigNumber = BigInt(1234567890123456789012345678901234567890n);

// 14. 类型守卫
function isString(value: any): value is string {
  return typeof value === 'string';
}

// 15. 装饰器 (仅作为示例,实际中需要装饰器实现)
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log(`Accessing ${propertyKey}`);
}
class MyClass {
  @log // babel 浏览器并不支持
  myMethod() {}
}

// 16. 命名空间
namespace MyNamespace {
  export class MyClass {}
}

// 17. Map 和 Set 数据结构
let map = new Map<string, number>();
let set = new Set<string>();

// 18. WeakMap 和 WeakSet 数据结构
let weakMap = new WeakMap<Node, string>();
let weakSet = new WeakSet<Node>();

// 19. Symbol 和 Symbol.iterator
const mySymbol = Symbol('mySymbol');
const iterable = [1, 2, 3][Symbol.iterator]();

// 20. 逻辑赋值运算符
let a = 0;
a ||= 'default value'; // 如果 a 是 falsy,a 将被赋值为 'default value'

// 使用类型别名和接口
const user1: User2 = { name: "Alice", age: 30 };
const person = new Person("Alice", 30);

// 使用泛型
const typedIdentity = identity<string>("hello");

// 使用类型守卫
if (isString(user2.name)) {
  console.log(`The name is: ${user2.name.toUpperCase()}`);
}

// 调用函数,演示默认参数和解构
greet(); // 使用默认参数

// 调用异步函数
fetchData().catch(console.error);

// 调用带有装饰器的方法
const myClassInstance = new MyClass();
myClassInstance.myMethod();

// 这段代码仅用于演示ESNext语法,并不是一个完整的应用程序。

1. tsc

console.time('tsc')

ts.transpileModule(sourceCode, {
  compilerOptions: {
    target: 'es5'
  }
})

console.timeEnd('tsc')
tsc 编译结果:
// 示例:使用ESNext语法的异步函数和现代JavaScript特性
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
    if (kind === "m") throw new TypeError("Private method is not writable");
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
    return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
    if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
    if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
    return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
    if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
        if (ar || !(i in from)) {
            if (!ar) ar = Array.prototype.slice.call(from, 0, i);
            ar[i] = from[i];
        }
    }
    return to.concat(ar || Array.prototype.slice.call(from));
};
var _a, _b, _c, _d, _e, _f;
var _User_username, _Counter_count;
// 1-10: 顶级等待
function initializeApp() {
    return __awaiter(this, void 0, void 0, function () {
        var appConfig;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, fetch('/config.json').then(function (res) { return res.json(); })];
                case 1:
                    appConfig = _a.sent();
                    console.log('App initialized with:', appConfig);
                    return [2 /*return*/];
            }
        });
    });
}
initializeApp();
// 11-20: 空值合并运算符
var config = {
    // ...config properties
    timeout: undefined,
};
var effectiveTimeout = (_a = config.timeout) !== null && _a !== void 0 ? _a : 5000;
console.log("Effective timeout is set to ".concat(effectiveTimeout, "ms"));
// 21-30: 可选链
var database = {
    // ...database properties
    config: {
    // ...nested config
    },
};
var maxConnections = (_c = (_b = database === null || database === void 0 ? void 0 : database.config) === null || _b === void 0 ? void 0 : _b.maxConnections) !== null && _c !== void 0 ? _c : 10;
console.log("Max connections set to ".concat(maxConnections));
// 31-40: 空值合并运算符和可选链
var user = {
    // ...user properties
    details: {
    // ...details properties
    },
};
var username = (_e = (_d = user.details) === null || _d === void 0 ? void 0 : _d.name) !== null && _e !== void 0 ? _e : 'Guest';
console.log("Welcome, ".concat(username, "!"));
// 41-50: Promise.allSettled (ES2020)
Promise.allSettled([
    fetch('/api/data1'),
    fetch('/api/data2'),
]).then(function (results) {
    results.forEach(function (result, index) {
        if (result.status === 'fulfilled') {
            console.log("Data ".concat(index + 1, " fetched successfully"));
        }
    });
});
// 51-60: Logical Assignment Operators (Proposal)
var isDataLoaded = false;
isDataLoaded || (isDataLoaded = fetchData().then(function (data) {
    console.log('Data loaded:', data);
    return data;
}));
// 61-70: Private Fields (Stage 3 Proposal)
var User = /** @class */ (function () {
    function User(username) {
        _User_username.set(this, void 0);
        __classPrivateFieldSet(this, _User_username, username, "f");
    }
    Object.defineProperty(User.prototype, "username", {
        get: function () {
            return __classPrivateFieldGet(this, _User_username, "f");
        },
        enumerable: false,
        configurable: true
    });
    return User;
}());
_User_username = new WeakMap();
var user1 = new User('Alice');
console.log(user1.username);
// 71-80: Private Methods (Stage 3 Proposal)
var Counter = /** @class */ (function () {
    function Counter() {
        _Counter_count.set(this, 0);
    }
    Counter.prototype.increment = function () {
        var _a;
        __classPrivateFieldSet(this, _Counter_count, (_a = __classPrivateFieldGet(this, _Counter_count, "f"), _a++, _a), "f");
    };
    Object.defineProperty(Counter.prototype, "value", {
        get: function () {
            return __classPrivateFieldGet(this, _Counter_count, "f");
        },
        enumerable: false,
        configurable: true
    });
    return Counter;
}());
_Counter_count = new WeakMap();
var counter = new Counter();
counter.increment();
console.log(counter.value);
// 81-90: Class Fields (Stage 3 Proposal)
var Rectangle = /** @class */ (function () {
    function Rectangle() {
        this.width = 10;
        this.height = 5;
    }
    Object.defineProperty(Rectangle.prototype, "area", {
        get: function () {
            return this.width * this.height;
        },
        enumerable: false,
        configurable: true
    });
    return Rectangle;
}());
var rectangle = new Rectangle();
console.log("Area: ".concat(rectangle.area));
// 91-100: GlobalThis (ES2020)
console.log('globalThis is', globalThis);
// TypeScript高级语法示例
// 1. 类型注解
var age = 25;
// 3. 类和类型别名
var Person = /** @class */ (function () {
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    return Person;
}());
// 4. 枚举
var Color;
(function (Color) {
    Color[Color["Red"] = 0] = "Red";
    Color[Color["Green"] = 1] = "Green";
    Color[Color["Blue"] = 2] = "Blue";
})(Color || (Color = {}));
// 5. 泛型
function identity(arg) {
    return arg;
}
// 6. 箭头函数
var sum = function (a, b) { return a + b; };
// 7. 模板字符串
var name = "TypeScript";
console.log("Hello, ".concat(name, "!"));
// 8. 解构赋值
var _g = [1, 2], first = _g[0], second = _g[1];
// 9. 默认参数值
function greet(name) {
    if (name === void 0) { name = "Guest"; }
    console.log("Hello, ".concat(name, "!"));
}
// 10. 剩余参数和扩展运算符
function restArgs() {
    var args = [];
    for (var _i = 0; _i < arguments.length; _i++) {
        args[_i] = arguments[_i];
    }
}
var arr = __spreadArray([1, 2], [3, 4], false);
// 11. Promise 和 async/await
function fetchData() {
    return __awaiter(this, void 0, void 0, function () {
        var data;
        return __generator(this, function (_a) {
            switch (_a.label) {
                case 0: return [4 /*yield*/, Promise.resolve("Data")];
                case 1:
                    data = _a.sent();
                    console.log(data);
                    return [2 /*return*/];
            }
        });
    });
}
// 12. 可选链 (?.) 和 空值合并 (??)
var value = (_f = obj === null || obj === void 0 ? void 0 : obj.property) !== null && _f !== void 0 ? _f : "default";
// 13. BigInt
var bigNumber = BigInt(1234567890123456789012345678901234567890n);
// 14. 类型守卫
function isString(value) {
    return typeof value === 'string';
}
// 15. 装饰器 (仅作为示例,实际中需要装饰器实现)
function log(target, propertyKey, descriptor) {
    console.log("Accessing ".concat(propertyKey));
}
var MyClass = /** @class */ (function () {
    function MyClass() {
    }
    MyClass.prototype.myMethod = function () { };
    __decorate([
        log
    ], MyClass.prototype, "myMethod", null);
    return MyClass;
}());
// 16. 命名空间
var MyNamespace;
(function (MyNamespace) {
    var MyClass = /** @class */ (function () {
        function MyClass() {
        }
        return MyClass;
    }());
    MyNamespace.MyClass = MyClass;
})(MyNamespace || (MyNamespace = {}));
// 17. Map 和 Set 数据结构
var map = new Map();
var set = new Set();
// 18. WeakMap 和 WeakSet 数据结构
var weakMap = new WeakMap();
var weakSet = new WeakSet();
// 19. Symbol 和 Symbol.iterator
var mySymbol = Symbol('mySymbol');
var iterable = [1, 2, 3][Symbol.iterator]();
// 20. 逻辑赋值运算符
var a = 0;
a || (a = 'default value'); // 如果 a 是 falsy,a 将被赋值为 'default value'
// 使用类型别名和接口
var user = { name: "Alice", age: 30 };
var person = new Person("Alice", 30);
// 使用泛型
var typedIdentity = identity("hello");
// 使用类型守卫
if (isString(user.name)) {
    console.log("The name is: ".concat(user.name.toUpperCase()));
}
// 调用函数,演示默认参数和解构
greet(); // 使用默认参数
// 调用异步函数
fetchData().catch(console.error);
// 调用带有装饰器的方法
var myClassInstance = new MyClass();
myClassInstance.myMethod();
// 这段代码仅用于演示ESNext语法,并不是一个完整的应用程序。

tsc 耗时数据

#ms
114.114013671875
213.0849609375
312.35009765625
411.89208984375

平均值:12.86029052734375

2. esbuild

console.time('esbuild.transform')
await esbuild.transform(sourceCode2, { loader: 'ts', target: 'es6' }) // 浏览器还不支持编译 es5
console.timeEnd('esbuild.transform')
esbuild 编译结果
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __knownSymbol = (name2, symbol) => (symbol = Symbol[name2]) ? symbol : Symbol.for("Symbol." + name2);
var __typeError = (msg) => {
  throw TypeError(msg);
};
var __defNormalProp = (obj2, key, value2) => key in obj2 ? __defProp(obj2, key, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj2[key] = value2;
var __name = (target, value2) => __defProp(target, "name", { value: value2, configurable: true });
var __decoratorStart = (base) => {
  var _a2;
  return [, , , __create((_a2 = base == null ? void 0 : base[__knownSymbol("metadata")]) != null ? _a2 : null)];
};
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
var __decoratorContext = (kind, name2, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name: name2, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
var __runInitializers = (array, flags, self, value2) => {
  for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) flags & 1 ? fns[i].call(self) : value2 = fns[i].call(self, value2);
  return value2;
};
var __decorateElement = (array, flags, name2, decorators, target, extra) => {
  var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
  var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
  var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
  var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name2]() {
    return __privateGet(this, extra);
  }, set [name2](x) {
    return __privateSet(this, extra, x);
  } }, name2));
  k ? p && k < 4 && __name(extra, (k > 2 ? "set " : k > 1 ? "get " : "") + name2) : __name(target, name2);
  for (var i = decorators.length - 1; i >= 0; i--) {
    ctx = __decoratorContext(k, name2, done = {}, array[3], extraInitializers);
    if (k) {
      ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name2 in x };
      if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name2];
      if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name2] = y;
    }
    it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
    if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
    else if (typeof it !== "object" || it === null) __typeError("Object expected");
    else __expectFn(fn = it.get) && (desc.get = fn), __expectFn(fn = it.set) && (desc.set = fn), __expectFn(fn = it.init) && initializers.unshift(fn);
  }
  return k || __decoratorMetadata(array, target), desc && __defProp(target, name2, desc), p ? k ^ 4 ? extra : desc : target;
};
var __publicField = (obj2, key, value2) => __defNormalProp(obj2, typeof key !== "symbol" ? key + "" : key, value2);
var __accessCheck = (obj2, member, msg) => member.has(obj2) || __typeError("Cannot " + msg);
var __privateIn = (member, obj2) => Object(obj2) !== obj2 ? __typeError('Cannot use the "in" operator on this value') : member.has(obj2);
var __privateGet = (obj2, member, getter) => (__accessCheck(obj2, member, "read from private field"), getter ? getter.call(obj2) : member.get(obj2));
var __privateAdd = (obj2, member, value2) => member.has(obj2) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj2) : member.set(obj2, value2);
var __privateSet = (obj2, member, value2, setter) => (__accessCheck(obj2, member, "write to private field"), setter ? setter.call(obj2, value2) : member.set(obj2, value2), value2);
var __privateMethod = (obj2, member, method) => (__accessCheck(obj2, member, "access private method"), method);
var __privateWrapper = (obj2, member, setter, getter) => ({
  set _(value2) {
    __privateSet(obj2, member, value2, setter);
  },
  get _() {
    return __privateGet(obj2, member, getter);
  }
});
var __async = (__this, __arguments, generator) => {
  return new Promise((resolve, reject) => {
    var fulfilled = (value2) => {
      try {
        step(generator.next(value2));
      } catch (e) {
        reject(e);
      }
    };
    var rejected = (value2) => {
      try {
        step(generator.throw(value2));
      } catch (e) {
        reject(e);
      }
    };
    var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
    step((generator = generator.apply(__this, __arguments)).next());
  });
};
var _a, _b, _c, _d, _e, _username, _count, _f, _myMethod_dec, _init;
function initializeApp() {
  return __async(this, null, function* () {
    const appConfig = yield fetch("/config.json").then((res) => res.json());
    console.log("App initialized with:", appConfig);
  });
}
initializeApp();
const config = {
  // ...config properties
  timeout: void 0
};
const effectiveTimeout = (_a = config.timeout) != null ? _a : 5e3;
console.log(`Effective timeout is set to ${effectiveTimeout}ms`);
const database = {
  // ...database properties
  config: {
    // ...nested config
  }
};
const maxConnections = (_c = (_b = database == null ? void 0 : database.config) == null ? void 0 : _b.maxConnections) != null ? _c : 10;
console.log(`Max connections set to ${maxConnections}`);
const user = {
  // ...user properties
  details: {
    // ...details properties
  }
};
const username = (_e = (_d = user.details) == null ? void 0 : _d.name) != null ? _e : "Guest";
console.log(`Welcome, ${username}!`);
Promise.allSettled([
  fetch("/api/data1"),
  fetch("/api/data2")
]).then((results) => {
  results.forEach((result, index) => {
    if (result.status === "fulfilled") {
      console.log(`Data ${index + 1} fetched successfully`);
    }
  });
});
const isDataLoaded = false;
isDataLoaded || (isDataLoaded = fetchData().then((data) => {
  console.log("Data loaded:", data);
  return data;
}));
class User {
  constructor(username2) {
    __privateAdd(this, _username);
    __privateSet(this, _username, username2);
  }
  get username() {
    return __privateGet(this, _username);
  }
}
_username = new WeakMap();
const user1 = new User("Alice");
console.log(user1.username);
class Counter {
  constructor() {
    __privateAdd(this, _count, 0);
  }
  increment() {
    __privateWrapper(this, _count)._++;
  }
  get value() {
    return __privateGet(this, _count);
  }
}
_count = new WeakMap();
const counter = new Counter();
counter.increment();
console.log(counter.value);
class Rectangle {
  constructor() {
    __publicField(this, "width", 10);
    __publicField(this, "height", 5);
  }
  get area() {
    return this.width * this.height;
  }
}
const rectangle = new Rectangle();
console.log(`Area: ${rectangle.area}`);
console.log("globalThis is", globalThis);
let age = 25;
class Person {
  constructor(name2, age2) {
    __publicField(this, "name");
    __publicField(this, "age");
    this.name = name2;
    this.age = age2;
  }
}
var Color = /* @__PURE__ */ ((Color2) => {
  Color2[Color2["Red"] = 0] = "Red";
  Color2[Color2["Green"] = 1] = "Green";
  Color2[Color2["Blue"] = 2] = "Blue";
  return Color2;
})(Color || {});
function identity(arg) {
  return arg;
}
const sum = (a2, b) => a2 + b;
const name = "TypeScript";
console.log(`Hello, ${name}!`);
const [first, second] = [1, 2];
function greet(name2 = "Guest") {
  console.log(`Hello, ${name2}!`);
}
function restArgs(...args) {
}
let arr = [1, 2, ...[3, 4]];
function fetchData() {
  return __async(this, null, function* () {
    let data = yield Promise.resolve("Data");
    console.log(data);
  });
}
const value = (_f = obj == null ? void 0 : obj.property) != null ? _f : "default";
function isString(value2) {
  return typeof value2 === "string";
}
function log(target, propertyKey, descriptor) {
  console.log(`Accessing ${propertyKey}`);
}
_myMethod_dec = [log];
class MyClass {
  constructor() {
    __runInitializers(_init, 5, this);
  }
  myMethod() {
  }
}
_init = __decoratorStart(null);
__decorateElement(_init, 1, "myMethod", _myMethod_dec, MyClass);
__decoratorMetadata(_init, MyClass);
var MyNamespace;
((MyNamespace2) => {
  class MyClass2 {
  }
  MyNamespace2.MyClass = MyClass2;
})(MyNamespace || (MyNamespace = {}));
let map = /* @__PURE__ */ new Map();
let set = /* @__PURE__ */ new Set();
let weakMap = /* @__PURE__ */ new WeakMap();
let weakSet = /* @__PURE__ */ new WeakSet();
const mySymbol = Symbol("mySymbol");
const iterable = [1, 2, 3][Symbol.iterator]();
let a = 0;
a || (a = "default value");
const user2 = { name: "Alice", age: 30 };
const person = new Person("Alice", 30);
const typedIdentity = identity("hello");
if (isString(user2.name)) {
  console.log(`The name is: ${user2.name.toUpperCase()}`);
}
greet();
fetchData().catch(console.error);
const myClassInstance = new MyClass();
myClassInstance.myMethod();

esbuild 耗时数据

#ms
110.6298828125
26.633056640625
36.365966796875
46.963134765625
56.636962890625

平均值:7.44580078125

3. babel

console.time('Babel.transform')
    
Babel.transform(sourceCode, ...)
    
console.timeEnd('Babel.transform')
babel 编译结果
var _config$timeout, _database$config$maxC, _database$config, _user$details$name, _user$details, _obj$property, _obj;
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
// 示例:使用ESNext语法的异步函数和现代JavaScript特性

// 1-10: 顶级等待
async function initializeApp() {
  const appConfig = await fetch('/config.json').then(res => res.json());
  console.log('App initialized with:', appConfig);
}
initializeApp();

// 11-20: 空值合并运算符
const config = {
  // ...config properties
  timeout: undefined
};
const effectiveTimeout = (_config$timeout = config.timeout) !== null && _config$timeout !== void 0 ? _config$timeout : 5000;
console.log(`Effective timeout is set to ${effectiveTimeout}ms`);

// 21-30: 可选链
const database = {
  // ...database properties
  config: {
    // ...nested config
  }
};
const maxConnections = (_database$config$maxC = database === null || database === void 0 || (_database$config = database.config) === null || _database$config === void 0 ? void 0 : _database$config.maxConnections) !== null && _database$config$maxC !== void 0 ? _database$config$maxC : 10;
console.log(`Max connections set to ${maxConnections}`);

// 31-40: 空值合并运算符和可选链
const user = {
  // ...user properties
  details: {
    // ...details properties
  }
};
const username = (_user$details$name = (_user$details = user.details) === null || _user$details === void 0 ? void 0 : _user$details.name) !== null && _user$details$name !== void 0 ? _user$details$name : 'Guest';
console.log(`Welcome, ${username}!`);

// 41-50: Promise.allSettled (ES2020)
Promise.allSettled([fetch('/api/data1'), fetch('/api/data2')]).then(results => {
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Data ${index + 1} fetched successfully`);
    }
  });
});

// 51-60: Logical Assignment Operators (Proposal)
const isDataLoaded = false;
isDataLoaded || (isDataLoaded = fetchData().then(data => {
  console.log('Data loaded:', data);
  return data;
}));

// 61-70: Private Fields (Stage 3 Proposal)
var _username = /*#__PURE__*/new WeakMap();
class User {
  constructor(username) {
    _classPrivateFieldInitSpec(this, _username, void 0);
    _classPrivateFieldSet(_username, this, username);
  }
  get username() {
    return _classPrivateFieldGet(_username, this);
  }
}
const user1 = new User('Alice');
console.log(user1.username);

// 71-80: Private Methods (Stage 3 Proposal)
var _count = /*#__PURE__*/new WeakMap();
class Counter {
  constructor() {
    _classPrivateFieldInitSpec(this, _count, 0);
  }
  increment() {
    var _this$count, _this$count2;
    _classPrivateFieldSet(_count, this, (_this$count = _classPrivateFieldGet(_count, this), _this$count2 = _this$count++, _this$count)), _this$count2;
  }
  get value() {
    return _classPrivateFieldGet(_count, this);
  }
}
const counter = new Counter();
counter.increment();
console.log(counter.value);

// 81-90: Class Fields (Stage 3 Proposal)
class Rectangle {
  constructor() {
    _defineProperty(this, "width", 10);
    _defineProperty(this, "height", 5);
  }
  get area() {
    return this.width * this.height;
  }
}
const rectangle = new Rectangle();
console.log(`Area: ${rectangle.area}`);

// 91-100: GlobalThis (ES2020)
console.log('globalThis is', globalThis);

// TypeScript高级语法示例

// 1. 类型注解
let age = 25;

// 2. 接口

// 3. 类和类型别名
class Person {
  constructor(name, age) {
    _defineProperty(this, "name", void 0);
    _defineProperty(this, "age", void 0);
    this.name = name;
    this.age = age;
  }
}
// 4. 枚举
var Color = /*#__PURE__*/function (Color) {
  Color[Color["Red"] = 0] = "Red";
  Color[Color["Green"] = 1] = "Green";
  Color[Color["Blue"] = 2] = "Blue";
  return Color;
}(Color || {}); // 5. 泛型
function identity(arg) {
  return arg;
}

// 6. 箭头函数
const sum = (a, b) => a + b;

// 7. 模板字符串
const name = "TypeScript";
console.log(`Hello, ${name}!`);

// 8. 解构赋值
const [first, second] = [1, 2];

// 9. 默认参数值
function greet(name = "Guest") {
  console.log(`Hello, ${name}!`);
}

// 10. 剩余参数和扩展运算符
function restArgs(...args) {}
let arr = [1, 2, ...[3, 4]];

// 11. Promise 和 async/await
async function fetchData() {
  let data = await Promise.resolve("Data");
  console.log(data);
}

// 12. 可选链 (?.) 和 空值合并 (??)
const value = (_obj$property = (_obj = obj) === null || _obj === void 0 ? void 0 : _obj.property) !== null && _obj$property !== void 0 ? _obj$property : "default";

// 13. BigInt
const bigNumber = BigInt(1234567890123456789012345678901234567890n);

// 14. 类型守卫
function isString(value) {
  return typeof value === 'string';
}

// 15. 装饰器 (仅作为示例,实际中需要装饰器实现)
// function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
//   console.log(`Accessing ${propertyKey}`);
// }
class MyClass {
  // @log
  myMethod() {}
}

// 16. 命名空间
let MyNamespace;
(function (_MyNamespace) {
  class MyClass {}
  _MyNamespace.MyClass = MyClass;
})(MyNamespace || (MyNamespace = {})); // 17. Map 和 Set 数据结构
let map = new Map();
let set = new Set();

// 18. WeakMap 和 WeakSet 数据结构
let weakMap = new WeakMap();
let weakSet = new WeakSet();

// 19. Symbol 和 Symbol.iterator
const mySymbol = Symbol('mySymbol');
const iterable = [1, 2, 3][Symbol.iterator]();

// 20. 逻辑赋值运算符
let a = 0;
a || (a = 'default value'); // 如果 a 是 falsy,a 将被赋值为 'default value'

// 使用类型别名和接口
const user2 = {
  name: "Alice",
  age: 30
};
const person = new Person("Alice", 30);

// 使用泛型
const typedIdentity = identity("hello");

// 使用类型守卫
if (isString(user2.name)) {
  console.log(`The name is: ${user2.name.toUpperCase()}`);
}

// 调用函数,演示默认参数和解构
greet(); // 使用默认参数

// 调用异步函数
fetchData().catch(console.error);

// 调用带有装饰器的方法
const myClassInstance = new MyClass();
myClassInstance.myMethod();

// 这段代码仅用于演示ESNext语法,并不是一个完整的应用程序。

babel 耗时数据

#ms
114.01416015625
212.88720703125
311.2509765625
412.955078125
511.85400390625

平均值:12.59228515625

注意:

babel 并未编译箭头函数和模板字符串,这些需要加载对应插件,所以实际这个时间应该更长一些。

性能总结

性能:esbuild (7.44580078125) 快于 babel (12.59228515625) 快于 tsc (12.86029052734375)。

参考