ProseMirror is now a TypeScript project

@marijn did an error slip through here? result appears to be undefined here:

Yeah, no, that has nothing to do with TypeScript. I just accepted a PR without double-checking every little piece, and it was a broken PR. 1.24.1 should fix this.

1 Like

Hi @marijn, We are using some fix versions of prosemirror and since yesterday we are getting some errors while trying to build our project.

@types/prosemirror-commands”: “1.0.4”, “@types/prosemirror-history”: “1.0.3”, “@types/prosemirror-inputrules”: “1.0.4”, “@types/prosemirror-keymap”: “1.0.4”, “@types/prosemirror-model”: “1.16.1”, “@types/prosemirror-schema-list”: “1.0.3”, “@types/prosemirror-state”: “1.2.8”, “@types/prosemirror-view”: “1.23.1”, “prosemirror-commands”: “1.2.2”, “prosemirror-dropcursor”: “1.4.0”, “prosemirror-gapcursor”: “1.2.2”, “prosemirror-history”: “1.2.0”, “prosemirror-inputrules”: “1.1.3”, “prosemirror-keymap”: “1.1.5”, “prosemirror-model”: “1.16.1”, “prosemirror-schema-list”: “1.1.6”, “prosemirror-state”: “1.3.4”, “prosemirror-tables”: “1.1.1”, “prosemirror-view”: “1.23.9”

Have you also modified any of the above-listed versions? Thanks a lot in advance.

NPM does not make it possible to modify previously released versions. Sounds like you upgraded some packages to versions that include type declarations, but have old versions of other packages. That doesn’t work, because the new type declarations don’t 100% match the definitelytyped declarations. Try upgrading everything and doing a full reinstall (removing the package lock).

Thank you very much for your quick response! :slight_smile:

I’ll try to align all versions.

Best Regards

For nodeView, the docs said:

For nodes, the third argument getPos is a function that can be called to get the node’s current position, which can be useful when creating transactions to update it. For marks, the third argument is a boolean that indicates whether the mark’s content is inline.

So it seems that the typing of nodeView should be as follow:

nodeViews⁠?: Object<
fn(
  node: Node,
  view: EditorView,
- getPos: fn() → number | undefined,
+ getPos: (fn() → number | undefined) | boolean,
  decorations: readonly Decoration[],
  innerDecorations: DecorationSource
) → NodeView
>

Hi again, I just wanted to confirm that the issue was related to incompatible versions. In fact, I was not setting prosemirror-transform dependency in my package.json at all but it seems prosemirror was automatically installing version 1.4.2 which was not compatible with the other modules installed. I hope it helps.

Thanks a lot for your tips

This seems a dead end – even if we make the function type wide enough to cover both cases, it’ll be royal pain to use, because inside the function you’ll get the uselessly wide types. prosemirror-view 1.25.0 splits out mark views into a separate prop instead, markViews, to work around this mess. (Mark views can still be provided in nodeViews for backwards compatibility).

1 Like

it’ll be royal pain to use, because inside the function you’ll get the uselessly wide types

Yes, we’ve felt that pain when dealing with nodeView in TypeScript. A separate markViews would be a perfect solution for TS users. Thanks!

By the way, do you think it’s good idea to export the NodeViewConstructor and MarkViewConstructor types in prosemirror-view? This would be helpful when I try to write a long and stand-alone nodeView function.

1 Like

Thanks for your epic work @marijn

I agree with the decision to omit the Schema type parameter, we had a code base using that feature, but I don’t think we really got any real benefit out the strong typing in that regard.

But now the Schema type itself doesn’t provide any information on the nodes and marks it defines, one thing I’m seeing upgrading our code base is that working with the schema requires lots of non-null assertions. Basically I am now getting lots of warnings in code like this:

(where schema is our concrete schema instance)

So far this is mostly in test code, which I’m probably going to change to pass the node type as string, but that still feels like a step back.

Would you consider porting the stronger schema typings from the DT types? Something along the lines of:

interface SchemaSpec<N extends string = any, M extends string = any> {
  nodes: { [name in N]: NodeSpec } | OrderedMap<NodeSpec>;
  marks?: { [name in M]: MarkSpec } | OrderedMap<MarkSpec> | null | undefined;
}

