From ea96af8cf1da0e39afb47fff10067f32db0dffdf Mon Sep 17 00:00:00 2001 From: Remco Haszing Date: Mon, 21 Aug 2023 10:39:38 +0200 Subject: [PATCH] Fix ESM exports This uses the `package.json` `exports` field to support native ESM. The `module` field is redundant and can even lead to compatibility issues, so it was removed. TypeScript project references are used for building the project with two different settings. An additional `package.json` is written to mark the ESM output as actual ESM. Since ESM requires file extensions, those were added to all imports. Mocha has been reconfigured to run tests for both CJS and ESM. Also ESLint has been configured to lint all files in the repo, not just a shell specific glob pattern. As a result, this change contains some small ESLint fixes. --- .eslintrc.json | 39 +++++++++++++--------- .mocharc.json | 8 +++-- build/fix-esm.mjs | 12 +++++++ build/remove-sourcemap-refs.js | 4 +-- package.json | 22 +++++++----- src/impl/edit.ts | 6 ++-- src/impl/format.ts | 6 ++-- src/impl/parser.ts | 4 +-- src/impl/scanner.ts | 2 +- src/main.ts | 8 ++--- src/test/edit.test.ts | 10 +++--- src/test/format.test.ts | 10 +++--- src/test/json.test.ts | 8 ++--- src/tsconfig.json | 16 --------- tsconfig.cjs.json | 7 ++++ src/tsconfig.esm.json => tsconfig.esm.json | 10 +++--- tsconfig.json | 7 ++++ 17 files changed, 103 insertions(+), 76 deletions(-) create mode 100755 build/fix-esm.mjs delete mode 100644 src/tsconfig.json create mode 100644 tsconfig.cjs.json rename src/tsconfig.esm.json => tsconfig.esm.json (60%) create mode 100644 tsconfig.json diff --git a/.eslintrc.json b/.eslintrc.json index 41f516b..421675b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,24 +1,11 @@ { "root": true, - "parser": "@typescript-eslint/parser", "parserOptions": { - "ecmaVersion": 6, + "ecmaVersion": "latest", "sourceType": "module" }, - "plugins": [ - "@typescript-eslint" - ], "rules": { - "@typescript-eslint/naming-convention": [ - "warn", - { - "selector": "typeLike", - "format": [ - "PascalCase" - ] - } - ], "@typescript-eslint/semi": "warn", "curly": "warn", "eqeqeq": "warn", @@ -27,5 +14,25 @@ "no-unused-expressions": "warn", "no-duplicate-imports": "warn", "new-parens": "warn" - } -} \ No newline at end of file + }, + "overrides": [ + { + "files": ["*.ts"], + "plugins": [ + "@typescript-eslint" + ], + "parser": "@typescript-eslint/parser", + "rules": { + "@typescript-eslint/naming-convention": [ + "warn", + { + "selector": "typeLike", + "format": [ + "PascalCase" + ] + } + ] + } + } + ] +} diff --git a/.mocharc.json b/.mocharc.json index 0411c77..df902d0 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,4 +1,8 @@ { "ui": "tdd", - "color": true -} \ No newline at end of file + "color": true, + "spec": [ + "./lib/cjs/test/*.test.js", + "./lib/esm/test/*.test.js" + ] +} diff --git a/build/fix-esm.mjs b/build/fix-esm.mjs new file mode 100755 index 0000000..6baccd1 --- /dev/null +++ b/build/fix-esm.mjs @@ -0,0 +1,12 @@ +#!/usr/bin/env node +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import fs from 'fs/promises'; + +await fs.writeFile( + new URL('../lib/esm/package.json', import.meta.url), + JSON.stringify({ type: 'module' }, undefined, 2) + '\n' +); diff --git a/build/remove-sourcemap-refs.js b/build/remove-sourcemap-refs.js index 89a0997..195cc03 100644 --- a/build/remove-sourcemap-refs.js +++ b/build/remove-sourcemap-refs.js @@ -15,13 +15,13 @@ function deleteRefs(dir) { deleteRefs(filePath); } else if (path.extname(file) === '.js') { const content = fs.readFileSync(filePath, 'utf8'); - const newContent = content.replace(/\/\/\# sourceMappingURL=[^]+.js.map/, '') + const newContent = content.replace(/\/\/\# sourceMappingURL=[^]+.js.map/, ''); if (content.length !== newContent.length) { console.log('remove sourceMappingURL in ' + filePath); fs.writeFileSync(filePath, newContent); } } else if (path.extname(file) === '.map') { - fs.unlinkSync(filePath) + fs.unlinkSync(filePath); console.log('remove ' + filePath); } } diff --git a/package.json b/package.json index da7a76b..250d69b 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,14 @@ "name": "jsonc-parser", "version": "3.2.1", "description": "Scanner and parser for JSON with comments.", - "main": "./lib/umd/main.js", - "typings": "./lib/umd/main.d.ts", - "module": "./lib/esm/main.js", + "main": "./lib/cjs/main.js", + "exports": { + ".": { + "import": "./lib/esm/main.js", + "module": "./lib/esm/main.js", + "default": "./lib/cjs/main.js" + } + }, "author": "Microsoft Corporation", "repository": { "type": "git", @@ -25,13 +30,12 @@ "rimraf": "^5.0.5" }, "scripts": { - "prepack": "npm run clean && npm run compile-esm && npm run test && npm run remove-sourcemap-refs", - "compile": "tsc -p ./src && npm run lint", - "compile-esm": "tsc -p ./src/tsconfig.esm.json", + "prepack": "npm run clean && npm run test && npm run remove-sourcemap-refs", + "compile": "tsc -b && node ./build/fix-esm.mjs && npm run lint", "remove-sourcemap-refs": "node ./build/remove-sourcemap-refs.js", "clean": "rimraf lib", - "watch": "tsc -w -p ./src", - "test": "npm run compile && mocha ./lib/umd/test", - "lint": "eslint src/**/*.ts" + "watch": "tsc -w -b", + "test": "npm run compile && mocha", + "lint": "eslint ." } } diff --git a/src/impl/edit.ts b/src/impl/edit.ts index e810c6b..4ce554c 100644 --- a/src/impl/edit.ts +++ b/src/impl/edit.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Edit, ParseError, Node, JSONPath, Segment, ModificationOptions } from '../main'; -import { format, isEOL } from './format'; -import { parseTree, findNodeAtLocation } from './parser'; +import { Edit, ParseError, Node, JSONPath, Segment, ModificationOptions } from '../main.js'; +import { format, isEOL } from './format.js'; +import { parseTree, findNodeAtLocation } from './parser.js'; export function removeProperty(text: string, path: JSONPath, options: ModificationOptions): Edit[] { return setProperty(text, path, void 0, options); diff --git a/src/impl/format.ts b/src/impl/format.ts index 383f2fb..e9081fb 100644 --- a/src/impl/format.ts +++ b/src/impl/format.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { Range, FormattingOptions, Edit, SyntaxKind, ScanError } from '../main'; -import { createScanner } from './scanner'; -import { cachedSpaces, cachedBreakLinesWithSpaces, supportedEols, SupportedEOL } from './string-intern'; +import { Range, FormattingOptions, Edit, SyntaxKind, ScanError } from '../main.js'; +import { createScanner } from './scanner.js'; +import { cachedSpaces, cachedBreakLinesWithSpaces, supportedEols, SupportedEOL } from './string-intern.js'; export function format(documentText: string, range: Range | undefined, options: FormattingOptions): Edit[] { let initialIndentLevel: number; diff --git a/src/impl/parser.ts b/src/impl/parser.ts index 3680431..c6f0666 100644 --- a/src/impl/parser.ts +++ b/src/impl/parser.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { createScanner } from './scanner'; +import { createScanner } from './scanner.js'; import { JSONPath, JSONVisitor, @@ -17,7 +17,7 @@ import { ScanError, Segment, SyntaxKind -} from '../main'; +} from '../main.js'; namespace ParseOptions { export const DEFAULT = { diff --git a/src/impl/scanner.ts b/src/impl/scanner.ts index 8ef32f9..5422a4e 100644 --- a/src/impl/scanner.ts +++ b/src/impl/scanner.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { ScanError, SyntaxKind, JSONScanner } from '../main'; +import { ScanError, SyntaxKind, JSONScanner } from '../main.js'; /** * Creates a JSON scanner on the given text. diff --git a/src/main.ts b/src/main.ts index 9852b67..44b5a7f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as formatter from './impl/format'; -import * as edit from './impl/edit'; -import * as scanner from './impl/scanner'; -import * as parser from './impl/parser'; +import * as formatter from './impl/format.js'; +import * as edit from './impl/edit.js'; +import * as scanner from './impl/scanner.js'; +import * as parser from './impl/parser.js'; /** * Creates a JSON scanner on the given text. diff --git a/src/test/edit.test.ts b/src/test/edit.test.ts index 2e011de..c2f0cda 100644 --- a/src/test/edit.test.ts +++ b/src/test/edit.test.ts @@ -5,18 +5,18 @@ 'use strict'; import * as assert from 'assert'; -import { FormattingOptions, Edit, ModificationOptions, modify } from '../main'; +import { FormattingOptions, Edit, ModificationOptions, modify } from '../main.js'; suite('JSON - edits', () => { function assertEdit(content: string, edits: Edit[], expected: string) { - assert(edits); + assert.ok(edits); let lastEditOffset = content.length; for (let i = edits.length - 1; i >= 0; i--) { let edit = edits[i]; - assert(edit.offset >= 0 && edit.length >= 0 && edit.offset + edit.length <= content.length); - assert(typeof edit.content === 'string'); - assert(lastEditOffset >= edit.offset + edit.length); // make sure all edits are ordered + assert.ok(edit.offset >= 0 && edit.length >= 0 && edit.offset + edit.length <= content.length); + assert.ok(typeof edit.content === 'string'); + assert.ok(lastEditOffset >= edit.offset + edit.length); // make sure all edits are ordered lastEditOffset = edit.offset; content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); } diff --git a/src/test/format.test.ts b/src/test/format.test.ts index c3a7b45..b8ec119 100644 --- a/src/test/format.test.ts +++ b/src/test/format.test.ts @@ -5,8 +5,8 @@ 'use strict'; import * as assert from 'assert'; -import * as Formatter from '../impl/format'; -import { Range } from '../main'; +import * as Formatter from '../impl/format.js'; +import { Range } from '../main.js'; suite('JSON - formatter', () => { @@ -25,9 +25,9 @@ suite('JSON - formatter', () => { for (let i = edits.length - 1; i >= 0; i--) { const edit = edits[i]; - // assert(edit.offset >= 0 && edit.length >= 0 && edit.offset + edit.length <= content.length); - // assert(typeof edit.content === 'string'); - // assert(lastEditOffset >= edit.offset + edit.length); // make sure all edits are ordered + // assert.ok(edit.offset >= 0 && edit.length >= 0 && edit.offset + edit.length <= content.length); + // assert.ok(typeof edit.content === 'string'); + // assert.ok(lastEditOffset >= edit.offset + edit.length); // make sure all edits are ordered lastEditOffset = edit.offset; content = content.substring(0, edit.offset) + edit.content + content.substring(edit.offset + edit.length); } diff --git a/src/test/json.test.ts b/src/test/json.test.ts index 4cf15c9..e647e62 100644 --- a/src/test/json.test.ts +++ b/src/test/json.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import { SyntaxKind, createScanner, parse, getLocation, Node, ParseError, parseTree, ParseErrorCode, ParseOptions, Segment, findNodeAtLocation, getNodeValue, getNodePath, ScanError, visit, JSONVisitor, JSONPath -} from '../main'; +} from '../main.js'; function assertKinds(text: string, ...kinds: SyntaxKind[]): void { var scanner = createScanner(text); @@ -43,7 +43,7 @@ function assertInvalidParse(input: string, expected: any, options?: ParseOptions var errors: ParseError[] = []; var actual = parse(input, errors, options); - assert(errors.length > 0); + assert.ok(errors.length > 0); assert.deepStrictEqual(actual, expected); } @@ -117,7 +117,7 @@ function assertLocation(input: string, expectedSegments: Segment[], expectedNode var offset = input.indexOf('|'); input = input.substring(0, offset) + input.substring(offset + 1, input.length); var actual = getLocation(input, offset); - assert(actual); + assert.ok(actual); assert.deepStrictEqual(actual.path, expectedSegments, input); assert.strictEqual(actual.previousNode && actual.previousNode.type, expectedNodeType, input); assert.strictEqual(actual.isAtPropertyKey, expectedCompleteProperty, input); @@ -127,7 +127,7 @@ function assertMatchesLocation(input: string, matchingSegments: Segment[], expec var offset = input.indexOf('|'); input = input.substring(0, offset) + input.substring(offset + 1, input.length); var actual = getLocation(input, offset); - assert(actual); + assert.ok(actual); assert.strictEqual(actual.matches(matchingSegments), expectedResult); } diff --git a/src/tsconfig.json b/src/tsconfig.json deleted file mode 100644 index d399fcb..0000000 --- a/src/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "es2020", - "module": "umd", - "moduleResolution": "node", - "sourceMap": true, - "declaration": true, - "stripInternal": true, - "outDir": "../lib/umd", - "strict": true, - "preserveConstEnums": true, - "lib": [ - "es2020" - ] - } -} \ No newline at end of file diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json new file mode 100644 index 0000000..d9c0b81 --- /dev/null +++ b/tsconfig.cjs.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.esm.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "./lib/cjs" + } +} diff --git a/src/tsconfig.esm.json b/tsconfig.esm.json similarity index 60% rename from src/tsconfig.esm.json rename to tsconfig.esm.json index 8c8246c..710d61d 100644 --- a/src/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -1,16 +1,18 @@ { + "include": ["src"], "compilerOptions": { "target": "es2020", - "module": "es6", - "moduleResolution": "node", + "module": "esnext", + "moduleResolution": "node16", "sourceMap": true, "declaration": true, "stripInternal": true, - "outDir": "../lib/esm", + "outDir": "./lib/esm", + "rootDir": "./src", "strict": true, "preserveConstEnums": true, "lib": [ "es2020" ] } -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..cc45744 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.cjs.json" }, + { "path": "./tsconfig.esm.json" } + ] +}