Fonto 8.0: Upgrading to TypeScript

Fonto 8.0: Upgrading to TypeScript

Fonto 8.0 is introducing TypeScript! To fully benefit of TypeScript you need to convert your editor. In this blog post I want to show how easy it is to convert your editor to TypeScript. I will exhibit how we upgraded our DITA example configuration from JavaScript to TypeScript. This effort took around a day and should be illustrative to how long it will take you upgrading your editor! This post is for everyone who is upgrading their Fonto configuration to 8.0 and everyone who is configuring Fonto in a version later than 8.0!

The DITA example editor

The DITA example editor is a generic configuration of Fonto Editor specifically for the DITA 1.3 schema. It supports glossaries, tasks and more. We at Fonto use it for demos, as a reference configuration and as a start of more specific configurations.

This editor has different features. There are some custom React components, some custom mutations and a lot of family configuration. We upgraded all of that to TypeScript and we fixed a lot of errors. But we did not yet solve every error. We are planning to work on the last few type warnings whenever we work on a specific package. Because our development tooling does not enforce 100% correctness for the written code, that is fine and actually something we recommend to you as well!

Linting

The editor is going to see a lot of changes in a lot of different files. We started by setting up a linting and code style configuration that we also use for Fonto development. We highly recommend doing the same as clean-looking and tidy code makes for fewer bugs. The linting configuration is publicly available here: https://github.com/FontoXML/eslint-config-fontoxml#fontoxmleslint-config.

To enable our linter, install the following dependencies in your editor. We used a -E flag to tell NPM to install these exact versions to make sure this will work when new versions of the dependencies are released!

npm i -E @babel/eslint-plugin@7.14.5 @fontoxml/eslint-config@4.0.2 @typescript-eslint/eslint-plugin@4.28.0 eslint-config-prettier@8.3.0 eslint-import-resolver-typescript@2.4.0 eslint-plugin-import@2.23.4 eslint-plugin-prettier@3.4.0 eslint-plugin-react@7.24.0 eslint-plugin-react-hooks@4.2.0 eslint-plugin-simple-import-sort@7.0.0 eslint@7.29.0 typescript@4.3.4

Then, add the following to the scripts section of your package.json:

"scripts": {
    "lint": "eslint \"./packages/**/*.ts\" \"./packages/**/*.tsx\" \"./config/**/*.ts\"",
    "lint-fix": "eslint --fix \"./packages/**/*.ts\" \"./packages/**/*.tsx\" \"./config/**/*.ts\""
}

Also, add a prettierrc.js file with the following configuration:

module.exports = require("@fontoxml/eslint-config/prettier");

Finally, add our config by adding our example .eslintrc.js file to the root of the configuration. You can find it on github.

With this, you can run the command npm run lint-fix before you make a commit. This will automatically fix most common errors, apply a consistent code style and warn about possible errors that it could not fix. Depending on your editor, you can also automatically apply fixes. For VSCode, we recommend the ESLint extension.

Converting all JavaScript files to TypeScript

With the linter set up, we enabled TypeScript everywhere. We changed the extension of all files ending with .js to .ts and from .jsx to .tsx. This was automated by using a tool we already mentioned in an earlier TypeScript blog post. We ran these commands:

$ cd packages
$ npx wvbe/change-file-extensions .js .ts
$ npx wvbe/change-file-extensions .jsx .tsx
$ cd ../config
$ npx wvbe/change-file-extensions .js .ts
$ npx wvbe/change-file-extensions .js .ts
$ cd ..
$ npm run lint-fix

After these commands, we made a commit to keep our changes safe. We now have a Fonto Editor in TypeScript. This already improves the developer experience a lot, even though there may be a lot of errors. While you can stop now, we recommend putting in some effort to resolve errors in files you work in a lot. By improving the code quality in those, you can trust the autocomplete functionality of your editor!

Actually fixing errors!

The linter failed the first round with 40 problems: 20 errors and 20 warnings. The errors is what we should focus on. They do not block development of your configuration, so you can leave them in, but we recommend fixing at least the easily fixable errors. You might see similar errors in your configuration and you might see different ones. Be sure to reach out to our support channel if we can help you resolving them!

