Skip to content

Commit 89df277

Browse files
committed
Migrate kg-lexical-html-renderer to TypeScript
- Move lib/ to src/, update tsconfig rootDir - Update tsconfig.json: NodeNext module, verbatimModuleSyntax, declarationMap - Add "type": "module" to package.json - Convert source from CJS to ESM (require -> import, module.exports -> export) - Add .js extensions to all relative imports - Rename test files .js to .ts with type annotations - Switch test runner to tsx - Replace .eslintrc.js with eslint.config.js (flat config)
1 parent ceadf70 commit 89df277

30 files changed

Lines changed: 186 additions & 160 deletions
Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,33 @@
1-
import {fixupPluginRules} from '@eslint/compat';
21
import eslint from '@eslint/js';
2+
import {defineConfig} from 'eslint/config';
33
import ghostPlugin from 'eslint-plugin-ghost';
4-
import globals from 'globals';
54
import tseslint from 'typescript-eslint';
65

7-
const ghost = fixupPluginRules(ghostPlugin);
8-
9-
export default tseslint.config(
6+
export default defineConfig([
107
{ignores: ['build/**']},
118
{
12-
files: ['lib/**/*.ts'],
9+
files: ['**/*.ts'],
1310
extends: [
1411
eslint.configs.recommended,
1512
tseslint.configs.recommended
1613
],
17-
plugins: {ghost},
1814
languageOptions: {
19-
globals: globals.node
15+
parserOptions: {ecmaVersion: 2022, sourceType: 'module'}
2016
},
17+
plugins: {ghost: ghostPlugin},
2118
rules: {
2219
...ghostPlugin.configs.ts.rules,
23-
'ghost/filenames/match-exported-class': 'off',
24-
'@typescript-eslint/no-unused-expressions': 'off',
25-
'@typescript-eslint/no-require-imports': 'off'
20+
'@typescript-eslint/no-explicit-any': 'error'
2621
}
2722
},
2823
{
29-
files: ['test/**/*.js'],
30-
extends: [
31-
eslint.configs.recommended
32-
],
33-
plugins: {ghost},
34-
languageOptions: {
35-
globals: {
36-
...globals.node,
37-
...globals.mocha,
38-
should: true,
39-
sinon: true
40-
}
41-
},
24+
files: ['test/**/*.ts'],
4225
rules: {
43-
...ghostPlugin.configs.node.rules,
44-
'no-unused-vars': ['error', {caughtErrors: 'none'}],
45-
'ghost/filenames/match-exported-class': 'off',
46-
'ghost/filenames/match-exported': 'off',
47-
'ghost/filenames/match-regex': 'off',
48-
...ghostPlugin.configs.test.rules,
26+
...ghostPlugin.configs['ts-test'].rules,
27+
'ghost/mocha/no-global-tests': 'off',
28+
'ghost/mocha/handle-done-callback': 'off',
29+
'ghost/mocha/no-mocha-arrows': 'off',
4930
'ghost/mocha/max-top-level-suites': 'off'
5031
}
5132
}
52-
);
33+
]);

packages/kg-lexical-html-renderer/lib/index.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/kg-lexical-html-renderer/lib/transformers/index.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.

