From ab25964c4de45dd8f03dbcfc2934acef7aaf3754 Mon Sep 17 00:00:00 2001 From: Philipp A Date: Sat, 17 Oct 2020 21:12:58 +0200 Subject: [PATCH] Update & ESLint --- .eslintrc.json | 17 ++- package.json | 48 ++++---- rollup.config.ts | 3 +- src/build-tools/rollup-plugin-renderdoc.ts | 4 +- src/components/Plotly.tsx | 2 +- src/components/markup/Markup.tsx | 108 ++---------------- src/components/markup/MarkupNodeComponent.tsx | 100 ++++++++++++++++ src/markup/MarkupDocument.ts | 45 ++++---- src/markup/md.tsx | 9 +- src/markup/rst.tsx | 42 ++++--- tsconfig.json | 2 +- 11 files changed, 202 insertions(+), 178 deletions(-) create mode 100644 src/components/markup/MarkupNodeComponent.tsx diff --git a/.eslintrc.json b/.eslintrc.json index bbca3270..8be67f22 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,7 @@ "browser": true, "es6": true }, - "parser": "typescript-eslint-parser", + "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module", "ecmaFeatures": { @@ -14,12 +14,19 @@ "rules": { "react/jsx-filename-extension": [1, { "extensions": [".jsx", ".tsx"] }], "import/no-unresolved": 0, - "import/extensions": 0, + "import/extensions": 0, + "import/no-extraneous-dependencies": 0, + "no-continue": 0, "no-dupe-args": 0, - "no-undef": 0, - "no-unused-vars": 0, + "no-undef": 0, + "no-redeclare": 0, + "no-shadow": 0, + "no-use-before-define": 0, + "no-unused-vars": 0, "no-useless-constructor": 0, "no-empty-function": 0, + "react/prop-types": 0, + "react/jsx-props-no-spreading": 0, "react/sort-comp": [2, { "order": [ "displayName", @@ -42,6 +49,6 @@ "/^render.+$/", "render" ] - }] + }] } } diff --git a/package.json b/package.json index 6af68032..ed3fb8ed 100644 --- a/package.json +++ b/package.json @@ -14,58 +14,58 @@ "test": "eslint *.js src/**/*.ts*" }, "dependencies": { - "@material-ui/core": "^3.9.3", - "katex": "^0.10.2", - "plotly.js": "^1.56.0", - "react": "^16.13.1", + "@material-ui/core": "^4.11.0", + "katex": "^0.12.0", + "plotly.js": "^1.57.0", + "react": "^16.14.0", "react-did-catch": "^0.2.1", - "react-dom": "^16.13.1", + "react-dom": "^16.14.0", "react-katex": "^2.0.2", "react-plotly.js": "^2.5.0", - "react-redux": "^5.1.1", - "react-router": "^4.3.1", - "react-router-dom": "^4.3.1", - "redux": "^4.0.4", + "react-redux": "^7.2.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", + "redux": "^4.0.5", "typed-jsx": "^0.1.1" }, "devDependencies": { + "@rollup/plugin-commonjs": "^15.1.0", + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^9.0.0", + "@rollup/plugin-replace": "^2.3.3", "@types/autoprefixer": "^9.7.2", "@types/detect-browser": "^4.0.0", "@types/glob": "^7.1.1", "@types/markdown-it": "^10.0.2", - "@types/react": "^16.9.50", + "@types/react": "^16.9.53", "@types/react-dom": "^16.9.8", "@types/react-plotly.js": "^2.2.4", - "@types/react-router-dom": "^5.1.5", + "@types/react-router-dom": "^5.1.6", "@types/rollup-plugin-json": "^3.0.2", "@types/rollup-plugin-node-builtins": "^2.1.1", "@types/rollup-plugin-postcss": "^2.0.1", + "@typescript-eslint/parser": "^4.4.1", "@wessberg/rollup-plugin-ts": "^1.3.5", - "autoprefixer": "^9.8.6", - "eslint": "^7.10.0", + "autoprefixer": "^10.0.1", + "eslint": "^7.11.0", "eslint-config-airbnb": "^18.2.0", "eslint-config-flying-sheep": "^5.2.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-no-foreach": "^1.0.0", - "eslint-plugin-react": "^7.21.3", - "markdown-it": "^11.0.1", - "matched": "^4.0.0", + "eslint-plugin-react": "^7.21.4", + "markdown-it": "^12.0.0", + "matched": "^5.0.0", "resolve": "^1.17.0", "restructured": "^0.0.11", - "rollup": "^2.28.2", - "@rollup/plugin-commonjs": "^15.1.0", - "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^9.0.0", - "@rollup/plugin-replace": "^2.3.3", + "rollup": "^2.32.0", "rollup-plugin-analyzer": "^3.3.0", "rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-postcss-modules": "^2.0.2", "rollup-plugin-serve": "^1.0.4", "rollup-watch": "^4.3.1", "ts-node": "^9.0.0", - "tslib": "^2.0.1", - "typescript": "^4.0.3", - "typescript-eslint-parser": "^22.0.0" + "tslib": "^2.0.3", + "typescript": "^4.0.3" } } diff --git a/rollup.config.ts b/rollup.config.ts index 48639548..187a4eb4 100644 --- a/rollup.config.ts +++ b/rollup.config.ts @@ -16,7 +16,6 @@ import * as autoprefixer from 'autoprefixer' import renderdoc from './src/build-tools/rollup-plugin-renderdoc' - const NODE_ENV = process.env.NODE_ENV || 'development' const isDev = NODE_ENV === 'development' @@ -41,7 +40,7 @@ export default { plugins: [ analyze({ writeTo(formatted) { - fs.writeFile('dist/bundle.log', formatted, e => (e !== null ? console.error(e) : {})) + fs.writeFile('dist/bundle.log', formatted, (e) => (e !== null ? console.error(e) : {})) }, }), postcss({ diff --git a/src/build-tools/rollup-plugin-renderdoc.ts b/src/build-tools/rollup-plugin-renderdoc.ts index 48bb9b7b..53399af0 100644 --- a/src/build-tools/rollup-plugin-renderdoc.ts +++ b/src/build-tools/rollup-plugin-renderdoc.ts @@ -31,7 +31,7 @@ export default function renderdoc(config: Partial = {}) { const converters = config.converters ?? DEFAULT_CONVERTERS const include: string[] = typeof config.include === 'string' ? [config.include] : config.include || [] const exclude: string[] = typeof config.exclude === 'string' ? [config.exclude] : config.exclude || [] - const patterns = include.concat(exclude.map(pattern => `!${pattern}`)) + const patterns = include.concat(exclude.map((pattern) => `!${pattern}`)) async function doGlob(id: string): Promise { const stats = await fs.stat(id) @@ -58,7 +58,7 @@ export default function renderdoc(config: Partial = {}) { const convert = converters[path.extname(p)] return convert(content) })) - const map = zipObject(paths.map(p => path.basename(p)), contents) + const map = zipObject(paths.map((p) => path.basename(p)), contents) return `export default ${JSON.stringify(map)}` }, } diff --git a/src/components/Plotly.tsx b/src/components/Plotly.tsx index aded21b7..8b8e5476 100644 --- a/src/components/Plotly.tsx +++ b/src/components/Plotly.tsx @@ -32,7 +32,7 @@ export default class Plotly extends React.Component throw new Error(r.statusText) }).then(({ layout, data }) => { this.setState({ layout, data }) - }).catch(e => console.error(e)) + }).catch((e) => console.error(e)) this.handleOnClickLink = this.handleOnClickLink.bind(this) } diff --git a/src/components/markup/Markup.tsx b/src/components/markup/Markup.tsx index 1c5db908..d1e56d5e 100644 --- a/src/components/markup/Markup.tsx +++ b/src/components/markup/Markup.tsx @@ -1,14 +1,8 @@ import * as React from 'react' -import { Typography } from '@material-ui/core' -import { ThemeStyle } from '@material-ui/core/styles/createTypography' -import { InlineMath } from 'react-katex' - -import ASTErrorMessage, { ASTErrorMessageProps } from './ASTErrorMessage' -import { Document, Node, Elem, Type, Bullet } from '../../markup/MarkupDocument' -import ASTError from '../../markup/AstError' -import Plotly from '../Plotly' - +import { Document, Node } from '../../markup/MarkupDocument' +import { ASTErrorMessageProps } from './ASTErrorMessage' +import MarkupNodeComponent from './MarkupNodeComponent' export interface MarkupProps { doc: Document @@ -23,7 +17,6 @@ export default class Markup extends React.Component { const { doc } = props this.title = doc.title this.children = doc.children // DEBUG - this.state = { errorMessage: null } } static getDerivedStateFromError(error: Error) { @@ -31,101 +24,16 @@ export default class Markup extends React.Component { } render(): React.ReactElement { - const nodes = convertChildren(this, 0) + const nodes = this.children.map((e) => ) + const body = React.Children.toArray(nodes) if (process.env.NODE_ENV === 'development') { return (
- {nodes} -
{JSON.stringify(nodes, undefined, '\t')}
+ {body} +
{JSON.stringify(this.children, undefined, '\t')}
) } - return
{nodes}
- } -} - -export interface MarkupElementProps { - node: Node - level: number -} - -export interface MarkupElementState { - errorMessage: string | null -} - -function convertChildren(elem: Elem | Document | Markup, level: number): React.ReactNode[] { - const children = elem.children.map(e => ) - return React.Children.toArray(children) -} - -export class MarkupNodeComponent extends React.Component - { - constructor(props: MarkupElementProps) { - super(props) - this.state = { errorMessage: null } - } - - static getDerivedStateFromError(error: Error) { - return { errorMessage: error.message } - } - - render(): React.ReactNode { - const { node, level } = this.props - const { errorMessage } = this.state - if (errorMessage !== null) { - return {errorMessage} - } - if (typeof node === 'string') return `${node}\n` - switch (node.type) { - case Type.LineBreak: - return
- case Type.Link: { - if ('name' in node.ref) throw new ASTError(`Unresolved reference ${node.ref.name}`, node) - return {convertChildren(node, level)} - } - case Type.Section: - return
{convertChildren(node, level + 1)}
- case Type.Title: { - if (level < 1) throw new ASTError(`Header with level ${level} < 1`, node) - const hLevel = Math.min(level, 6) - return {convertChildren(node, level)} - } - case Type.Paragraph: - return {convertChildren(node, level)} - case Type.Code: - return {convertChildren(node, level)} - case Type.Emph: - return {convertChildren(node, level)} - case Type.Strong: - return {convertChildren(node, level)} - case Type.BulletList: { - const listStyleType = node.bullet == Bullet.text ? node.text : node.bullet - return
    {convertChildren(node, level)}
- } - case Type.EnumList: - return
    {convertChildren(node, level)}
- case Type.ListItem: - return
  • {convertChildren(node, level)}
  • - case Type.InlineMath: - return - case Type.CodeBlock: - return
    {convertChildren(node, level)}
    - case Type.Table: - return ( -
    - {convertChildren(node, level)}
    - {node.caption &&
    {node.caption}
    } -
    - ) - case Type.Row: - return {convertChildren(node, level)} - case Type.Cell: - return {convertChildren(node, level)} - // custom - case Type.Plotly: { - const props = Object.assign({type: undefined}, node) - return - } - } + return
    {body}
    } } diff --git a/src/components/markup/MarkupNodeComponent.tsx b/src/components/markup/MarkupNodeComponent.tsx new file mode 100644 index 00000000..b1be0401 --- /dev/null +++ b/src/components/markup/MarkupNodeComponent.tsx @@ -0,0 +1,100 @@ +import * as React from 'react' + +import { Typography } from '@material-ui/core' +import { ThemeStyle } from '@material-ui/core/styles/createTypography' +import { InlineMath } from 'react-katex' + +import { + Node, Elem, Type, Bullet, +} from '../../markup/MarkupDocument' +import ASTError from '../../markup/AstError' +import Plotly from '../Plotly' +import ASTErrorMessage from './ASTErrorMessage' + +export interface MarkupElementProps { + node: Node + level: number +} + +export interface MarkupElementState { + errorMessage: string | null +} + +function convertChildren(elem: Elem, level: number): React.ReactNode[] { + const children = elem.children.map((e) => ) + return React.Children.toArray(children) +} + +export default class MarkupNodeComponent extends React.Component + { + constructor(props: MarkupElementProps) { + super(props) + this.state = { errorMessage: null } + } + + static getDerivedStateFromError(error: Error) { + return { errorMessage: error.message } + } + + render(): React.ReactNode { + const { node, level } = this.props + const { errorMessage } = this.state + if (errorMessage !== null) { + return {errorMessage} + } + if (typeof node === 'string') return `${node}\n` + switch (node.type) { + case Type.LineBreak: + return
    + case Type.Link: { + if ('name' in node.ref) throw new ASTError(`Unresolved reference ${node.ref.name}`, node) + return {convertChildren(node, level)} + } + case Type.Section: + return
    {convertChildren(node, level + 1)}
    + case Type.Title: { + if (level < 1) throw new ASTError(`Header with level ${level} < 1`, node) + const hLevel = Math.min(level, 6) + return {convertChildren(node, level)} + } + case Type.Paragraph: + return {convertChildren(node, level)} + case Type.Code: + return {convertChildren(node, level)} + case Type.Emph: + return {convertChildren(node, level)} + case Type.Strong: + return {convertChildren(node, level)} + case Type.BulletList: { + const listStyleType = node.bullet === Bullet.text ? node.text : node.bullet + return
      {convertChildren(node, level)}
    + } + case Type.EnumList: + return
      {convertChildren(node, level)}
    + case Type.ListItem: + return
  • {convertChildren(node, level)}
  • + case Type.InlineMath: + return + case Type.CodeBlock: + return
    {convertChildren(node, level)}
    + case Type.Table: + return ( +
    + {convertChildren(node, level)}
    + {node.caption &&
    {node.caption}
    } +
    + ) + case Type.Row: + return {convertChildren(node, level)} + case Type.Cell: + return {convertChildren(node, level)} + // custom + case Type.Plotly: { + const { type, ...props } = node + return + } + default: + throw new ASTError(`Unknown type ${(node as Elem).type}`, node) + } + } +} diff --git a/src/markup/MarkupDocument.ts b/src/markup/MarkupDocument.ts index 716b7eef..169f7d07 100644 --- a/src/markup/MarkupDocument.ts +++ b/src/markup/MarkupDocument.ts @@ -25,7 +25,7 @@ export enum Bullet { disc = 'disc', circle = 'circle', square = 'square', - text = '__text__', // custom text + text = '__text__', // custom text // none can be represented by null. } @@ -33,21 +33,21 @@ export enum Bullet { /// but CSS list-style-type. export enum Enumeration { decimal = 'decimal', - decimal_leading_zero = 'decimal-leading-zero', - lower_roman = 'lower-roman', - upper_roman = 'upper-roman', - lower_greek = 'lower-greek', - lower_alpha = 'lower-alpha', - upper_alpha = 'upper-alpha', - lower_latin = 'lower-latin', - upper_latin = 'upper-latin', - arabic_indic = 'arabic-indic', + decimalLeadingZero = 'decimal-leading-zero', + lowerRoman = 'lower-roman', + upperRoman = 'upper-roman', + lowerGreek = 'lower-greek', + lowerAlpha = 'lower-alpha', + upperAlpha = 'upper-alpha', + lowerLatin = 'lower-latin', + upperLatin = 'upper-latin', + arabicIndic = 'arabic-indic', armenian = 'armenian', bengali = 'bengali', - cjk_earthly_branch = 'cjk-earthly-branch', - cjk_heavenly_stem = 'cjk-heavenly-stem', + cjkEarthlyBranch = 'cjk-earthly-branch', + cjkHeavenlyStem = 'cjk-heavenly-stem', devanagari = 'devanagari', - ethiopic_numeric = 'ethiopic_numeric', + ethiopicNumeric = 'ethiopic_numeric', georgian = 'georgian', gujarati = 'gujarati', gurmukhi = 'gurmukhi', @@ -96,16 +96,20 @@ function flatten(nested: A[]): E[] { return cumul } +function arrayify(obj: undefined | E | A[]): E[] { + if (obj === undefined) return [] + if (Array.isArray(obj)) return flatten(obj) + return [obj] +} + type Nodes = Node | Nodes[] type Props

    = Omit & { children?: Nodes } function mkFun

    (type: Type): FunctionComponent, P> { - return ({children: nestedChildren, ...props}) => { - const children: Node[] = Array.isArray(nestedChildren) ? flatten(nestedChildren) : nestedChildren !== undefined ? [nestedChildren] : [] - return { type, children, ...props } as unknown as P - } + return ({ children: nested, ...props }) => ({ + type, children: arrayify(nested), ...props, + } as unknown as P) } - // Block interface Paragraph extends Element { type: Type.Paragraph } @@ -119,7 +123,8 @@ export const Title = mkFun(Type.Title) interface BulletList extends Element { type: Type.BulletList, bullet?: Bullet, text?: string } export const BulletList = mkFun<BulletList>(Type.BulletList) -interface EnumList extends Element { type: Type.EnumList, enumeration?: Enumeration } // TODO: rst also has prefix/suffix +// TODO: rst also has prefix/suffix +interface EnumList extends Element { type: Type.EnumList, enumeration?: Enumeration } export const EnumList = mkFun<EnumList>(Type.EnumList) interface ListItem extends Element { type: Type.ListItem } export const ListItem = mkFun<ListItem>(Type.ListItem) @@ -160,7 +165,7 @@ interface Plotly extends Element { type: Type.Plotly, url: string, onClickLink?: string, - style?: Partial<CSSStyleDeclaration>, + style?: Partial<React.CSSProperties>, config?: Plotly.Config, } export const Plotly = mkFun<Plotly>(Type.Plotly) diff --git a/src/markup/md.tsx b/src/markup/md.tsx index 62356e6c..cae595ad 100644 --- a/src/markup/md.tsx +++ b/src/markup/md.tsx @@ -43,14 +43,15 @@ function convertNode(token: Token): m.Node[] { return [token.content] case 'paragraph': return [<m.Paragraph>{convertChildren(token)}</m.Paragraph>] - case 'heading': + case 'heading': { const level = /h(?<level>[1-6])/.exec(token.tag)?.groups?.level if (!level) throw new ASTError(`Unexpected header tag ${token.tag}`, token) - return [<m.Title level={parseInt(level)}>{convertChildren(token)}</m.Title>] + return [<m.Title level={parseInt(level, 10)}>{convertChildren(token)}</m.Title>] + } case 'link': { const href = token.attrs?.filter(([a, v]) => a === 'href')?.[0]?.[1] - if (!href) throw new ASTError(`Link without href encountered`, token) - return [<m.Link ref={{href}}>{convertChildren(token)}</m.Link>] + if (!href) throw new ASTError('Link without href encountered', token) + return [<m.Link ref={{ href }}>{convertChildren(token)}</m.Link>] } case 'hardbreak': return [<m.LineBreak/>] diff --git a/src/markup/rst.tsx b/src/markup/rst.tsx index c7d07a6f..1103aaca 100644 --- a/src/markup/rst.tsx +++ b/src/markup/rst.tsx @@ -4,7 +4,6 @@ import * as rst from 'restructured' import * as m from './MarkupDocument' import ASTError from './AstError' - interface Directive { header: string | null params: { [k: string]: string } @@ -14,7 +13,7 @@ interface Directive { // https://github.com/microsoft/TypeScript/issues/21699 function parseDirective(lines: rst.Node[]): Directive { - const texts = lines.map(n => (n.type === 'text' ? n.value as string : JSON.stringify(n))) + const texts = lines.map((n) => (n.type === 'text' ? n.value as string : JSON.stringify(n))) const [header = null, ...rest] = texts let lastParam = -1 const params = rest @@ -42,7 +41,7 @@ function convertNode(node: rst.Node, level: number): m.Node[] { return [] case 'reference': { const name = node.children?.[0].value as string - return [<m.Link ref={{name}}>{[name]}</m.Link>] + return [<m.Link ref={{ name }}>{[name]}</m.Link>] } case 'section': return [<m.Section>{convertChildren(node, level + 1)}</m.Section>] @@ -64,14 +63,16 @@ function convertNode(node: rst.Node, level: number): m.Node[] { <m.BulletList bullet={node.bullet ? m.Bullet.text : undefined} text={node.bullet || undefined} - >{convertChildren(node, level)}</m.BulletList> + > + {convertChildren(node, level)} + </m.BulletList>, ] case 'list_item': return [<m.ListItem>{convertChildren(node, level)}</m.ListItem>] case 'interpreted_text': switch (node.role) { case 'math': - return [<m.InlineMath math={(node.children || []).map(text => text.value).join('')}/>] + return [<m.InlineMath math={(node.children || []).map((text) => text.value).join('')}/>] case null: return [<m.Emph>{convertChildren(node, level)}</m.Emph>] default: @@ -79,9 +80,10 @@ function convertNode(node: rst.Node, level: number): m.Node[] { } case 'directive': switch (node.directive as rst.DirectiveType | 'plotly') { - case 'code': + case 'code': { const { header, body } = parseDirective(node.children || []) return [<m.CodeBlock language={header || undefined}>{body}</m.CodeBlock>] + } case 'csv-table': { const { header, params, body } = parseDirective(node.children || []) const delim = (() => { @@ -93,14 +95,14 @@ function convertNode(node: rst.Node, level: number): m.Node[] { })() return [ <m.Table caption={header || undefined}> - {body.map(r => ( + {body.map((r) => ( <m.Row> - {r.split(delim).map(cell => ( + {r.split(delim).map((cell) => ( <m.Cell>{cell}</m.Cell> ))} </m.Row> ))} - </m.Table> + </m.Table>, ] } // custom @@ -112,7 +114,7 @@ function convertNode(node: rst.Node, level: number): m.Node[] { onClickLink={params.onClickLink} style={{ width: '100%' }} config={{ responsive: true } as any} // typing has no responsive - /> + />, ] } default: @@ -124,7 +126,10 @@ function convertNode(node: rst.Node, level: number): m.Node[] { } function convertChildren(node: rst.Node, level: number): m.Node[] { - return (node.children || []).reduce((ns: m.Node[], n: rst.Node) => [...ns, ...convertNode(n, level)], []) + return (node.children || []).reduce( + (ns: m.Node[], n: rst.Node) => [...ns, ...convertNode(n, level)], + [], + ) } function* extractTargetsInner(elem: rst.Node): IterableIterator<[string, string]> { @@ -143,15 +148,14 @@ function* extractTargetsInner(elem: rst.Node): IterableIterator<[string, string] const URL_SCHEMA = /^https?:.*$/ function extractTargets(node: rst.Node): {[key: string]: string} { - const pending = - Array.from(extractTargetsInner(node)) - .reduce((obj, [k, v]) => { obj[k] = v; return obj }, {} as {[key: string]: string}) + const pending = Object.fromEntries(extractTargetsInner(node)) const resolved: {[key: string]: string} = {} let newResolvable = true while (newResolvable) { newResolvable = false - for (let [k, v] of Object.entries(pending)) { - if (v in resolved) v = resolved[v] // now the match will be true + for (const entry of Object.entries(pending)) { + const k = entry[0] + const v = entry[1] in resolved ? resolved[entry[1]] : entry[1] // if so the match will be true // TODO: more schemas if (v.match(URL_SCHEMA)) { resolved[k] = v @@ -166,12 +170,12 @@ function extractTargets(node: rst.Node): {[key: string]: string} { return resolved } -function resolveTargets(root: m.Elem, targets: {[key: string]: string}): m.Elem { - root = {...root} +function resolveTargets(root_: m.Elem, targets: {[key: string]: string}): m.Elem { + const root = { ...root_ } if (root.type === m.Type.Link && 'name' in root.ref && root.ref.name in targets) { root.ref = { href: targets[root.ref.name] } } - root.children = (root.children || []).map(c => typeof c === 'string' ? c : resolveTargets(c, targets)) + root.children = (root.children || []).map((c) => (typeof c === 'string' ? c : resolveTargets(c, targets))) return root } diff --git a/tsconfig.json b/tsconfig.json index 6af92a22..5a6b1cf2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "module": "ES2015", "moduleResolution": "node", "target": "ES2017", - "lib": ["es2017", "dom", "dom.iterable"], + "lib": ["es2019", "dom", "dom.iterable"], "jsx": "react", //stronger types "strict": true,