interface Schema<N extends string = any, M extends string = any> {
  spec: SchemaSpec<N, M>;
  constructor(spec: SchemaSpec<N, M>);
  nodes: { [name in N]: NodeType } & { [key: string]: NodeType };
  marks: { [name in M]: MarkType } & { [key: string]: MarkType };
}

I guess you have some TypeScript strictness turned on that I don’t have, since for me it assumes subscripting an object like this returns something non-null.

Seems like in most cases, TypeScript wouldn’t be able to infer those type parameters. Are you currently defining them manually?

Ok, you may be right about the extra strictness, that could be noUncheckedIndexedAccess. I will check that and report back.

To your question, I copied those type declarations from the DT types for prosemirror-model, and they did actually properly infer N and M. At least I never manually specified them, so I am assuming they were inferred.

Ok, I actually tested my hypothesis, and it appears to be working quite well. I have created a PR for this change: Add stronger typing for Schema by cmlenz · Pull Request #67 · ProseMirror/prosemirror-model · GitHub

1 Like

Hello everyone, Sorry to post here but we are out of options, can anyone take a look at this stackoverflow problem and help us: https://stackoverflow.com/questions/72647609/new-prosemirror-model-lib-1-18-1-causes-error-in-console-schema-is-not-a-cons Many thanks

We are trying to upgrade to use the new Prosemirror types and although our type checker basically exploded, the experience has been really good.

However, the lack of schema generics is causing loads of problems for us because we do use multiple schemas under different conditions and looking up nodes. On top of that, doing checks like node.type.schema won’t yield our main schema so we don’t get intellisense that includes our nodes and marks. That is going to make our types worse overall but I’m going to try to find a way to just overwrite the default Schema types so we can retain our ability to look up nodes and marks easily. I understand the reasoning though, just wish there was an alternative for those that did use that generic.

Other than that, we only have ran into a few other issues:

  • The SelectionRange class constructor does not seem to be typed. No idea why, maybe it’s the internal comment?
  • Decorations should ideally have a generic to populate spec. Currently, it is just set to “any.” I can try to make my own generic and overwrite the class, but others may find it useful to include a generic
  • Prosemirror-tables using too many generics (which hopefully is addressed from a pending PR. I may just pull in what I need from it and then get rid of our dependency though)

Other than that, the type errors seem reasonable and are a big improvement IMO. There are a lot of good utilizations of readonly which caused some friction, but that is to be expected. I also think these types do a much better job with handling null and undefined.

That’s fixed in prosemirror-state 1.4.1

That seems awkward, since decoration types don’t encode what kind of decoration they are (widget, node, inline) and shouldn’t (since these can be put together in a set, and will usually be accessed through a set). Specs allow arbitrary keys, and it should be easy to just introduce some kind of cast when you’re sure a decoration spec is of a given type and want to narrow it.

1 Like

That seems awkward, since decoration types don’t encode what kind of decoration they are (widget, node, inline) and shouldn’t (since these can be put together in a set, and will usually be accessed through a set). Specs allow arbitrary keys, and it should be easy to just introduce some kind of cast when you’re sure a decoration spec is of a given type and want to narrow it.

I’d argue that is one of the major use cases of generics though. They can be used to assert what a particular value or set of values are. Imagine if I had a typeguard that looked like this

const isMyDeco = (deco: Decoration): deco is Decoration<MySpec> => {
   return Boolean(deco.spec.isMySpec);
}

And that could be used in filters and finds

const myDecos = decorations.filter(isMyDeco);

Now I have an array of decorations that have the correct spec populated and I don’t have to worry as much about that default “any.”

A lot of authors also utilize “unknown” instead of “any” in these circumstances along with a generic to help with strictness.

That being said, you can work around this with a custom type that does take a generic

export interface DecorationWithSpec<S> extends Omit<Decoration, 'spec'> {
    spec: S;
}

And then use that custom type in your typeguards

Just noticed when I’m creating a new Plugin in a pnpm monorepo the prosemirror-view imports don’t resolve. Well, for some reason EditorView resolves but not the EditorProps and I have to manually retype it with: { ... } as EditorProps.

Not sure is this a problem with my setup or should prosemirror-view be added as a dependency to prosemirror-state. Not ideal as that would make them circular and add unneeded downloading of prosemirror-view just for types. Is there a way to use peer or optionalDependencies perhaps?