Skip to content

Commit c12e0d1

Browse files
committed
v2.0.0: ESM/CJS dual-package, new serialization features, throwOnNonSerializable option
- Add "type": "module" with .mjs/.cjs dual-package support - Add throwOnNonSerializable option for WeakRef, WeakMap, WeakSet, FinalizationRegistry, Promise, Generator - Improve serialization: -0, sparse arrays, Symbol.for(), RegExp lastIndex, error subclasses, Object.create(null) - Use dot notation in cross-ref output (___ref1.prop instead of ___ref1['prop']) - Update @lopatnov/get-internal-type to 2.0.0 - Update CI to Node.js 18/20/22/24, tsconfig lib to ES2022 - Update README and CHANGELOG for v2.0.0 - 191 tests passing
1 parent 53b9260 commit c12e0d1

23 files changed

+480
-99
lines changed

.github/workflows/nodejs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88

99
strategy:
1010
matrix:
11-
node-version: [18.x, 20.x, 22.x]
11+
node-version: [18.x, 20.x, 22.x, 24.x]
1212

1313
steps:
1414
- uses: actions/checkout@v4

.github/workflows/npmpublish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
- uses: actions/checkout@v4
1212
- uses: actions/setup-node@v4
1313
with:
14-
node-version: 20
14+
node-version: 22
1515
cache: 'npm'
1616
- run: npm ci
1717
- run: npm run check
@@ -26,7 +26,7 @@ jobs:
2626
- run: npm run configure-npm
2727
- uses: actions/setup-node@v4
2828
with:
29-
node-version: 20
29+
node-version: 22
3030
registry-url: 'https://registry.npmjs.org'
3131
cache: 'npm'
3232
- run: npm ci
@@ -42,7 +42,7 @@ jobs:
4242
- uses: actions/checkout@v4
4343
- uses: actions/setup-node@v4
4444
with:
45-
node-version: 20
45+
node-version: 22
4646
registry-url: 'https://npm.pkg.github.com'
4747
scope: '@lopatnov'
4848
cache: 'npm'

CHANGELOG.md

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,51 @@
22

33
All notable changes to this project will be documented in this file.
44

5-
## [2.0.0] - 2026-02-13
5+
## [2.0.0] - 2026-02-14
66

77
### Added
88

99
- Cross-reference support: shared objects between different branches are now preserved as references
10+
- `throwOnNonSerializable` option: throws an error for non-serializable values instead of returning `"undefined"`
11+
- Explicit handling for non-serializable types: `Promise`, `Generator`, `WeakRef`, `WeakMap`, `WeakSet`, `FinalizationRegistry`
12+
- Negative zero (`-0`) preserved correctly
13+
- Sparse arrays preserved (holes are not filled with `undefined`)
14+
- `Symbol.for()` registry symbols distinguished from regular symbols
15+
- `Symbol("")` (empty description) distinguished from `Symbol()` (no description)
16+
- RegExp `lastIndex` preserved when non-zero
17+
- Error subclasses preserved: `TypeError`, `RangeError`, `ReferenceError`, `SyntaxError`, `URIError`, `EvalError`
18+
- `Object.create(null)` objects supported
19+
- Async functions and async generator functions supported
20+
- `SharedArrayBuffer` supported (grouped with `ArrayBuffer`)
21+
- ESM (`.mjs`) and CJS (`.cjs`) dual-package support via `exports` field
22+
- UMD build for browsers
23+
- `"type": "module"` in package.json
1024
- Biome for linting and formatting (replaced JSHint)
1125
- Jest coverage reporting enabled
12-
- 23 new tests for circular and cross-reference scenarios (76 total)
26+
- 191 tests total (up from 53)
1327

1428
### Fixed
1529

1630
- **Issue #1:** Circular references to parent elements at the top level are now resolved correctly
1731
- Circular chain references (A -> B -> C -> A) now work at any depth
32+
- `counter = counter++` post-increment bug in cross-reference actions (value never incremented)
33+
- `Object.prototype.hasOwnProperty.call()` used instead of `value.hasOwnProperty()` to support null-prototype objects
34+
- Non-identifier property names in function properties now use bracket notation (`fn["my-prop"]` instead of invalid `fn.my-prop`)
35+
- Non-identifier property names in object literals are now quoted
36+
- Invalid `Date` objects now serialize as `new Date(NaN)` instead of `new Date("null")`
37+
- Date strings are now quoted in output
1838

1939
### Changed
2040