packages/kg-lexical-html-renderer/package.json

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,25 @@
44
"repository": "https://github.com/TryGhost/Koenig/tree/main/packages/kg-lexical-html-renderer",
55
"author": "Ghost Foundation",
66
"license": "MIT",
7-
"main": "build/index.js",
8-
"types": "build/index.d.ts",
7+
"main": "build/cjs/index.js",
8+
"module": "build/esm/index.js",
9+
"types": "build/esm/index.d.ts",
10+
"exports": {
11+
".": {
12+
"types": "./build/esm/index.d.ts",
13+
"import": "./build/esm/index.js",
14+
"require": "./build/cjs/index.js"
15+
}
16+
},
917
"scripts": {
1018
"dev": "tsc --watch --preserveWatchOutput --sourceMap",
11-
"build": "tsc",
12-
"prepare": "tsc",
13-
"pretest": "yarn build",
19+
"build": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json",
20+
"prepare": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json",
21+
"pretest": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\":\"module\"}' > build/esm/package.json && tsc -p tsconfig.test.json",
1422
"test": "yarn test:unit && yarn test:types",
15-
"test:unit": "NODE_ENV=testing c8 --lib --check-coverage --reporter text --reporter cobertura mocha './test/**/*.test.js'",
23+
"test:unit": "NODE_ENV=testing c8 --src src --all --check-coverage --reporter text --reporter cobertura tsx mocha './test/**/*.test.ts'",
1624
"test:types": "tsc --noEmit",
17-
"lint": "eslint . --cache",
25+
"lint": "eslint",
1826
"posttest": "yarn lint"
1927
},
2028
"files": [
@@ -26,12 +34,20 @@
2634
"access": "public"
2735
},
2836
"devDependencies": {
37+
"@eslint/js": "9.39.4",
38+
"@types/jsdom": "21.1.7",
39+
"@types/mocha": "10.0.10",
40+
"@types/should": "13.0.0",
41+
"@types/sinon": "21.0.0",
2942
"c8": "11.0.0",
3043
"jsdom": "^29.0.0",
3144
"mocha": "11.7.5",
3245
"prettier": "3.8.1",
3346
"should": "13.2.3",
34-
"sinon": "21.0.3"
47+
"sinon": "21.0.3",
48+
"tsx": "4.21.0",
49+
"typescript": "5.9.3",
50+
"typescript-eslint": "8.57.0"
3551
},
3652
"dependencies": {
3753
"@lexical/clipboard": "0.13.1",

packages/kg-lexical-html-renderer/src/LexicalHTMLRenderer.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
import {SerializedEditorState, LexicalEditor, LexicalNode, Klass} from 'lexical';
21
import {createHeadlessEditor} from '@lexical/headless';
32
import {ListItemNode, ListNode} from '@lexical/list';
43
import {HeadingNode, QuoteNode} from '@lexical/rich-text';
54
import {LinkNode} from '@lexical/link';
6-
import $convertToHtmlString from './convert-to-html-string';
7-
import getDynamicDataNodes from './get-dynamic-data-nodes';
8-
9-
// TODO: Using import causes circular definitions for kg-default-nodes
10-
11-
const {registerRemoveAtLinkNodesTransform} = require('@tryghost/kg-default-transforms');
5+
import {JSDOM} from 'jsdom';
6+
import $convertToHtmlString from './convert-to-html-string.js';
7+
import getDynamicDataNodes from './get-dynamic-data-nodes.js';
8+
import {registerRemoveAtLinkNodesTransform} from '@tryghost/kg-default-transforms';
9+
import type {SerializedEditorState, LexicalEditor, LexicalNode, Klass} from 'lexical';
1210

1311
interface RenderOptions {
1412
target?: 'html' | 'email' | 'plaintext';
1513
dom?: import('jsdom').JSDOM;
1614
// TODO: we should define some standard here once we move to more cards with dynamic data
17-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
18-
renderData?: Map<number, any>;
15+
renderData?: Map<number, unknown>;
1916
}
2017

2118
function defaultOnError() {
@@ -29,9 +26,6 @@ export default class LexicalHTMLRenderer {
2926

3027
constructor({dom, nodes, onError}: {dom?: import('jsdom').JSDOM, nodes?: Klass<LexicalNode>[], onError?: () => void} = {}) {
3128
if (!dom) {
32-
const jsdom = require('jsdom');
33-
const {JSDOM} = jsdom;
34-
3529
this.dom = new JSDOM();
3630
} else {
3731
this.dom = dom;

packages/kg-lexical-html-renderer/src/convert-to-html-string.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import {ElementNode, LexicalNode} from 'lexical';
21
import {$getRoot, $isElementNode, $isLineBreakNode, $isParagraphNode, $isTextNode} from 'lexical';
32
import {$isLinkNode} from '@lexical/link';
4-
import {$isKoenigCard, RendererOptions} from '@tryghost/kg-default-nodes';
5-
import TextContent from './utils/TextContent';
6-
import elementTransformers from './transformers';
3+
import {$isKoenigCard} from '@tryghost/kg-default-nodes';
4+
import TextContent from './utils/TextContent.js';
5+
import elementTransformers from './transformers/index.js';
6+
import type {ElementNode, LexicalNode} from 'lexical';
7+
import type {RendererOptions} from '@tryghost/kg-default-nodes';
78

89
export default function $convertToHtmlString(options: RendererOptions = {}): string {
910
const output: string[] = [];

packages/kg-lexical-html-renderer/src/get-dynamic-data-nodes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {$getRoot} from 'lexical';
2-
import {$isKoenigCard, KoenigDecoratorNode} from '@tryghost/kg-default-nodes';
3-
2+
import {$isKoenigCard} from '@tryghost/kg-default-nodes';
3+
import type {KoenigDecoratorNode} from '@tryghost/kg-default-nodes';
44
import type {EditorState} from 'lexical';
55

66
export default function getDynamicDataNodes(editorState: EditorState): KoenigDecoratorNode[] {
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* c8 ignore start */
2+
import LexicalHTMLRenderer from './LexicalHTMLRenderer.js';
3+
4+
export {LexicalHTMLRenderer};
5+
/* c8 ignore stop */

packages/kg-lexical-html-renderer/src/kg-default-nodes.d.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ declare module '@tryghost/kg-default-nodes' {
99
target?: 'html' | 'email' | 'plaintext';
1010
}
1111

12-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13-
type X = any;
12+
type X = unknown;
1413

1514
export class KoenigDecoratorNode extends DecoratorNode<X> {
1615
// TODO: exportDOM override isn't directly compatible with base class, should fix when converting kg-default-nodes

packages/kg-lexical-html-renderer/src/transformers/element/aside.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import {$isAsideNode, RendererOptions} from '@tryghost/kg-default-nodes';
1+
import {$isAsideNode} from '@tryghost/kg-default-nodes';
2+
import type {RendererOptions} from '@tryghost/kg-default-nodes';
23
import type {ElementNode} from 'lexical';
3-
import type {ExportChildren} from '..';
4+
import type {ExportChildren} from '../index.js';
45

5-
module.exports = {
6+
export default {
67
export(node: ElementNode, options: RendererOptions, exportChildren: ExportChildren) {
78
if (!$isAsideNode(node)) {
89
return null;

0 commit comments

Comments
 (0)