kn-es-features 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/esfeatures.iife.js +45 -0
  2. package/dist/esfeatures.js +3795 -0
  3. package/dist/esfeatures.umd.cjs +45 -0
  4. package/dist/polyfills.iife.js +7 -0
  5. package/package.json +39 -0
  6. package/src/features/es2015/01-let-const.js +54 -0
  7. package/src/features/es2015/02-arrow-functions.js +68 -0
  8. package/src/features/es2015/03-template-literals.js +51 -0
  9. package/src/features/es2015/04-destructuring.js +68 -0
  10. package/src/features/es2015/05-default-rest-spread.js +87 -0
  11. package/src/features/es2015/06-classes.js +100 -0
  12. package/src/features/es2015/07-promises.js +79 -0
  13. package/src/features/es2015/08-symbols.js +85 -0
  14. package/src/features/es2015/09-iterators-generators.js +104 -0
  15. package/src/features/es2015/10-map-set.js +106 -0
  16. package/src/features/es2015/11-proxy-reflect.js +122 -0
  17. package/src/features/es2015/12-enhanced-objects.js +101 -0
  18. package/src/features/es2015/13-new-methods.js +123 -0
  19. package/src/features/es2015/14-modules.js +38 -0
  20. package/src/features/es2015/15-binary-octal-unicode.js +65 -0
  21. package/src/features/es2015/16-for-of.js +75 -0
  22. package/src/features/es2015/17-map.js +80 -0
  23. package/src/features/es2022/01-class-fields.js +114 -0
  24. package/src/features/es2022/02-class-static-blocks.js +108 -0
  25. package/src/features/es2022/03-at-method.js +86 -0
  26. package/src/features/es2022/04-object-has-own.js +82 -0
  27. package/src/features/es2022/05-error-cause.js +92 -0
  28. package/src/features/es2022/06-regexp-d-flag.js +89 -0
  29. package/src/features/es2022/07-top-level-await.js +84 -0
  30. package/src/features/es2025/01-iterator-helpers.js +96 -0
  31. package/src/features/es2025/02-set-methods.js +92 -0
  32. package/src/features/es2025/03-promise-try.js +72 -0
  33. package/src/features/es2025/04-regexp-duplicate-groups.js +69 -0
  34. package/src/features/es2025/05-uint8array-base64-hex.js +94 -0
  35. package/src/features/es2025/06-json-parse-source.js +86 -0
  36. package/src/features/es2025/07-error-is-error.js +83 -0
  37. package/src/features/es2025/08-float16array.js +88 -0
  38. package/src/features/es2026/01-math-sum-precise.js +79 -0
  39. package/src/features/es2026/02-regexp-escape.js +80 -0
  40. package/src/features/es2026/03-explicit-resource-management.js +117 -0
  41. package/src/features/es2026/04-atomics-pause.js +81 -0
  42. package/src/features/es2026/05-import-attributes.js +86 -0
  43. package/src/index.js +166 -0
  44. package/src/main.js +226 -0
  45. package/src/polyfills.js +6 -0
  46. package/src/run.js +3 -0
  47. package/src/style.css +412 -0
  48. package/src/utils/runner.js +55 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * ES2026 —— Import Attributes(导入断言 / 导入属性)
