diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index c5f2397..f9699f3 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,4 +1,4 @@ -name: Node CI +name: Node Package CI on: [push, pull_request] @@ -11,24 +11,24 @@ jobs: node-version: [18.x, 20.x, 22.x, 24.x] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' - - name: Install dependencies - run: npm ci + - name: Install dependencies + run: npm ci - - name: Lint and format check - run: npm run check + - name: Lint and format check + run: npm run check - - name: Build - run: npm run build + - name: Build + run: npm run build - - name: Test - run: npm test - env: - CI: true + - name: Test + run: npm test + env: + CI: true diff --git a/.github/workflows/npmpublish.yml b/.github/workflows/npmpublish.yml deleted file mode 100644 index 2f23dfc..0000000 --- a/.github/workflows/npmpublish.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Node.js Package - -on: - release: - types: [created] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - cache: 'npm' - - run: npm ci - - run: npm run check - - run: npm run build - - run: npm test - - publish-npm: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: npm run configure-npm - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: 'https://registry.npmjs.org' - cache: 'npm' - - run: npm ci - - run: npm run build - - run: npm publish --access=public - env: - NODE_AUTH_TOKEN: ${{ secrets.npm_token }} - - publish-gpr: - needs: build - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 22 - registry-url: 'https://npm.pkg.github.com' - scope: '@lopatnov' - cache: 'npm' - - run: npm run configure-gpr - - run: npm ci - - run: npm run build - - run: npm publish --access=public - env: - NODE_AUTH_TOKEN: ${{ secrets.GPR_PAT }} diff --git a/.github/workflows/publish-github.yml b/.github/workflows/publish-github.yml new file mode 100644 index 0000000..cb29b8b --- /dev/null +++ b/.github/workflows/publish-github.yml @@ -0,0 +1,36 @@ +name: Publish NPM package to GitHub Packages + +on: + push: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Use Node.js 24.x + uses: actions/setup-node@v5 + with: + node-version: "24.x" + cache: "npm" + - run: npm ci + - run: npm run build + - run: npm test + + publish-github: + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24.x" + registry-url: "https://npm.pkg.github.com" + scope: "@lopatnov" + - run: npm publish --access=public + env: + NODE_AUTH_TOKEN: ${{ secrets.github_token }} diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml new file mode 100644 index 0000000..20ff8bb --- /dev/null +++ b/.github/workflows/publish-npm.yml @@ -0,0 +1,35 @@ +name: Publish NPM package to npmjs.org + +on: + push: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Use Node.js 24.x + uses: actions/setup-node@v5 + with: + node-version: "24.x" + cache: "npm" + - run: npm ci + - run: npm run build + - run: npm test + + publish-npm: + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24.x" + registry-url: "https://registry.npmjs.org" + - run: npm publish --provenance --access=public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b78fb..c67dedd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ All notable changes to this project will be documented in this file. +## [2.1.0] - 2026-02-24 + +### Added + +- Table of Contents added to README + +### Changed + +- Migrated from Jest + ts-jest to Vitest — native ESM, native TypeScript, no transform workarounds +- Removed `jest`, `jest-config`, `ts-jest`, `@types/jest` dependencies +- `jest.config.js` replaced with `vitest.config.js` +- Test files converted from `.cjs` to `.ts` +- Updated `@lopatnov/get-internal-type` to 2.2.1 +- Updated `@lopatnov/rollup-plugin-uglify` to 4.1.3 + ## [2.0.0] - 2026-02-14 ### Added diff --git a/README.md b/README.md index 1cb215d..611b376 100644 --- a/README.md +++ b/README.md @@ -1,259 +1,272 @@ -# @lopatnov/javascripttostring - -[](https://www.npmjs.com/package/@lopatnov/javascripttostring) -[](https://www.npmjs.com/package/@lopatnov/javascripttostring) -[](https://github.com/lopatnov/jsToString/blob/master/LICENSE) -[](https://www.typescriptlang.org/) -[](https://github.com/lopatnov/jsToString/stargazers) - -A TypeScript library that converts any JavaScript runtime value into its string source code representation. Supports objects, arrays, functions, circular references, cross-references, and more. - -## Installation - -```bash -npm install @lopatnov/javascripttostring -``` - -### Browser (CDN) - -```html - -``` - -## Usage - -### ES Modules - -```typescript -import javaScriptToString from "@lopatnov/javascripttostring"; -``` - -### CommonJS - -```javascript -const javaScriptToString = require("@lopatnov/javascripttostring"); -``` - -### Browser (UMD) - -```javascript -const javaScriptToString = window.javaScriptToString; -``` - -## API - -### javaScriptToString(value, options?): string - -Converts a JavaScript value to its string source code representation. - -| Parameter | Type | Description | -|-----------|------|-------------| -| `value` | `any` | The value to convert | -| `options` | `IJ2SOptions` | Optional configuration | - -**Returns:** `string` - Source code representation that can be evaluated back to the original value - -### Options - -| Option | Type | Default | Description | -|--------|------|---------|-------------| -| `includeFunctionProperties` | `boolean` | `true` | Include function's own properties | -| `includeFunctionPrototype` | `boolean` | `true` | Include function's prototype properties | -| `includeBuffers` | `boolean` | `true` | Include ArrayBuffer and TypedArray contents | -| `nestedObjectsAmount` | `number` | `Infinity` | Max depth for nested objects | -| `nestedArraysAmount` | `number` | `Infinity` | Max depth for nested arrays | -| `nestedFunctionsAmount` | `number` | `Infinity` | Max depth for nested functions | -| `throwOnNonSerializable` | `boolean` | `false` | Throw an error for non-serializable values (Promise, Generator, WeakRef, WeakMap, WeakSet, FinalizationRegistry) | - -## Examples - -### Primitives - -```typescript -javaScriptToString("Hello world"); // '"Hello world"' -javaScriptToString(42); // '42' -javaScriptToString(true); // 'true' -javaScriptToString(undefined); // 'undefined' -javaScriptToString(null); // 'null' -``` - -### Arrays - -```typescript -javaScriptToString(["Hello", "World"]); -// '["Hello", "World"]' -``` - -### Objects - -```typescript -javaScriptToString({ - name: "Alex", - friends: ["Shurik", "Hola"], - greet: () => { - console.log("How you doing?"); - } -}); -// '{name: "Alex", friends: ["Shurik", "Hola"], greet: () => { console.log("How you doing?"); }}' -``` - -### Functions with Properties - -```typescript -function Simple(title) { - this.title = title || "world"; -} -Simple.count = 0; -Simple.prototype.show = function () { - Simple.count++; - console.log("title =", this.title); -}; - -javaScriptToString(Simple); -// '(function(){ var Simple = function Simple(title) { ... }; Simple.count = 0; Simple.prototype.show = function(){ ... }; return Simple; }())' -``` - -### Circular References - -Objects that reference themselves are fully supported: - -```typescript -var x = [1, 2, 3]; -x[0] = x; - -javaScriptToString(x); -// '(function(){ var ___ref1 = [null, 2, 3]; ___ref1[0] = ___ref1; return ___ref1; }())' -``` - -### Cross-References - -Objects shared between different branches are preserved as references: - -```typescript -var shared = { value: 42 }; -var obj = { a: shared, b: shared }; - -javaScriptToString(obj); -// Generates code where obj.a === obj.b (same reference): -// (function(){ var ___ref1 = { -// a: { value: 42 }, -// b: null -// }; ___ref1.b = ___ref1.a; return ___ref1; }()) -``` - -### Using with Web Workers - -Combine with [@lopatnov/worker-from-string](https://www.npmjs.com/package/@lopatnov/worker-from-string) to serialize functions and data for execution in a Web Worker: - -```typescript -import javaScriptToString from "@lopatnov/javascripttostring"; -import workerFromString from "@lopatnov/worker-from-string"; - -// Function with attached lookup data -function classify(value) { - const range = classify.ranges.find(r => value >= r.min && value < r.max); - return range ? range.label : "unknown"; -} -classify.ranges = [ - { min: 0, max: 30, label: "cold" }, - { min: 30, max: 60, label: "warm" }, - { min: 60, max: 100, label: "hot" }, -]; - -// Serialize and send to a worker -// javaScriptToString preserves the function AND its properties: -const code = javaScriptToString(classify); - -const worker = workerFromString(` - const classify = ${code}; - self.onmessage = (e) => postMessage(classify(e.data)); -`); - -worker.onmessage = (e) => console.log(e.data); -worker.postMessage(45); // "warm" -``` - -### Restoring Values - -The generated string can be evaluated back to a working JavaScript value: - -```typescript -var original = { name: "test" }; -original.self = original; - -var code = javaScriptToString(original); -var restored = Function("return " + code)(); - -console.log(restored.self === restored); // true -console.log(restored.name); // "test" -``` - -## Supported Types - -| Type | Example | Notes | -|------|---------|-------| -| Primitives | `string`, `number`, `boolean`, `undefined`, `null` | Including `-0` and `NaN` | -| BigInt | `BigInt(123)` | | -| Symbol | `Symbol("desc")`, `Symbol.for("key")` | Registry symbols preserved | -| RegExp | `/pattern/gi` | `lastIndex` preserved when non-zero | -| Date | `new Date("...")` | Invalid dates → `new Date(NaN)` | -| Error | `new Error()`, `new TypeError()` | TypeError, RangeError, ReferenceError, SyntaxError, URIError, EvalError | -| Array | `[1, 2, 3]` | Sparse arrays preserved | -| Object | `{ key: "value" }` | Including `Object.create(null)` | -| Function | `function() {}`, `() => {}`, `async function() {}` | Properties and prototype included | -| Generator Function | `function*() {}`, `async function*() {}` | | -| Map | `new Map([["key", "value"]])` | | -| Set | `new Set([1, 2, 3])` | | -| TypedArray | `Int8Array`, `Float64Array`, etc. | | -| ArrayBuffer | `new ArrayBuffer(8)`, `SharedArrayBuffer` | | -| DataView | `new DataView(buffer)` | | - -### Non-serializable Types - -The following types cannot be serialized and return `"undefined"` by default. Use `throwOnNonSerializable: true` to throw an error instead: - -`Promise`, `Generator`, `WeakRef`, `WeakMap`, `WeakSet`, `FinalizationRegistry` - -## Demo - -Try the library interactively: - -| | Link | -|---|---| -| Interactive Demo | [demo/index.html](./demo/index.html) | -| RunKit Playground | [runkit.com](https://npm.runkit.com/%40lopatnov%2Fjavascripttostring) | - -## Documentation - -| | Link | -|---|---| -| API Reference | [docs/index.html](./docs/index.html) | -| Changelog | [CHANGELOG.md](./CHANGELOG.md) | - -## Related Packages - -| Package | Description | -|---|---| -| [@lopatnov/worker-from-string](https://www.npmjs.com/package/@lopatnov/worker-from-string) | Create Web Workers from strings — pairs well with `javaScriptToString` | -| [@lopatnov/get-internal-type](https://www.npmjs.com/package/@lopatnov/get-internal-type) | Runtime type detection used internally by this library | - -## Contributing - -Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. - -## License - -[Apache-2.0](LICENSE) - -Copyright 2019-2026 Oleksandr Lopatnov - ---- - -### Author - -**Oleksandr Lopatnov** - -[](https://www.linkedin.com/in/lopatnov/) -[](https://github.com/lopatnov) - -If you find this project useful, please consider giving it a star on GitHub! +# @lopatnov/javascripttostring + +[](https://www.npmjs.com/package/@lopatnov/javascripttostring) +[](https://www.npmjs.com/package/@lopatnov/javascripttostring) +[](https://github.com/lopatnov/jsToString/blob/master/LICENSE) +[](https://www.typescriptlang.org/) +[](https://github.com/lopatnov/jsToString/stargazers) + +A TypeScript library that converts any JavaScript runtime value into its string source code representation. Supports objects, arrays, functions, circular references, cross-references, and more. + +## Table of Contents + +- [Installation](#installation) +- [Usage](#usage) +- [API](#api) +- [Examples](#examples) +- [Supported Types](#supported-types) +- [Demo](#demo) +- [Documentation](#documentation) +- [Related Packages](#related-packages) +- [Contributing](#contributing) +- [Built With](#built-with) +- [License](#license) + +## Installation + +```bash +npm install @lopatnov/javascripttostring +``` + +### Browser (CDN) + +```html + +``` + +## Usage + +### ES Modules + +```typescript +import javaScriptToString from "@lopatnov/javascripttostring"; +``` + +### CommonJS + +```javascript +const javaScriptToString = require("@lopatnov/javascripttostring"); +``` + +### Browser (UMD) + +```javascript +const javaScriptToString = window.javaScriptToString; +``` + +## API + +### javaScriptToString(value, options?): string + +Converts a JavaScript value to its string source code representation. + +| Parameter | Type | Description | +|-----------|------|-------------| +| `value` | `any` | The value to convert | +| `options` | `IJ2SOptions` | Optional configuration | + +**Returns:** `string` - Source code representation that can be evaluated back to the original value + +### Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `includeFunctionProperties` | `boolean` | `true` | Include function's own properties | +| `includeFunctionPrototype` | `boolean` | `true` | Include function's prototype properties | +| `includeBuffers` | `boolean` | `true` | Include ArrayBuffer and TypedArray contents | +| `nestedObjectsAmount` | `number` | `Infinity` | Max depth for nested objects | +| `nestedArraysAmount` | `number` | `Infinity` | Max depth for nested arrays | +| `nestedFunctionsAmount` | `number` | `Infinity` | Max depth for nested functions | +| `throwOnNonSerializable` | `boolean` | `false` | Throw an error for non-serializable values (Promise, Generator, WeakRef, WeakMap, WeakSet, FinalizationRegistry) | + +## Examples + +### Primitives + +```typescript +javaScriptToString("Hello world"); // '"Hello world"' +javaScriptToString(42); // '42' +javaScriptToString(true); // 'true' +javaScriptToString(undefined); // 'undefined' +javaScriptToString(null); // 'null' +``` + +### Arrays + +```typescript +javaScriptToString(["Hello", "World"]); +// '["Hello", "World"]' +``` + +### Objects + +```typescript +javaScriptToString({ + name: "Alex", + friends: ["Shurik", "Hola"], + greet: () => { + console.log("How you doing?"); + } +}); +// '{name: "Alex", friends: ["Shurik", "Hola"], greet: () => { console.log("How you doing?"); }}' +``` + +### Functions with Properties + +```typescript +function Simple(title) { + this.title = title || "world"; +} +Simple.count = 0; +Simple.prototype.show = function () { + Simple.count++; + console.log("title =", this.title); +}; + +javaScriptToString(Simple); +// '(function(){ var Simple = function Simple(title) { ... }; Simple.count = 0; Simple.prototype.show = function(){ ... }; return Simple; }())' +``` + +### Circular References + +Objects that reference themselves are fully supported: + +```typescript +var x = [1, 2, 3]; +x[0] = x; + +javaScriptToString(x); +// '(function(){ var ___ref1 = [null, 2, 3]; ___ref1[0] = ___ref1; return ___ref1; }())' +``` + +### Cross-References + +Objects shared between different branches are preserved as references: + +```typescript +var shared = { value: 42 }; +var obj = { a: shared, b: shared }; + +javaScriptToString(obj); +// Generates code where obj.a === obj.b (same reference): +// (function(){ var ___ref1 = { +// a: { value: 42 }, +// b: null +// }; ___ref1.b = ___ref1.a; return ___ref1; }()) +``` + +### Using with Web Workers + +Combine with [@lopatnov/worker-from-string](https://www.npmjs.com/package/@lopatnov/worker-from-string) to serialize functions and data for execution in a Web Worker: + +```typescript +import javaScriptToString from "@lopatnov/javascripttostring"; +import workerFromString from "@lopatnov/worker-from-string"; + +// Function with attached lookup data +function classify(value) { + const range = classify.ranges.find(r => value >= r.min && value < r.max); + return range ? range.label : "unknown"; +} +classify.ranges = [ + { min: 0, max: 30, label: "cold" }, + { min: 30, max: 60, label: "warm" }, + { min: 60, max: 100, label: "hot" }, +]; + +// Serialize and send to a worker +// javaScriptToString preserves the function AND its properties: +const code = javaScriptToString(classify); + +const worker = workerFromString(` + const classify = ${code}; + self.onmessage = (e) => postMessage(classify(e.data)); +`); + +worker.onmessage = (e) => console.log(e.data); +worker.postMessage(45); // "warm" +``` + +### Restoring Values + +The generated string can be evaluated back to a working JavaScript value: + +```typescript +var original = { name: "test" }; +original.self = original; + +var code = javaScriptToString(original); +var restored = Function("return " + code)(); + +console.log(restored.self === restored); // true +console.log(restored.name); // "test" +``` + +## Supported Types + +| Type | Example | Notes | +|------|---------|-------| +| Primitives | `string`, `number`, `boolean`, `undefined`, `null` | Including `-0` and `NaN` | +| BigInt | `BigInt(123)` | | +| Symbol | `Symbol("desc")`, `Symbol.for("key")` | Registry symbols preserved | +| RegExp | `/pattern/gi` | `lastIndex` preserved when non-zero | +| Date | `new Date("...")` | Invalid dates → `new Date(NaN)` | +| Error | `new Error()`, `new TypeError()` | TypeError, RangeError, ReferenceError, SyntaxError, URIError, EvalError | +| Array | `[1, 2, 3]` | Sparse arrays preserved | +| Object | `{ key: "value" }` | Including `Object.create(null)` | +| Function | `function() {}`, `() => {}`, `async function() {}` | Properties and prototype included | +| Generator Function | `function*() {}`, `async function*() {}` | | +| Map | `new Map([["key", "value"]])` | | +| Set | `new Set([1, 2, 3])` | | +| TypedArray | `Int8Array`, `Float64Array`, etc. | | +| ArrayBuffer | `new ArrayBuffer(8)`, `SharedArrayBuffer` | | +| DataView | `new DataView(buffer)` | | + +### Non-serializable Types + +The following types cannot be serialized and return `"undefined"` by default. Use `throwOnNonSerializable: true` to throw an error instead: + +`Promise`, `Generator`, `WeakRef`, `WeakMap`, `WeakSet`, `FinalizationRegistry` + +## Demo + +Try the library interactively: + +- [Interactive Demo](./demo/index.html) +- [RunKit Playground](https://npm.runkit.com/%40lopatnov%2Fjavascripttostring) + +## Documentation + +- [API Reference](./docs/index.html) +- [Changelog](./CHANGELOG.md) + +## Related Packages + +| Package | Description | +|---|---| +| [@lopatnov/worker-from-string](https://www.npmjs.com/package/@lopatnov/worker-from-string) | Create Web Workers from strings — pairs well with `javaScriptToString` | +| [@lopatnov/get-internal-type](https://www.npmjs.com/package/@lopatnov/get-internal-type) | Runtime type detection used internally by this library | + +## Contributing + +Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request. + +- Bug reports → [open an issue](https://github.com/lopatnov/jsToString/issues) +- Found it useful? A [star on GitHub](https://github.com/lopatnov/jsToString) helps others discover the project + +--- + +## Built With + +- [TypeScript](https://www.typescriptlang.org/) — strict typing throughout +- [Rollup](https://rollupjs.org/) — bundled to ESM, CJS, and UMD formats +- [Biome](https://biomejs.dev/) — linting and formatting +- [Vitest](https://vitest.dev/) — unit testing +- [TypeDoc](https://typedoc.org/) — API documentation generation +- [@lopatnov/get-internal-type](https://github.com/lopatnov/get-internal-type) — reliable runtime type detection + +--- + +## License + +[Apache-2.0](https://github.com/lopatnov/jsToString/blob/master/LICENSE) © 2019–2026 [Oleksandr Lopatnov](https://github.com/lopatnov) · [LinkedIn](https://www.linkedin.com/in/lopatnov/) diff --git a/docs/functions/default.html b/docs/functions/default.html index a7364bf..581e198 100644 --- a/docs/functions/default.html +++ b/docs/functions/default.html @@ -1,4 +1,4 @@
the value of any type
Optionaloptions: IJ2SOptions[optional] The options of conversion
-
Converts JavaScript value to string