41+
- Cross-reference output uses dot notation when possible (`___ref1.prop` instead of `___ref1['prop']`)
42+
- Internal IIFE variable renamed from `___j2s_` to `___ref` for readability
43+
- Updated `@lopatnov/get-internal-type` to 2.0.0
2144
- Updated all dependencies to latest versions
2245
- Migrated from `rollup-plugin-*` to `@rollup/plugin-*` official packages
23-
- Migrated CI from Node.js 12.x to Node.js 18/20/22
46+
- Migrated CI from Node.js 12.x to Node.js 18/20/22/24
2447
- Updated `@lopatnov/rollup-plugin-uglify` from 2.x to 3.x
2548
- Upgraded TypeScript to 5.8, Jest to 30, Rollup to 4, Biome to 2.x
49+
- tsconfig lib updated to ES2022
2650
- Minimum Node.js version is now 18.0.0
2751

2852
### Removed

README.md

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Converts a JavaScript value to its string source code representation.
6363
| `nestedObjectsAmount` | `number` | `Infinity` | Max depth for nested objects |
6464
| `nestedArraysAmount` | `number` | `Infinity` | Max depth for nested arrays |
6565
| `nestedFunctionsAmount` | `number` | `Infinity` | Max depth for nested functions |
66+
| `throwOnNonSerializable` | `boolean` | `false` | Throw an error for non-serializable values (Promise, Generator, WeakRef, WeakMap, WeakSet, FinalizationRegistry) |
6667

6768
## Examples
6869

@@ -121,7 +122,7 @@ var x = [1, 2, 3];
121122
x[0] = x;
122123

123124
javaScriptToString(x);
124-
// '(function(){ var ___j2s_0 = [null, 2, 3]; ___j2s_0['0'] = ___j2s_0; return ___j2s_0; }())'
125+
// '(function(){ var ___ref1 = [null, 2, 3]; ___ref1[0] = ___ref1; return ___ref1; }())'
125126
```
126127

127128
### Cross-References
@@ -133,13 +134,11 @@ var shared = { value: 42 };
133134
var obj = { a: shared, b: shared };
134135

135136
javaScriptToString(obj);
136-
// Generates code where obj.a === obj.b (same reference), like:
137-
// (function(){ var ___j2s_0 = {
138-
// a: {
139-
// value: 42
140-
// },
141-
// b: null
142-
// }; ___j2s_0['b'] = ___j2s_0['a']; return ___j2s_0; }())
137+
// Generates code where obj.a === obj.b (same reference):
138+
// (function(){ var ___ref1 = {
139+
// a: { value: 42 },
140+
// b: null
141+
// }; ___ref1.b = ___ref1.a; return ___ref1; }())
143142
```
144143

145144
### Using with Web Workers
@@ -191,22 +190,29 @@ console.log(restored.name); // "test"
191190

192191
## Supported Types
193192

194-
| Type | Example |
195-
|------|---------|
196-
| Primitives | `string`, `number`, `boolean`, `undefined`, `null` |
197-
| BigInt | `BigInt(123)` |
198-
| Symbol | `Symbol("description")` |
199-
| RegExp | `/pattern/gi` |
200-
| Date | `new Date()` |
201-
| Error | `new Error("message")` |
202-
| Array | `[1, 2, 3]` |
203-
| Object | `{ key: "value" }` |
204-
| Function | `function() {}`, `() => {}` |
205-
| Map | `new Map([["key", "value"]])` |
206-
| Set | `new Set([1, 2, 3])` |
207-
| TypedArray | `Int8Array`, `Float64Array`, etc. |
208-
| ArrayBuffer | `new ArrayBuffer(8)` |
209-
| DataView | `new DataView(buffer)` |
193+
| Type | Example | Notes |
194+
|------|---------|-------|
195+
| Primitives | `string`, `number`, `boolean`, `undefined`, `null` | Including `-0` and `NaN` |
196+
| BigInt | `BigInt(123)` | |
197+
| Symbol | `Symbol("desc")`, `Symbol.for("key")` | Registry symbols preserved |
198+
| RegExp | `/pattern/gi` | `lastIndex` preserved when non-zero |
199+
| Date | `new Date("...")` | Invalid dates → `new Date(NaN)` |
200+
| Error | `new Error()`, `new TypeError()` | TypeError, RangeError, ReferenceError, SyntaxError, URIError, EvalError |
201+
| Array | `[1, 2, 3]` | Sparse arrays preserved |
202+
| Object | `{ key: "value" }` | Including `Object.create(null)` |
203+
| Function | `function() {}`, `() => {}`, `async function() {}` | Properties and prototype included |
204+
| Generator Function | `function*() {}`, `async function*() {}` | |
205+
| Map | `new Map([["key", "value"]])` | |
206+
| Set | `new Set([1, 2, 3])` | |
207+
| TypedArray | `Int8Array`, `Float64Array`, etc. | |
208+
| ArrayBuffer | `new ArrayBuffer(8)`, `SharedArrayBuffer` | |
209+
| DataView | `new DataView(buffer)` | |
210+
211+
### Non-serializable Types
212+
213+
The following types cannot be serialized and return `"undefined"` by default. Use `throwOnNonSerializable: true` to throw an error instead:
214+
215+
`Promise`, `Generator`, `WeakRef`, `WeakMap`, `WeakSet`, `FinalizationRegistry`
210216