3
+ *
4
+ * 允许在 import 语句中附加类型属性,指定导入模块的预期类型:
5
+ * import data from './data.json' with { type: 'json' }
6
+ * import css from './style.css' with { type: 'css' }
7
+ *
8
+ * 主要解决的问题:
9
+ * - 防止将非 JS 文件误当作 JS 模块执行(MIME 混淆攻击)
10
+ * - 明确告知宿主环境如何解析该模块
11
+ *
12
+ * 注意:宿主环境(浏览器/Node.js)决定支持哪些 type 值。
13
+ * type: 'json' 是最广泛支持的选项。
14
+ */
15
+ import { createSuite } from '../../utils/runner.js'
16
+
17
+ export async function testImportAttributes() {
18
+ const { test, assert, getResults } = createSuite('Import Attributes (ES2026)')
19
+
20
+ // 检测语法支持(with 是新语法)
21
+ const syntaxSupported = (() => {
22
+ try {
23
+ // 用 Function 构造器检测 import with 语法是否被解析器接受
24
+ new Function(`import('data:text/javascript,export default 1', { with: { type: 'javascript' } })`)
25
+ return true
26
+ } catch { return false }
27
+ })()
28
+
29
+ test('动态 import() 支持 with 选项对象', async () => {
30
+ if (!syntaxSupported) { assert(true, '(跳过:环境不支持 import with 语法)'); return }
31
+ // 使用 data: URL + type: javascript 测试动态导入属性
32
+ let result = null
33
+ try {
34
+ const mod = await import('data:text/javascript,export default "hello"', {
35
+ with: { type: 'javascript' }
36
+ })
37
+ result = mod.default
38
+ } catch {
39
+ // 某些环境允许语法但不允许 data: URL,也视为语法支持
40
+ result = 'syntax-ok'
41
+ }
42
+ assert(result === 'hello' || result === 'syntax-ok', 'import with 语法应被环境支持')
43
+ })
44
+
45
+ test('import with 选项中 type 属性为字符串', () => {
46
+ // 验证属性结构正确性(不依赖实际 import)
47
+ const options = { with: { type: 'json' } }
48
+ assert(typeof options.with.type === 'string', 'type 属性应为字符串')
49
+ assert(options.with.type === 'json', 'json type 应正确设置')
50
+ })
51
+
52
+ test('import() 的第二个参数结构', () => {
53
+ // 动态 import() 语法:import(specifier, options)
54
+ // options.with 是属性映射,type 是最常用的键
55
+ const validOptions = [
56
+ { with: { type: 'json' } },
57
+ { with: { type: 'css' } },
58
+ { with: { type: 'javascript' } },
59
+ ]
60
+ validOptions.forEach(opt => {
61
+ assert(opt.with !== undefined, 'options.with 应存在')
62
+ assert(typeof opt.with.type === 'string', 'type 应为字符串')
63
+ })
64
+ assert(true, 'import() 选项结构验证通过')
65
+ })
66
+
67
+ test('JSON 模块导入属性防止 MIME 混淆(原理说明)', () => {
68
+ // 不带 type: 'json',浏览器可能将 JSON 文件当作 JS 执行 → 安全风险
69
+ // 带 type: 'json',浏览器验证 MIME 类型必须是 application/json
70
+ // 本测试验证该概念的正确性
71
+ const requiresType = true // JSON 模块必须声明 type: 'json'
72
+ assert(requiresType, 'JSON 模块导入应声明 type: "json" 防止 MIME 混淆攻击')
73
+ })
74
+
75
+ test('import with 仅传递元信息,不影响模块标识符', () => {
76
+ // 同一个 URL + 不同 type → 实际上是不同的模块缓存键
77
+ // 但模块说明符(specifier)本身不变
78
+ const specifier = './data.json'
79
+ const opts1 = { with: { type: 'json' } }
80
+ const opts2 = { with: { type: 'json' } }
81
+ assert(specifier === './data.json', '模块说明符不应被 with 属性修改')
82
+ assert(opts1.with.type === opts2.with.type, '相同 type 的 options 应等价')
83
+ })
84
+
85
+ return getResults()
86
+ }
package/src/index.js ADDED
@@ -0,0 +1,166 @@
1
+ // ── ES2015 特性 ──────────────────────────────────────────────────────────────
2
+ import { testLetConst } from './features/es2015/01-let-const.js'
3
+ import { testArrowFunctions } from './features/es2015/02-arrow-functions.js'
4
+ import { testTemplateLiterals } from './features/es2015/03-template-literals.js'
5
+ import { testDestructuring } from './features/es2015/04-destructuring.js'
6
+ import { testDefaultRestSpread } from './features/es2015/05-default-rest-spread.js'
7
+ import { testClasses } from './features/es2015/06-classes.js'
8
+ import { testPromises } from './features/es2015/07-promises.js'
9
+ import { testSymbols } from './features/es2015/08-symbols.js'
10
+ import { testIteratorsGenerators } from './features/es2015/09-iterators-generators.js'
11
+ import { testMapSet } from './features/es2015/10-map-set.js'
12
+ import { testProxyReflect } from './features/es2015/11-proxy-reflect.js'
13
+ import { testEnhancedObjects } from './features/es2015/12-enhanced-objects.js'
14
+ import { testNewMethods } from './features/es2015/13-new-methods.js'
15
+ import { testModules } from './features/es2015/14-modules.js'
16
+ import { testBinaryOctalUnicode } from './features/es2015/15-binary-octal-unicode.js'
17
+ import { testForOf } from './features/es2015/16-for-of.js'
18
+ import { testMapGetOrInsert } from './features/es2015/17-map.js'
19
+
20
+ // ── ES2022 特性 ──────────────────────────────────────────────────────────────
21
+ import { testClassFields } from './features/es2022/01-class-fields.js'
22
+ import { testClassStaticBlocks } from './features/es2022/02-class-static-blocks.js'
23
+ import { testAtMethod } from './features/es2022/03-at-method.js'
24
+ import { testObjectHasOwn } from './features/es2022/04-object-has-own.js'
25
+ import { testErrorCause } from './features/es2022/05-error-cause.js'
26
+ import { testRegExpDFlag } from './features/es2022/06-regexp-d-flag.js'
27
+ import { testTopLevelAwait } from './features/es2022/07-top-level-await.js'
28
+
29
+ // ── ES2025 特性 ──────────────────────────────────────────────────────────────
30
+ import { testIteratorHelpers } from './features/es2025/01-iterator-helpers.js'
31
+ import { testSetMethods } from './features/es2025/02-set-methods.js'
32
+ import { testPromiseTry } from './features/es2025/03-promise-try.js'
33
+ import { testRegExpDuplicateGroups } from './features/es2025/04-regexp-duplicate-groups.js'
34
+ import { testUint8ArrayBase64Hex } from './features/es2025/05-uint8array-base64-hex.js'
35
+ import { testJsonParseSource } from './features/es2025/06-json-parse-source.js'
36
+ import { testErrorIsError } from './features/es2025/07-error-is-error.js'
37
+ import { testFloat16Array } from './features/es2025/08-float16array.js'
38
+
39
+ // ── ES2026 特性 ──────────────────────────────────────────────────────────────
40
+ import { testMathSumPrecise } from './features/es2026/01-math-sum-precise.js'
41
+ import { testRegExpEscape } from './features/es2026/02-regexp-escape.js'
42
+ import { testExplicitResourceManagement } from './features/es2026/03-explicit-resource-management.js'
43
+ import { testAtomicsPause } from './features/es2026/04-atomics-pause.js'
44
+ import { testImportAttributes } from './features/es2026/05-import-attributes.js'
45
+
46
+ import { printResults } from './utils/runner.js'
47
+
48
+ // ── 测试套件注册表 ──────────────────────────────────────────────────────────
49
+
50
+ const suites2015 = [
51
+ { name: 'let & const', fn: testLetConst },
52
+ { name: '箭头函数', fn: testArrowFunctions },
53
+ { name: '模板字符串', fn: testTemplateLiterals },
54
+ { name: '解构赋值', fn: testDestructuring },
55
+ { name: '默认参数 / Rest / Spread', fn: testDefaultRestSpread },
56
+ { name: '类(Class)', fn: testClasses },
57
+ { name: 'Promise & async/await', fn: testPromises },
58
+ { name: 'Symbol', fn: testSymbols },
59
+ { name: '迭代器与生成器', fn: testIteratorsGenerators },
60
+ { name: 'Map & Set & WeakMap & WeakSet', fn: testMapSet },
61
+ { name: 'Proxy & Reflect', fn: testProxyReflect },
62
+ { name: '增强对象字面量', fn: testEnhancedObjects },
63
+ { name: '新增内置方法', fn: testNewMethods },
64
+ { name: '模块(Modules)', fn: testModules },
65
+ { name: '进制字面量与 Unicode', fn: testBinaryOctalUnicode },
66
+ { name: 'for...of', fn: testForOf },
67
+ { name: 'Map getOrInsert', fn: testMapGetOrInsert },
68
+ ]
69
+
70
+ const suites2022 = [
71
+ { name: 'Class Fields(私有字段 / 静态字段)', fn: testClassFields },
72
+ { name: 'Class Static Blocks', fn: testClassStaticBlocks },
73
+ { name: 'Array / String .at()', fn: testAtMethod },
74
+ { name: 'Object.hasOwn()', fn: testObjectHasOwn },
75
+ { name: 'Error Cause', fn: testErrorCause },
76
+ { name: 'RegExp /d flag(匹配索引)', fn: testRegExpDFlag },
77
+ { name: 'Top-level await', fn: testTopLevelAwait },
78
+ ]
79
+
80
+ const suites2025 = [
81
+ { name: 'Iterator Helpers', fn: testIteratorHelpers },
82
+ { name: 'New Set Methods', fn: testSetMethods },
83
+ { name: 'Promise.try', fn: testPromiseTry },
84
+ { name: 'RegExp Duplicate Named Capture Groups', fn: testRegExpDuplicateGroups },
85
+ { name: 'Uint8Array Base64 / Hex', fn: testUint8ArrayBase64Hex },
86
+ { name: 'JSON.parse Source Text Access', fn: testJsonParseSource },
87
+ { name: 'Error.isError', fn: testErrorIsError },
88
+ { name: 'Float16Array', fn: testFloat16Array },
89
+ ]
90
+
91
+ const suites2026 = [
92
+ { name: 'Math.sumPrecise()', fn: testMathSumPrecise },
93
+ { name: 'RegExp.escape()', fn: testRegExpEscape },
94
+ { name: 'Explicit Resource Management', fn: testExplicitResourceManagement },
95
+ { name: 'Atomics.pause()', fn: testAtomicsPause },
96
+ { name: 'Import Attributes', fn: testImportAttributes },
97
+ ]
98
+
99
+ // ── 核心运行函数 ──────────────────────────────────────────────────────────────
100
+
101
+ async function runSuites(suites) {
102
+ const allResults = []
103
+ for (const suite of suites) {
104
+ const results = await suite.fn()
105
+ allResults.push(...results)
106
+ }
107
+ return allResults
108
+ }
109
+
110
+ /** 运行 ES2015 测试 */
111
+ export async function runAll2015() { return runSuites(suites2015) }
112
+
113
+ /** 运行 ES2022 测试 */
114
+ export async function runAll2022() { return runSuites(suites2022) }
115
+
116
+ /** 运行 ES2025 测试 */
117
+ export async function runAll2025() { return runSuites(suites2025) }
118
+
119
+ /** 运行 ES2026 测试 */
120
+ export async function runAll2026() { return runSuites(suites2026) }
121
+
122
+ /** 运行全部测试(所有版本) */
123
+ export async function runAll() {
124
+ return runSuites([...suites2015, ...suites2022, ...suites2025, ...suites2026])
125
+ }
126
+
127
+ /** 运行全部测试并分版本打印结果 */
128
+ export async function runAndPrint() {
129
+ for (const [label, fn] of [
130
+ ['ES2015', runAll2015],
131
+ ['ES2022', runAll2022],
132
+ ['ES2025', runAll2025],
133
+ ['ES2026', runAll2026],
134
+ ]) {
135
+ console.log(`\n=== ${label} 特性测试 ===\n`)
136
+ printResults(await fn())
137
+ }
138
+ }
139
+
140
+ // ── 按版本导出各测试函数 ─────────────────────────────────────────────────────
141
+
142
+ // ES2015
143
+ export {
144
+ testLetConst, testArrowFunctions, testTemplateLiterals, testDestructuring,
145
+ testDefaultRestSpread, testClasses, testPromises, testSymbols,
146
+ testIteratorsGenerators, testMapSet, testProxyReflect, testEnhancedObjects,
147
+ testNewMethods, testModules, testBinaryOctalUnicode, testForOf, testMapGetOrInsert,
148
+ }
149
+
150
+ // ES2022
151
+ export {
152
+ testClassFields, testClassStaticBlocks, testAtMethod,
153
+ testObjectHasOwn, testErrorCause, testRegExpDFlag, testTopLevelAwait,
154
+ }
155
+
156
+ // ES2025
157
+ export {
158
+ testIteratorHelpers, testSetMethods, testPromiseTry, testRegExpDuplicateGroups,
159
+ testUint8ArrayBase64Hex, testJsonParseSource, testErrorIsError, testFloat16Array,
160
+ }
161
+
162
+ // ES2026
163
+ export {
164
+ testMathSumPrecise, testRegExpEscape, testExplicitResourceManagement,
165
+ testAtomicsPause, testImportAttributes,
166
+ }
package/src/main.js ADDED
@@ -0,0 +1,226 @@
1
+ (function () {
2
+ 'use strict'
3
+
4
+ /* ── 版本配置(数据驱动,新增版本只需加这里)─────────────────── */
5
+ const VERSIONS = [
6
+ { key: '2015', run: function () { return EsFeatures.runAll2015() } },
7
+ { key: '2022', run: function () { return EsFeatures.runAll2022() } },
8
+ { key: '2025', run: function () { return EsFeatures.runAll2025() } },
9
+ { key: '2026', run: function () { return EsFeatures.runAll2026() } },
10
+ ]
11
+
12
+ /* ── DOM 引用 ────────────────────────────────────────────────────── */
13
+ const btnRun = document.getElementById('btn-run')
14
+ const valTotal = document.getElementById('val-total')
15
+ const tabs = document.querySelectorAll('.tab')
16
+
17
+ /* ── Tab 切换 ────────────────────────────────────────────────────── */
18
+ tabs.forEach(function (tab) {
19
+ tab.addEventListener('click', function () {
20
+ tabs.forEach(function (t) { t.classList.remove('tab--active') })
21
+ tab.classList.add('tab--active')
22
+ document.querySelectorAll('.panel').forEach(function (p) {
23
+ p.classList.remove('panel--active')
24
+ })
25
+ document.getElementById(tab.dataset.panel).classList.add('panel--active')
26
+ })
27
+ })
28
+
29
+ /* ── 工具函数 ────────────────────────────────────────────────────── */
30
+
31
+ function escape(str) {
32
+ return String(str)
33
+ .replace(/&/g, '&')
34
+ .replace(/</g, '&lt;')
35
+ .replace(/>/g, '&gt;')
36
+ .replace(/"/g, '&quot;')
37
+ }
38
+
39
+ function groupBySuite(results) {
40
+ const suites = []
41
+ const map = Object.create(null)
42
+ results.forEach(function (r) {
43
+ if (!map[r.suite]) {
44
+ const s = { name: r.suite, cases: [] }
45
+ map[r.suite] = s
46
+ suites.push(s)
47
+ }
48
+ map[r.suite].cases.push(r)
49
+ })
50
+ return suites
51
+ }
52
+
53
+ function calcStats(results) {
54
+ let pass = 0, fail = 0
55
+ results.forEach(function (r) { r.status === 'pass' ? pass++ : fail++ })
56
+ return { pass: pass, fail: fail, total: results.length }
57
+ }
58
+
59
+ /* ── 渲染函数 ────────────────────────────────────────────────────── */
60
+
61
+ function renderCase(c) {
62
+ const li = document.createElement('li')
63
+ li.className = 'case case--' + c.status
64
+
65
+ const icon = document.createElement('span')
66
+ icon.className = 'case__icon'
67
+ icon.textContent = c.status === 'pass' ? '✓' : '✗'
68
+
69
+ const body = document.createElement('span')
70
+ body.className = 'case__body'
71
+
72
+ const name = document.createElement('span')
73
+ name.className = 'case__name'
74
+ name.textContent = c.case
75
+ body.appendChild(name)
76
+
77
+ if (c.error) {
78
+ const err = document.createElement('span')
79
+ err.className = 'case__error'
80
+ err.textContent = c.error
81
+ body.appendChild(err)
82
+ }
83
+
84
+ li.appendChild(icon)
85
+ li.appendChild(body)
86
+ return li
87
+ }
88
+
89
+ function renderSuite(suite) {
90
+ let pass = 0, fail = 0
91
+ suite.cases.forEach(function (c) { c.status === 'pass' ? pass++ : fail++ })
92
+ const allPass = fail === 0
93
+ const stateKey = allPass ? 'pass' : 'fail'
94
+
95
+ const card = document.createElement('div')
96
+ card.className = 'suite'
97
+
98
+ const header = document.createElement('div')
99
+ header.className = 'suite__header'
100
+ header.innerHTML =
101
+ '<div class="suite__header-left">' +
102
+ '<span class="suite__indicator suite__indicator--' + stateKey + '"></span>' +
103
+ '<span class="suite__name">' + escape(suite.name) + '</span>' +
104
+ '</div>' +
105
+ '<div class="suite__header-right">' +
106
+ '<span class="suite__stat suite__stat--' + stateKey + '">' +
107
+ pass + ' / ' + suite.cases.length +
108
+ '</span>' +
109
+ '<span class="suite__chevron">▾</span>' +
110
+ '</div>'
111
+
112
+ header.addEventListener('click', function () {
113
+ card.classList.toggle('suite--collapsed')
114
+ })
115
+
116
+ const ul = document.createElement('ul')
117
+ ul.className = 'suite__cases'
118
+ suite.cases.forEach(function (c) { ul.appendChild(renderCase(c)) })
119
+
120
+ card.appendChild(header)
121
+ card.appendChild(ul)
122
+ return card
123
+ }
124
+
125
+ function showLoader(panel) {
126
+ panel.innerHTML =
127
+ '<div class="loader">' +
128
+ '<span class="loader__spinner"></span>' +
129
+ '<span>正在运行测试…</span>' +
130
+ '</div>'
131
+ }
132
+
133
+ function renderPanel(panel, results) {
134
+ panel.innerHTML = ''
135
+ if (!results || results.length === 0) {
136
+ panel.innerHTML =
137
+ '<div class="placeholder">' +
138
+ '<span class="placeholder__icon">📭</span>' +
139
+ '<p class="placeholder__text">暂无测试结果</p>' +
140
+ '</div>'
141
+ return
142
+ }
143
+ const frag = document.createDocumentFragment()
144
+ groupBySuite(results).forEach(function (s) { frag.appendChild(renderSuite(s)) })
145
+ panel.appendChild(frag)
146
+ }
147
+
148
+ function updateSummaryValue(id, stats) {
149
+ const el = document.getElementById('val-' + id)
150
+ if (!el) return
151
+ if (!stats) { el.textContent = '—'; el.className = 'summary__value'; return }
152
+ el.textContent = stats.pass + ' / ' + stats.total
153
+ el.className = 'summary__value summary__value--' + (stats.fail === 0 ? 'pass' : 'fail')
154
+ }
155
+
156
+ function updateTabBadge(id, stats) {
157
+ const el = document.getElementById('badge-' + id)
158
+ if (!el) return
159
+ if (!stats) { el.textContent = ''; el.className = 'tab__badge'; return }
160
+ if (stats.fail === 0) {
161
+ el.textContent = '全部通过'
162
+ el.className = 'tab__badge tab__badge--pass'
163
+ } else {
164
+ el.textContent = stats.fail + ' 失败'
165
+ el.className = 'tab__badge tab__badge--fail'
166
+ }
167
+ }
168
+
169
+ /* ── 主运行流程 ──────────────────────────────────────────────────── */
170
+ async function runTests() {
171
+ btnRun.disabled = true
172
+ btnRun.classList.add('btn-run--running')
173
+ btnRun.querySelector('.btn-run__label').textContent = '运行中…'
174
+
175
+ // 重置统计
176
+ VERSIONS.forEach(function (v) {
177
+ updateSummaryValue(v.key, null)
178
+ updateTabBadge(v.key, null)
179
+ showLoader(document.getElementById('panel-' + v.key))
180
+ })
181
+ updateSummaryValue('total', null)
182
+
183
+ try {
184
+ // 并行运行所有版本
185
+ const resultSets = await Promise.all(
186
+ VERSIONS.map(function (v) { return v.run() })
187
+ )
188
+
189
+ let totalPass = 0, totalFail = 0, totalCount = 0
190
+
191
+ VERSIONS.forEach(function (v, i) {
192
+ const results = resultSets[i]
193
+ const stats = calcStats(results)
194
+ renderPanel(document.getElementById('panel-' + v.key), results)
195
+ updateSummaryValue(v.key, stats)
196
+ updateTabBadge(v.key, stats)
197
+ totalPass += stats.pass
198
+ totalFail += stats.fail
199
+ totalCount += stats.total
200
+ })
201
+
202
+ updateSummaryValue('total', {
203
+ pass: totalPass, fail: totalFail, total: totalCount
204
+ })
205
+
206
+ } catch (err) {
207
+ VERSIONS.forEach(function (v) {
208
+ const panel = document.getElementById('panel-' + v.key)
209
+ if (panel) {
210
+ panel.innerHTML =
211
+ '<div class="placeholder">' +
212
+ '<span class="placeholder__icon">⚠️</span>' +
213
+ '<p class="placeholder__text">运行出错:' + escape(err.message) + '</p>' +
214
+ '</div>'
215
+ }
216
+ })
217
+ console.error('[ES 测试]', err)
218
+ }
219
+
220
+ btnRun.disabled = false
221
+ btnRun.classList.remove('btn-run--running')
222
+ btnRun.querySelector('.btn-run__label').textContent = '重新运行'
223
+ }
224
+
225
+ btnRun.addEventListener('click', runTests)
226
+ })()
@@ -0,0 +1,6 @@
1
+ // core-js polyfill 入口:补齐目标浏览器(Chrome >= 40)缺失的 API
2
+ // 此文件由 Vite 编译为 IIFE,在页面中作为普通 <script> 提前加载
3
+
4
+ // 引入完整的现代特性补丁(涵盖 ES2015 到最新的特性)
5
+ import 'core-js/stable';
6
+ import 'regenerator-runtime/runtime'; // 如果需要兼容生成器函数
package/src/run.js ADDED
@@ -0,0 +1,3 @@
1
+ import { runAndPrint } from './index.js'
2
+
3
+ runAndPrint().catch(console.error)