The most common error we found looked like this: 10:8 error No default export found in imported module "react" import/default. This was solved by changing the import declaration for React to this: import * as React from 'react';. That error occured 10 times.

After that, we had a lot of output looking like this: 36:2 warning Caution: blueprintMutations also has a named export unsafeMoveNodes. Check if you meant to write import {unsafeMoveNodes} from 'fontoxml-blueprints/src/blueprintMutations' instead import/no-named-as-default-member. These were again caused by an import. These warnings were solved by following the instructions in the message.

The first error that was a bit more tricky to solve was this one: 50:9 error Operands of '+' operation with any is possible only with string, number, bigint or any @typescript-eslint/restrict-plus-operands. The code that triggered this error was the following:

function uppercaseFirstLetter(input) {
	if (input === '') {
		return input;
	}
	const firstCharacter = String.fromCodePoint(input.codePointAt(0));
	return firstCharacter.toUpperCase() + input.substr(firstCharacter.length);
	       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}

This error is there because TypeScript could not infer that the input argument should be a string. By doing that, the error was actually fixed! We replaced the function uppercaseFirstLetter(input) part with function uppercaseFirstLetter(input: string). This solved the error.

Type errors

This fixed the most of the errors from the linter. This did not yet remove all squiggles from the codebase. There are still a number left that are actually TypeScript errors. We did not fix all of them yet, but we are planning to fix them whenever we are working in a package that contains them. A number of these errors are actually because Fonto 8.0 contains some errors by itself. These are (going to be) fixed in the 8.1 version, which is planned to be released at the end of June this year. We got a report of all errors by running the command npx tsc --noemit. Before starting the fixing effort, we had a total of 106 errors.

A number of the errors were located in the pivot configuration of the editor, which defines how copy and pasting works. They all looked like this:

packages/dita-example-sx-pivot-model-translation/src/configureSxModule.ts:97:6 - error TS2322: Type '"frame"' is not assignable to type 'PivotNodeTypes'.

97      type: 'frame',
        ~~~~

The fix was the same: using the correct enum value from the PivotNodeTypes enum and letting the IDE generate the correct import for it!

The next error we picked out was this one:

packages/dita-example-sx-modules-xsd-highlight-domain/src/configureSxModule.ts:66:3 - error TS2322: Type 'string' is not assignable to type 'boolean'.

66   isMonospaced: 'true',
     ~~~~~~~~~~~~

  platform/fontoxml-families/src/types.d.ts:1300:5
    1300     isMonospaced?: IsMonospaced;
             ~~~~~~~~~~~~
    The expected type comes from property 'isMonospaced' which is declared here on type 'CvkOptions'

This was an actual mistake. The type is actually a boolean, not a string. We fixed it by making it so.

A lot of other errors looked like this:

packages/dita-example-sx-modules-xsd-reference-mod/src/install.ts:52:8 - error TS2345: Argument of type 'ReadOnlyBlueprint' is not assignable to parameter of type 'IDomFacade'.

52        readOnlyBlueprint
          ~~~~~~~~~~~~~~~~~

This is unfortunately not an error we can fix on the editor level. It is a mistake on the platform that will be resolved in the 8.1 version. We muted these errors by adding the following line above it:

!evaluateXPathToBoolean(
	xq`child::*/child::*[name() = ${column.currentNodeName}]`,
	tableNode,
	// @ts-expect-error The types of ReadOnlyBlueprint and IDomFacade are incompatible. This will be fixed in 8.1
	readOnlyBlueprint
)

For the other errors, I invite you to look at our commits on the dita-example editor.

Next steps

We are continuing to address TypeScript errors, warnings and difficulties in both the example configuration and in the platform. If you run into errors that you need help with, please contact us through our support board. Also keep your eyes on the Fonto: Why and How series! They will cover problems other partners had with TypeScript upgrades, upgrades in general and just Fonto as a whole!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top