211217
## Demo
212218

dist/javascripttostring.es5.mjs.map

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1-
var types = {}, typesToString = types.toString, buildInList = [
1+
const types = {};
2+
const typesToString = Object.prototype.toString;
3+
const builtInList = [
24
"Boolean",
35
"Number",
46
"String",
5-
"Function",
67
"Array",
78
"Date",
89
"RegExp",
910
"Object",
1011
"Error",
1112
"Promise",
12-
"Generator",
13-
"GeneratorFunction",
14-
"ArrayBuffer",
15-
"DataView"
16-
], typedArrays = [
13+
"DataView",
14+
"WeakRef",
15+
"FinalizationRegistry"
16+
];
17+
const functions = ["Function", "AsyncFunction"];
18+
const generators = ["Generator", "AsyncGenerator"];
19+
const generatorFunctions = ["GeneratorFunction", "AsyncGeneratorFunction"];
20+
const arrayBuffers = ["ArrayBuffer", "SharedArrayBuffer"];
21+
const maps = ["Map", "WeakMap"];
22+
const sets = ["Set", "WeakSet"];
23+
const typedArrays = [
1724
"Int8Array",
1825
"Uint8Array",
1926
"Uint8ClampedArray",
@@ -25,10 +32,22 @@ var types = {}, typesToString = types.toString, buildInList = [
2532
"Float64Array",
2633
"BigInt64Array",
2734
"BigUint64Array"
28-
], maps = ["Map", "WeakMap"], sets = ["Set", "WeakSet"];
29-
buildInList.forEach(function (name) {
35+
];
36+
builtInList.forEach(function (name) {
3037
types["[object " + name + "]"] = name.toLowerCase();
3138
});
39+
functions.forEach(function (name) {
40+
types["[object " + name + "]"] = "function";
41+
});
42+
generators.forEach(function (name) {
43+
types["[object " + name + "]"] = "generator";
44+
});
45+
generatorFunctions.forEach(function (name) {
46+
types["[object " + name + "]"] = "generatorfunction";
47+
});
48+
arrayBuffers.forEach(function (name) {
49+
types["[object " + name + "]"] = "arraybuffer";
50+
});
3251
maps.forEach(function (name) {
3352
types["[object " + name + "]"] = "map";
3453
});
@@ -359,8 +378,20 @@ function stringify(value, options, history) {
359378
case "typedarray":
360379
return typedArrayToString(value, options, history);
361380
case "set":
381+
if (value instanceof WeakSet) {
382+
if (options.throwOnNonSerializable) {
383+
throw new Error("Non-serializable value: WeakSet");
384+
}
385+
return "undefined";
386+
}
362387
return setToString(value, options, history);
363388
case "map":
389+
if (value instanceof WeakMap) {
390+
if (options.throwOnNonSerializable) {
391+
throw new Error("Non-serializable value: WeakMap");
392+
}
393+
return "undefined";
394+
}
364395
return mapToString(value, options, history);
365396
case "object":
366397
return objectToString(value, options, history);
@@ -373,6 +404,13 @@ function stringify(value, options, history) {
373404
return dataViewToString(value, options, history);
374405
case "promise":
375406
case "generator":
407+
case "weakref":
408+
case "weakmap":
409+
case "weakset":
410+
case "finalizationregistry":
411+
if (options.throwOnNonSerializable) {
412+
throw new Error(`Non-serializable value: ${getInternalType(value)}`);
413+
}
376414
return "undefined";
377415
default:
378416
return JSON.stringify(value);
@@ -476,6 +514,7 @@ function javaScriptToString(value, options) {
476514
nestedObjectsAmount: options.nestedObjectsAmount === undefined ? Number.POSITIVE_INFINITY : options.nestedObjectsAmount,
477515
nestedArraysAmount: options.nestedArraysAmount === undefined ? Number.POSITIVE_INFINITY : options.nestedArraysAmount,
478516
nestedFunctionsAmount: options.nestedFunctionsAmount === undefined ? Number.POSITIVE_INFINITY : options.nestedFunctionsAmount,
517+
throwOnNonSerializable: options.throwOnNonSerializable === undefined ? false : options.throwOnNonSerializable,
479518
};
480519
// Clear global state before conversion
481520
refs = [];
@@ -498,4 +537,4 @@ function javaScriptToString(value, options) {
498537
}
499538

500539
export { javaScriptToString as default };
501-
//# sourceMappingURL=javascripttostring.es5.mjs.map
540+
//# sourceMappingURL=javascripttostring.esm.mjs.map

dist/javascripttostring.esm.mjs.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/javascripttostring.umd.js

Lines changed: 48 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/javascripttostring.umd.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)