From 1784e437b6173a75168eca546647c3e24be49ab9 Mon Sep 17 00:00:00 2001
From: wellwelwel <46850407+wellwelwel@users.noreply.github.com>
Date: Thu, 22 Aug 2024 17:53:54 -0300
Subject: [PATCH] feat: support for direct (global) browser import
---
.gitignore | 1 +
.prettierignore | 1 +
CONTRIBUTING.md | 2 +
README.md | 14 ++-
package-lock.json | 68 +++++++++++++
package.json | 21 ++++-
src/index.ts | 162 +++++++++++++++----------------
tools/browserfy.ts | 231 +++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 413 insertions(+), 87 deletions(-)
create mode 100644 tools/browserfy.ts
diff --git a/.gitignore b/.gitignore
index 9c53102..2a10141 100755
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ node_modules
.DS_Store
deno.lock
/lib
+/browser
diff --git a/.prettierignore b/.prettierignore
index 5f9c485..67839f0 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,2 +1,3 @@
/lib
/CHANGELOG.md
+/browser
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6f204c2..956339e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -53,6 +53,8 @@ bun run test:bun
deno task test:deno
```
+Also, run `npm run build` to compile and run _E2E_ tests in the virtual browser.
+
---
### Lint
diff --git a/README.md b/README.md
index dcba901..ba49d44 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[![GitHub Workflow Status (Bun)](https://img.shields.io/github/actions/workflow/status/wellwelwel/jsonc.min/ci_bun.yml?event=push&label=&branch=main&logo=bun&logoColor=ffffff&color=f368e0)](https://github.com/wellwelwel/jsonc.min/actions/workflows/ci_bun.yml?query=branch%3Amain)
[![GitHub Workflow Status (Deno)](https://img.shields.io/github/actions/workflow/status/wellwelwel/jsonc.min/ci_deno.yml?event=push&label=&branch=main&logo=deno&logoColor=ffffff&color=079992)](https://github.com/wellwelwel/jsonc.min/actions/workflows/ci_deno.yml?query=branch%3Amain)
-✨ Faster and safer JSON and JSONC minify, parse and stringify for JavaScript (Browser compatible) — **2.6kB**.
+✨ Faster and safer JSON and JSONC minify, parse and stringify for JavaScript (Browser compatible) — **2.3kB**.
@@ -31,8 +31,6 @@
## Install
-[![Install Size](https://packagephobia.com/badge?p=jsonc.min)](https://pkg-size.dev/jsonc.min)
-
```bash
# Node.js
npm i jsonc.min
@@ -54,14 +52,24 @@ deno add npm:jsonc.min
### Import
+#### ES Modules
+
```js
import { JSONC } from 'jsonc.min';
```
+#### CommonJS
+
```js
const { JSONC } = require('jsonc.min');
```
+#### Browser
+
+```html
+
+```
+
---
### `toJSON`
diff --git a/package-lock.json b/package-lock.json
index 0687980..aca1155 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,9 @@
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "^1.8.3",
+ "@types/node": "^22.5.0",
+ "esbuild": "^0.23.1",
+ "happy-dom": "^15.0.0",
"packages-update": "^2.0.0",
"poku": "^2.5.0",
"prettier": "^3.3.3",
@@ -598,6 +601,29 @@
"node": ">=18"
}
},
+ "node_modules/@types/node": {
+ "version": "22.5.0",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz",
+ "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/esbuild": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
@@ -666,6 +692,21 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
+ "node_modules/happy-dom": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.0.0.tgz",
+ "integrity": "sha512-DsvANUcxxY20iCo3Yllm7dqwzPVPduGfVFxa7mONwMBLczFeQgkN0LpDir1kIY322JMh+hrcPV3aGLyHCESDlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^4.5.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-mimetype": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
"node_modules/packages-update": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/packages-update/-/packages-update-2.0.0.tgz",
@@ -759,6 +800,33 @@
"engines": {
"node": ">=14.17"
}
+ },
+ "node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
}
}
}
diff --git a/package.json b/package.json
index 56f8233..f89ccbe 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "jsonc.min",
"version": "1.0.1",
- "description": "✨ Faster and safer JSON and JSONC minify, parse and stringify for JavaScript (Browser compatible) — 2.6kB.",
+ "description": "✨ Faster and safer JSON and JSONC minify, parse and stringify for JavaScript (Browser compatible) — 2.3kB.",
"main": "./lib/index.js",
"license": "MIT",
"repository": {
@@ -17,6 +17,7 @@
"url": "https://github.com/sponsors/wellwelwel"
},
"files": [
+ "browser",
"lib"
],
"engines": {
@@ -25,17 +26,23 @@
"deno": ">=1.30.0"
},
"scripts": {
- "build": "tsc",
+ "prebuild": "rm -rf ./browser ./lib",
+ "build:browser": "tsx tools/browserfy.ts",
+ "build": "tsc && npm run build:browser",
"test:node": "poku --node -p",
"test:bun": "poku --bun -p",
"test:deno": "poku --deno -p",
"lint": "npx @biomejs/biome lint && prettier --check .",
"lint:fix": "npx @biomejs/biome lint --write && prettier --write .github/workflows/*.yml .",
"update": "pu minor && npm i && npm audit fix",
- "postupdate": "npm run lint:fix"
+ "postupdate": "npm run lint:fix",
+ "size": "ls -lh lib/index.js | awk '{print $5}'"
},
"devDependencies": {
"@biomejs/biome": "^1.8.3",
+ "@types/node": "^22.5.0",
+ "esbuild": "^0.23.1",
+ "happy-dom": "^15.0.0",
"packages-update": "^2.0.0",
"poku": "^2.5.0",
"prettier": "^3.3.3",
@@ -50,6 +57,12 @@
"parse",
"parser",
"stringify",
- "jsonc-to-json"
+ "jsonc-to-json",
+ "node",
+ "nodejs",
+ "bun",
+ "deno",
+ "typescript",
+ "browser"
]
}
diff --git a/src/index.ts b/src/index.ts
index 6e626df..ce372a6 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,105 +1,107 @@
-class JsoncProcessor {
- toJSON(content: string): string {
- const length = content.length;
-
- let inBlockComment = false;
- let inString = false;
- let skipChar = false;
- let result = '';
-
- for (let i = 0; i < length; i++) {
- const char = content[i];
+export const JSONC = (() => {
+ class JsoncProcessor {
+ toJSON(content: string): string {
+ const length = content.length;
+
+ let inBlockComment = false;
+ let inString = false;
+ let skipChar = false;
+ let result = '';
+
+ for (let i = 0; i < length; i++) {
+ const char = content[i];
+
+ if (skipChar) {
+ skipChar = false;
+ continue;
+ }
- if (skipChar) {
- skipChar = false;
- continue;
- }
+ if (inBlockComment) {
+ if (char === '*' && content[i + 1] === '/') {
+ inBlockComment = false;
+ skipChar = true;
+ }
- if (inBlockComment) {
- if (char === '*' && content[i + 1] === '/') {
- inBlockComment = false;
- skipChar = true;
+ continue;
}
- continue;
- }
+ if (inString) {
+ if (char === '"' && content[i - 1] !== '\\') {
+ inString = false;
+ }
- if (inString) {
- if (char === '"' && content[i - 1] !== '\\') {
- inString = false;
+ result += char;
+ continue;
}
- result += char;
- continue;
- }
+ if (char === '"') {
+ inString = true;
+ result += char;
+ continue;
+ }
- if (char === '"') {
- inString = true;
- result += char;
- continue;
- }
+ if (char === '/' && content[i + 1] === '*') {
+ inBlockComment = true;
+ skipChar = true;
+ continue;
+ }
- if (char === '/' && content[i + 1] === '*') {
- inBlockComment = true;
- skipChar = true;
- continue;
- }
+ if (char === '/' && content[i + 1] === '/') {
+ while (i < length && content[i] !== '\n') {
+ i++;
+ }
- if (char === '/' && content[i + 1] === '/') {
- while (i < length && content[i] !== '\n') {
- i++;
+ continue;
}
- continue;
+ result += char;
}
- result += char;
+ return result;
}
- return result;
- }
+ parse(
+ text: string,
+ reviver?: (this: unknown, key: string, value: unknown) => unknown
+ ): T {
+ const cleanContent = this.toJSON(text);
- parse(
- text: string,
- reviver?: (this: unknown, key: string, value: unknown) => unknown
- ): T {
- const cleanContent = this.toJSON(text);
+ return JSON.parse(cleanContent, reviver);
+ }
- return JSON.parse(cleanContent, reviver);
- }
+ stringify(
+ value: unknown,
+ replacer?: (this: unknown, key: string, value: unknown) => unknown,
+ space?: string | number
+ ): string;
+ stringify(
+ value: unknown,
+ replacer?: (number | string)[] | null,
+ space?: string | number
+ ): string;
+ stringify(
+ value: unknown,
+ replacer?:
+ | ((this: unknown, key: string, value: unknown) => unknown)
+ | (number | string)[]
+ | null,
+ space?: string | number
+ ): string {
+ const source = typeof value === 'string' ? this.parse(value) : value;
+
+ if (typeof replacer === 'function') {
+ return JSON.stringify(source, replacer, space);
+ }
- stringify(
- value: unknown,
- replacer?: (this: unknown, key: string, value: unknown) => unknown,
- space?: string | number
- ): string;
- stringify(
- value: unknown,
- replacer?: (number | string)[] | null,
- space?: string | number
- ): string;
- stringify(
- value: unknown,
- replacer?:
- | ((this: unknown, key: string, value: unknown) => unknown)
- | (number | string)[]
- | null,
- space?: string | number
- ): string {
- const source = typeof value === 'string' ? this.parse(value) : value;
-
- if (typeof replacer === 'function') {
return JSON.stringify(source, replacer, space);
}
- return JSON.stringify(source, replacer, space);
- }
+ minify(content: string): string {
+ const parsedConfig = this.parse(content);
- minify(content: string): string {
- const parsedConfig = this.parse(content);
-
- return JSON.stringify(parsedConfig, null, 0);
+ return JSON.stringify(parsedConfig, null, 0);
+ }
}
-}
-export const JSONC = new JsoncProcessor();
+ return new JsoncProcessor();
+})();
diff --git a/tools/browserfy.ts b/tools/browserfy.ts
new file mode 100644
index 0000000..2a0d456
--- /dev/null
+++ b/tools/browserfy.ts
@@ -0,0 +1,231 @@
+import { readFile } from 'node:fs/promises';
+import esbuild from 'esbuild';
+import { Window } from 'happy-dom';
+import { assert, test } from 'poku';
+
+(async () => {
+ const contents = (await readFile('src/index.ts', 'utf8'))
+ .replace(/export/im, '')
+ .replace(/(let|const) /gim, 'var ');
+
+ await esbuild.build({
+ stdin: {
+ contents,
+ loader: 'ts',
+ },
+ platform: 'browser',
+ target: 'es6',
+ bundle: false,
+ outfile: 'browser/jsonc.min.js',
+ minify: true,
+ });
+
+ test('Browser (E2E)', async () => {
+ const content = `
+ /** Configuração do Servidor */
+
+ /** Configuração do Servidor **/
+ /**
+ Configuração do Servidor
+ **/
+
+ /*
+ Configuração do Servidor
+ */
+ /**
+ Configuração do Servidor
+ */
+ /*
+ Configuração do Servidor
+ **/
+ /**
+ * Configuração do Servidor # //
+ **/
+
+ {
+ // Configuração do Servidor
+ "server": {
+ "host": "localhost", // Endereço do servidor
+ "port": 8080, /* Porta do servidor */
+
+ /* Configurações de segurança */
+ "security": {
+ "https": true, // Habilitar HTTPS
+ "certificatePath": "/path/to/cert.pem", /* Caminho para o certificado */
+ "keyPath": "/path/to/key.pem" // Caminho para a chave privada
+ }
+ },
+
+ // Configurações de banco de dados
+ "database": {
+ "type": "mysql", /* Tipo de banco de dados */
+ "host": "db.example.com", // Endereço do banco de dados
+ "port": 3306, /* Porta do banco de dados */
+ "username": "db_user", // Nome de usuário do banco de dados
+ "password": "db_pass", /* Senha do banco de dados */
+ "name": "app_db", // Nome do banco de dados
+
+ /* Configurações avançadas */
+ "pool": {
+ "min": 2, // Conexões mínimas no pool
+ "max": 10, /* Conexões máximas no pool */
+ "idleTimeoutMillis": 30000 // Tempo máximo de ociosidade (ms)
+ }
+ },
+
+ // Configurações de log
+ "logging": {
+ "level": "debug", /* Nível de log */
+ "file": "/var/log/app.log", // Caminho do arquivo de log
+ "maxSize": "10m", /* Tamanho máximo do arquivo de log */
+ "maxFiles": "5", // Número máximo de arquivos de log
+ "format": "json" /* Formato do log */
+ },
+
+ /* Configurações de API */
+ "api": {
+ "basePath": "/api/v1", // Caminho base da API
+ "timeout": 5000, /* Timeout da API (ms) */
+ "retries": 3 // Número de tentativas em caso de falha
+ },
+
+ // Configurações de autenticação
+ "auth": {
+ "jwtSecret": "supersecretkey", /* Chave secreta para JWT */
+ "tokenExpiration": "1h", // Expiração do token
+ "refreshTokenExpiration": "7d" /* Expiração do token de atualização */
+ },
+
+ /* Configurações de cache */
+ "cache": {
+ "enabled": true, // Habilitar cache
+ "type": "redis", /* Tipo de cache */
+ "host": "cache.example.com", // Endereço do cache
+ "port": 6379, /* Porta do cache */
+ "ttl": 3600 // Tempo de vida do cache (s)
+ }
+ }
+`;
+
+ const window = new Window();
+ const jsoncMin = await readFile('browser/jsonc.min.js', 'utf8');
+
+ window.eval(jsoncMin);
+
+ assert.deepEqual(
+ // @ts-expect-error
+ window.JSONC.parse(content),
+ {
+ server: {
+ host: 'localhost',
+ port: 8080,
+ security: {
+ https: true,
+ certificatePath: '/path/to/cert.pem',
+ keyPath: '/path/to/key.pem',
+ },
+ },
+ database: {
+ type: 'mysql',
+ host: 'db.example.com',
+ port: 3306,
+ username: 'db_user',
+ password: 'db_pass',
+ name: 'app_db',
+ pool: { min: 2, max: 10, idleTimeoutMillis: 30000 },
+ },
+ logging: {
+ level: 'debug',
+ file: '/var/log/app.log',
+ maxSize: '10m',
+ maxFiles: '5',
+ format: 'json',
+ },
+ api: { basePath: '/api/v1', timeout: 5000, retries: 3 },
+ auth: {
+ jwtSecret: 'supersecretkey',
+ tokenExpiration: '1h',
+ refreshTokenExpiration: '7d',
+ },
+ cache: {
+ enabled: true,
+ type: 'redis',
+ host: 'cache.example.com',
+ port: 6379,
+ ttl: 3600,
+ },
+ },
+ 'paser'
+ );
+
+ assert.equal(
+ // @ts-expect-error
+ window.JSONC.minify(content),
+ `{"server":{"host":"localhost","port":8080,"security":{"https":true,"certificatePath":"/path/to/cert.pem","keyPath":"/path/to/key.pem"}},"database":{"type":"mysql","host":"db.example.com","port":3306,"username":"db_user","password":"db_pass","name":"app_db","pool":{"min":2,"max":10,"idleTimeoutMillis":30000}},"logging":{"level":"debug","file":"/var/log/app.log","maxSize":"10m","maxFiles":"5","format":"json"},"api":{"basePath":"/api/v1","timeout":5000,"retries":3},"auth":{"jwtSecret":"supersecretkey","tokenExpiration":"1h","refreshTokenExpiration":"7d"},"cache":{"enabled":true,"type":"redis","host":"cache.example.com","port":6379,"ttl":3600}}`,
+ 'minify'
+ );
+
+ assert.equal(
+ // @ts-expect-error
+ window.JSONC.stringify(content, null, 2),
+ `{
+ "server": {
+ "host": "localhost",
+ "port": 8080,
+ "security": {
+ "https": true,
+ "certificatePath": "/path/to/cert.pem",
+ "keyPath": "/path/to/key.pem"
+ }
+ },
+ "database": {
+ "type": "mysql",
+ "host": "db.example.com",
+ "port": 3306,
+ "username": "db_user",
+ "password": "db_pass",
+ "name": "app_db",
+ "pool": {
+ "min": 2,
+ "max": 10,
+ "idleTimeoutMillis": 30000
+ }
+ },
+ "logging": {
+ "level": "debug",
+ "file": "/var/log/app.log",
+ "maxSize": "10m",
+ "maxFiles": "5",
+ "format": "json"
+ },
+ "api": {
+ "basePath": "/api/v1",
+ "timeout": 5000,
+ "retries": 3
+ },
+ "auth": {
+ "jwtSecret": "supersecretkey",
+ "tokenExpiration": "1h",
+ "refreshTokenExpiration": "7d"
+ },
+ "cache": {
+ "enabled": true,
+ "type": "redis",
+ "host": "cache.example.com",
+ "port": 6379,
+ "ttl": 3600
+ }
+}`,
+ 'stringify (from a string)'
+ );
+
+ assert.equal(
+ // @ts-expect-error
+ window.JSONC.stringify({ a: 123 }, null, 2),
+ `{
+ "a": 123
+}`,
+ 'stringify'
+ );
+ });
+})();