# jsesc-es

<div align="center">

[![npm version](https://img.shields.io/npm/v/jsesc-es.svg)](https://www.npmjs.com/package/jsesc-es) [![npm downloads](https://img.shields.io/npm/dm/jsesc-es.svg)](https://www.npmjs.com/package/jsesc-es) [![jsdelivr](https://data.jsdelivr.com/v1/package/npm/jsesc-es/badge)](https://www.jsdelivr.com/package/npm/jsesc-es) [![unpkg](https://img.shields.io/badge/unpkg-jsesc--es-blue.svg)](https://unpkg.com/jsesc-es/) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) [![ES Modules](https://img.shields.io/badge/ES-Modules-brightgreen.svg)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

**[English](README.md)** | **简体中文**

</div>

> 基于流行的 [jsesc](https://github.com/mathiasbynens/jsesc) 库，使用现代 TypeScript + ESM 重构，100% API 兼容

给定一些数据，_jsesc-es_ 返回该数据的字符串化表示。jsesc-es 类似于 `JSON.stringify()`，但有以下不同：

1. 默认情况下输出 JavaScript 而不是 [JSON](#json)，支持 ES6 map 和 set 等数据结构；
2. 提供[多种选项](#api)来自定义输出；
3. 默认情况下输出 [ASCII 安全](#minimal)，在需要时使用[转义序列](https://mathiasbynens.be/notes/javascript-escapes)；
4. **新特性**：完整的 TypeScript 支持和全面的类型定义；
5. **新特性**：原生 ES 模块，支持 tree-shaking；
6. **新特性**：现代开发工具链和改进的性能。

对于任何输入，jsesc-es 都会生成最短的有效可打印 ASCII 输出。[这里有一个在线演示。](https://mothereff.in/js-escapes)

jsesc-es 的输出可以用来替代 `JSON.stringify` 的输出，以避免[乱码](https://en.wikipedia.org/wiki/Mojibake)和其他编码问题，甚至可以[避免错误](https://twitter.com/annevk/status/380000829643571200)，当向 JavaScript 解析器或 UTF-8 编码器传递 JSON 格式数据（可能包含 U+2028 行分隔符、U+2029 段落分隔符或[孤立代理](https://esdiscuss.org/topic/code-points-vs-unicode-scalar-values#content-14)）时。

## ✨ jsesc-es 的新特性

### 🔥 TypeScript 优先

- **内置类型定义** - 无需 `@types/jsesc`
- **完整类型安全** 和全面的 `JsescOptions` 接口
- **更好的 IDE 支持** 包括 IntelliSense 和自动补全
- **编译时类型安全选项验证**

### 📦 现代模块系统

- **原生 ES 模块** 支持 tree-shaking
- **多种导入样式** - 默认导入、命名导入或混合导入
- **CommonJS 兼容性** 用于传统项目
- **优化的包大小** 配合现代构建工具

### ⚡ 增强性能

- **改进的函数处理** 更好的类型检测
- **优化的转义逻辑** 适用于常见用例
- **现代 JavaScript 特性** 提供更好的性能
- **相比原版减少的包开销**

### 🛠️ 开发体验

- **现代工具链** 使用 Vitest、tsdown 和 pnpm
- **更好的错误消息** 集成 TypeScript
- **全面的测试覆盖** 包含类型检查
- **积极维护** 定期更新

## 安装

```bash
# pnpm（推荐）- https://pnpm.io/
pnpm add jsesc-es

# yarn - https://yarnpkg.com/
yarn add jsesc-es

# npm - https://www.npmjs.com/
npm install jsesc-es

# 或其他运行时如...

# bun - https://bun.sh/
bun add jsesc-es

# deno - https://deno.land/
deno add jsesc-es
```

## 使用方法

### ES 模块（推荐）

```typescript

// 默认导入（最常见）
import jsesc from 'jsesc-es'

// 命名导入
import { jsesc } from 'jsesc-es'

// 带类型导入
import type { JsescOptions } from 'jsesc-es'
import jsesc from 'jsesc-es'

// 导入版本信息
import { version } from 'jsesc-es'

// 混合导入（不推荐）
import jsesc, { version } from 'jsesc-es'
```

### TypeScript 使用

```typescript
import type { JsescOptions } from 'jsesc-es'
import jsesc from 'jsesc-es'

// 类型安全的选项
const options: JsescOptions = {
  quotes: 'single',
  wrap: true,
  es6: true,
  minimal: false
}

// 类型安全的函数使用
function escapeForHTML(input: string): string {
  return jsesc(input, {
    quotes: 'double',
    wrap: true,
    isScriptContext: true
  })
}

// 类型安全的配置对象
const configs = {
  json: { json: true } as JsescOptions,
  minimal: { minimal: true } as JsescOptions,
  es6: { es6: true, quotes: 'backtick' } as JsescOptions
}
```

### CommonJS（传统支持）

```javascript
// CommonJS require（仍然支持）
const jsesc = require('jsesc-es')

// 动态导入（现代替代方案）
const { jsesc } = await import('jsesc-es')
```

### Node.js ESM

确保您的 `package.json` 包含：

```json
{
  "type": "module"
}
```

或使用 `.mjs` 文件扩展名：

```javascript
// app.mjs
import jsesc from 'jsesc-es'
```

## API

### `jsesc(value, options?)`

此函数接受一个值并返回该值的转义版本，其中任何非可打印 ASCII 符号的字符都使用最短可能（但有效的）[JavaScript 字符串转义序列](https://mathiasbynens.be/notes/javascript-escapes)进行转义。第一个支持的值类型是字符串：

```js
jsesc('Ich ♥ Bücher')
// → 'Ich \\u2665 B\\xFCcher'

jsesc('foo 𝌆 bar')
// → 'foo \\uD834\\uDF06 bar'
```

除了字符串，`value` 也可以是数组、对象、map、set、数字、BigInt 或 buffer。在这种情况下，`jsesc` 返回值的字符串化版本，其中任何非可打印 ASCII 符号的字符都以相同方式转义。

```js
// 转义数组
jsesc([
  'Ich ♥ Bücher',
  'foo 𝌆 bar'
])
// → '[\'Ich \\u2665 B\\xFCcher\',\'foo \\uD834\\uDF06 bar\']'

// 转义对象
jsesc({
  'Ich ♥ Bücher': 'foo 𝌆 bar'
})
// → '{\'Ich \\u2665 B\\xFCcher\':\'foo \\uD834\\uDF06 bar\'}'
```

### 配置选项

可选的 `options` 参数接受一个具有以下选项的对象。在 TypeScript 中，使用 `JsescOptions` 类型获得完整的类型安全：

```typescript
import type { JsescOptions } from 'jsesc-es'

const options: JsescOptions = {
  // 您的选项在这里，享有完整的 IntelliSense 支持
}
```

#### `quotes`

`quotes` 选项的默认值是 `'single'`。这意味着输入字符串中出现的任何 `'` 都被转义为 `\'`，以便输出可以在用单引号包裹的字符串字面量中使用。

```js
jsesc('`Lorem` ipsum "dolor" sit \'amet\' etc.')
// → 'Lorem ipsum "dolor" sit \\\'amet\\\' etc.'

jsesc('`Lorem` ipsum "dolor" sit \'amet\' etc.', {
  quotes: 'single'
})
// → '`Lorem` ipsum "dolor" sit \\\'amet\\\' etc.'
```

如果您想将输出用作用双引号包裹的字符串字面量的一部分，请将 `quotes` 选项设置为 `'double'`。

```js
jsesc('`Lorem` ipsum "dolor" sit \'amet\' etc.', {
  quotes: 'double'
})
// → '`Lorem` ipsum \\"dolor\\" sit \'amet\' etc.'
```

如果您想将输出用作模板字面量（即用反引号包裹）的一部分，请将 `quotes` 选项设置为 `'backtick'`。

```js
jsesc('`Lorem` ipsum "dolor" sit \'amet\' etc.', {
  quotes: 'backtick'
})
// → '\\`Lorem\\` ipsum "dolor" sit \'amet\' etc.'
```

#### `numbers`

`numbers` 选项的默认值是 `'decimal'`。这意味着任何数值都使用十进制整数字面量表示。其他有效选项是 `binary`、`octal` 和 `hexadecimal`。

```js
jsesc(42, { numbers: 'binary' })
// → '0b101010'

jsesc(42, { numbers: 'octal' })
// → '0o52'

jsesc(42, { numbers: 'decimal' })
// → '42'

jsesc(42, { numbers: 'hexadecimal' })
// → '0x2A'
```

#### `wrap`

`wrap` 选项接受布尔值（`true` 或 `false`），默认为 `false`（禁用）。启用时，输出是一个有效的 JavaScript 字符串字面量，用引号包裹。

```js
jsesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
  quotes: 'single',
  wrap: true
})
// → '\'Lorem ipsum "dolor" sit \\\'amet\\\' etc.\''

jsesc('Lorem ipsum "dolor" sit \'amet\' etc.', {
  quotes: 'double',
  wrap: true
})
// → '"Lorem ipsum \\"dolor\\" sit \'amet\' etc."'
```

#### `es6`

`es6` 选项接受布尔值（`true` 或 `false`），默认为 `false`（禁用）。启用时，输入中的任何星界 Unicode 符号都使用 [ECMAScript 6 Unicode 代码点转义序列](https://mathiasbynens.be/notes/javascript-escapes#unicode-code-point)进行转义。

```js
// 默认情况下，`es6` 选项是禁用的：
jsesc('foo 𝌆 bar 💩 baz')
// → 'foo \\uD834\\uDF06 bar \\uD83D\\uDCA9 baz'

// 启用它：
jsesc('foo 𝌆 bar 💩 baz', {
  es6: true
})
// → 'foo \\u{1D306} bar \\u{1F4A9} baz'
```

#### `escapeEverything`

`escapeEverything` 选项接受布尔值（`true` 或 `false`），默认为 `false`（禁用）。启用时，输出中的所有符号都被转义——甚至是可打印的 ASCII 符号。

```js
jsesc('lolwat"foo\'bar', {
  escapeEverything: true
})
// → '\\x6C\\x6F\\x6C\\x77\\x61\\x74\\"\\x66\\x6F\\x6F\\\'\\x62\\x61\\x72'
```

#### `minimal`

`minimal` 选项接受布尔值（`true` 或 `false`），默认为 `false`（禁用）。启用时，输出中只有有限的符号集合被转义。

**注意：** 启用此选项后，jsesc-es 的输出不再保证是 ASCII 安全的。

```js
jsesc('foo\u2029bar\nbaz©qux𝌆flops', {
  minimal: true
})
// → 'foo\\u2029bar\\nbaz©qux𝌆flops'
```

#### `isScriptContext`

`isScriptContext` 选项接受布尔值（`true` 或 `false`），默认为 `false`（禁用）。启用时，输出中出现的 [`</script` 和 `</style`](https://mathiasbynens.be/notes/etago) 被转义。

```js
jsesc('foo</script>bar', {
  isScriptContext: true
})
// → 'foo<\\/script>bar'
```

#### `compact`

`compact` 选项接受布尔值（`true` 或 `false`），默认为 `true`（启用）。启用时，数组和对象的输出尽可能紧凑。

```js
jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  compact: true // 这是默认值
})
// → '{\'Ich \u2665 B\xFCcher\':\'foo \uD834\uDF06 bar\'}'

jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  compact: false
})
// → '{\n\t\'Ich \u2665 B\xFCcher\': \'foo \uD834\uDF06 bar\'\n}'
```

#### `indent`

`indent` 选项接受字符串值，默认为 `'\t'`。当 `compact` 设置禁用（`false`）时，`indent` 选项的值用于格式化输出。

```js
jsesc({ 'Ich ♥ Bücher': 'foo 𝌆 bar' }, {
  compact: false,
  indent: '  '
})
// → '{\n  \'Ich \u2665 B\xFCcher\': \'foo \uD834\uDF06 bar\'\n}'
```

#### `indentLevel`

`indentLevel` 选项接受数值，默认为 `0`。它表示当前的缩进级别。

```js
jsesc(['a', 'b', 'c'], {
  compact: false,
  indentLevel: 1
})
// → '[\n\t\t\'a\',\n\t\t\'b\',\n\t\t\'c\'\n\t]'
```

#### `json`

`json` 选项接受布尔值（`true` 或 `false`），默认为 `false`（禁用）。启用时，输出是有效的 JSON。

```js
jsesc('foo\x00bar\xFF\uFFFDbaz', {
  json: true
})
// → '"foo\\u0000bar\\u00FF\\uFFFDbaz"'

jsesc({ 'foo\x00bar\xFF\uFFFDbaz': 'foo\x00bar\xFF\uFFFDbaz' }, {
  json: true
})
// → '{"foo\\u0000bar\\u00FF\\uFFFDbaz":"foo\\u0000bar\\u00FF\\uFFFDbaz"}'
```

#### `lowercaseHex`

`lowercaseHex` 选项接受布尔值（`true` 或 `false`），默认为 `false`（禁用）。启用时，转义序列中的任何字母十六进制数字都是小写的。

```js
jsesc('Ich ♥ Bücher', {
  lowercaseHex: true
})
// → 'Ich \\u2665 B\\xfccher'
//                    ^^

jsesc(42, {
  numbers: 'hexadecimal',
  lowercaseHex: true
})
// → '0x2a'
//      ^^
```

### 版本信息

```typescript
// 访问版本信息
import jsesc, { version } from 'jsesc-es'

console.log(jsesc.version) // 例如："0.0.3"
console.log(version) // 例如："0.0.3"
```

## 从 jsesc 迁移

从原始 `jsesc` 库迁移很简单：

### 1. 更新依赖

```bash
# 移除旧包
npm uninstall jsesc

# 安装新包
npm install jsesc-es
```

### 2. 更新导入

```javascript
// 之前（CommonJS）
const jsesc = require('jsesc')

// 之后（ES 模块）
import jsesc from 'jsesc-es'

// 或带类型（TypeScript）
import jsesc, { type JsescOptions } from 'jsesc-es'
```

### 3. 享受增强功能

- **完整的 TypeScript 支持** 无需额外设置
- **更好的 IDE 体验** 包括 IntelliSense
- **现代模块系统** 支持 tree-shaking
- **100% API 兼容** - 无需代码更改

详细的迁移说明，请参见 [MIGRATION.md](./MIGRATION.md)。

## TypeScript 示例

### 创建工具函数

```typescript
import type { JsescOptions } from 'jsesc-es'
import jsesc from 'jsesc-es'

// 创建类型安全的工具函数
function createEscaper(defaultOptions: JsescOptions) {
  return (input: string, options?: Partial<JsescOptions>): string => {
    return jsesc(input, { ...defaultOptions, ...options })
  }
}

// 预配置的转义器
const escapeForHTML = createEscaper({
  quotes: 'double',
  wrap: true,
  isScriptContext: true
})

const escapeForJSON = createEscaper({
  json: true
})

const escapeMinimal = createEscaper({
  minimal: true
})

// 具有完整类型安全的使用
const htmlSafe = escapeForHTML('Hello "World"')
const jsonSafe = escapeForJSON({ message: 'Hello 世界' })
const minimalEscape = escapeMinimal('简单文本')
```

### 高级配置

```typescript
import type { JsescOptions } from 'jsesc-es'

// 定义配置预设
const PRESETS: Record<string, JsescOptions> = {
  html: {
    quotes: 'double',
    wrap: true,
    isScriptContext: true,
    escapeEverything: false
  },
  json: {
    json: true,
    compact: true
  },
  es6: {
    es6: true,
    quotes: 'backtick',
    wrap: true
  },
  debug: {
    escapeEverything: true,
    compact: false,
    indent: '  '
  }
} as const

// 类型安全的预设使用
function escapeWithPreset(
  input: any,
  preset: keyof typeof PRESETS,
  overrides?: Partial<JsescOptions>
): string {
  const config = { ...PRESETS[preset], ...overrides }
  return jsesc(input, config)
}
```

## 性能

jsesc-es 相比原版包含几项性能改进：

- **优化的类型检查** 提供更好的运行时性能
- **改进的函数处理** 增强的检测逻辑
- **现代 JavaScript 特性** 更好的引擎优化
- **常见转义场景中的开销减少**
- **Tree-shaking 支持** 更小的包大小

## 浏览器支持

jsesc-es 通过其双构建系统提供广泛的兼容性：

### 运行时要求

**对于 ES 模块（推荐）：**

- **Node.js**：14+（原生 ESM 支持）
- **浏览器**：Chrome 61+、Firefox 60+、Safari 10.1+、Edge 16+
- **ES 模块**：需要原生支持

**对于 CommonJS（传统）：**

- **Node.js**：6+（转译输出）
- **浏览器**：Chrome 27+、Firefox 3+、Safari 4+、Opera 10+（使用打包器）

### 构建目标 vs. 模块系统

虽然 jsesc-es 通过其转译的 CommonJS 构建保持与传统环境的兼容性（目标 node6、chrome27 等），**ES 模块需要原生支持 `import`/`export` 语法的现代环境**。构建目标确保 _JavaScript 语法和 API_ 在较旧环境中工作，但 _模块系统本身_ 有最低版本要求。

**要点：**

- 📦 **CommonJS 构建**：在非常旧的环境中工作（Node 6+、Chrome 27+）
- 🚀 **ESM 构建**：需要具有原生 ESM 支持的现代环境
- 🔧 **传统项目**：使用 CommonJS 或使用 Babel/TypeScript 转译 ESM
- ⚡ **现代项目**：使用 ESM 获得更好的 tree-shaking 和性能

### TypeScript 支持

- **TypeScript**：4.5+
- **内置类型**：无需 `@types/` 包

## 开发

```bash
# 克隆仓库
git clone https://github.com/Drswith/jsesc-es.git
cd jsesc-es

# 安装依赖
pnpm install

# 运行测试
pnpm test

# 构建项目
pnpm build

# 类型检查
pnpm typecheck

# 代码检查
pnpm lint
```

## 贡献

欢迎贡献！请阅读我们的[贡献指南](CONTRIBUTING.md)了解行为准则和提交拉取请求的流程详情。

## 许可证

本项目采用 MIT 许可证 - 详情请参见 [LICENSE](LICENSE) 文件。

## 致谢

- **[Mathias Bynens](https://github.com/mathiasbynens)** - 原始 [jsesc](https://github.com/mathiasbynens/jsesc) 库的创建者
- **TypeScript 团队** - 提供出色的工具和类型系统
- **开源社区** - 持续的反馈和贡献

---

**jsesc-es** - 现代的、TypeScript 优先的 JavaScript 字符串转义方法。🚀
