The team is now working on the WordPress Interactivity API. This unblocks the same UX Frontity framework enabled but directly in WordPress Core, fully compatible with the new Site Editor.
Right now we are merging the types of a package with the types needed for actions and components. It’d be great to to merge somehow the packages types.
They get verbose if you need to add multiple things like state, actions and libraries.
Unnecessary for the MyPackage type when you use it to type your package in src/index.js. I’d say they are even bad because you have optional types that are not really optional.
Unnecessary when you import this type in other packages. Because this types from other packages doesn’t belong to it. They are only there because they are used internally in some action or component.
Possible solution
Initial proposed solution (click to open)
I propose to use two different types in the types.ts file, one exclusively for the package, and the other a merge with the external packages for actions and components.
I think it’s a cleaner approach, although it means people need to understand this a bit better because they need to use these to types in different places.
They have to remember to use Packages in their Action and Derived.
I did a quick test in our frontity.org repository and it looks like we can simply use the intersection operator to do a deep merge of all the packages.
Also, if some package depends on other packages (as Router and Source do) you can still pass all the merged packages to them in the same type definition.
Do you all agree to use Packages? @mmczaplinski what do you think?
orballo10
Packages sounds good to me
1 Like
mmczaplinski11
Yeah, Packages sounds fine to me
David12
Well, I tried to implement types following what we discussed here for the analytics packages and some problems appeared. In order to avoid them we can follow these rules:
Do not use generics for exported code that is mean to be imported by other packages
Avoid exporting types that are only used internaly (the [key: string] part)
When an action could be reimplemented, try to pass the less possible amount of packages to that action type so it’s more difficult to have a type conflict.
I guess that what we were missing in https://www.youtube.com/watch?v=WKTXO9ziZ6g was that we were importing the actions (code) from the original @frontity/analytics package.
I’ll try to review the PR today.
luisherranz14
We’ve been using this approach lately with success, but we just bumped into a problem.
Right now our types have a name field, which is defined as the final string, like this:
interface SomePackage extends Package {
name: "@some-org/some-package";
// Other types, like state, actions...
}
interface OtherPackage extends Package {
name: "@some-org/other-package";
// Other types, like state, actions...
}
When we merge more than one type containing a hardcoded name, TS 3.9 returns never so it’s not usable.
export type Packages = SomePackage & OtherPackage; // Returns `never`.
This used to work on TS 3.8.
The possible solutions are:
We get rid of name in the types. I think we’re not using it and we have that information elsewhere, but I have to take a look to confirm this.
Use string for the name type, like this:
interface SomePackage extends Package {
name: string;
// Other types, like state, actions...
}
I just remember the reason I used the hardcoded package names back then. It was to do the match of the types in the frontity.settings.ts file and have some type safety in that file.
Something like this. The state inside the ExtensionExample1 and ExtensionExample2 is based on a DeepPartial of the state of those types. The matching is done thanks to the name field.
It looks like we are going to need a MergePackages util to solve this problem:
import { MergePackages } from "frontity/types";
export type Packages = MergePackages<SomePackage, OtherPackage>;
mmczaplinski18
I’ve played around with the implementation and came up with the following type for merging the packages. Works great, the only thing that I don’t like is that the Packages have to be passed as a tuple Merge<[Package1, Package2]> instead of individual parameters like Merge<Package1, Package>:
// Change the type of the package `name` from the string literal
// like "@frontity/wp-source" to just `string`.
type NameToString<T> = 'name' extends keyof T
? Omit<T, 'name'> & { name: string }
: T;
type MergePackages<T extends unknown[], U = T[number] > =
(U extends any ? (k: NameToString<U>)=> void : never) extends ((k: infer I)=> void) ? I : never
// ---------- Example
type Package1 = { name: 'a', x: string, y: string}
type Package2 = { name: 'b', z: string}
const packages1: MergePackages<[Package1, Package2]> = { name: 'test', x: 'hello', y: 'hi', z: 'hola' }
I know it’s really ugly compared to Michal’s solution, but I think that tuples is less standard in terms of syntax 🤷
Also, I think we can remove name instead of replacing it for a string because it’s not required for that use. And if someone wants to extend several packages at once that were merged using MergePackages he/she can add it back.
yeah, that’s fine because it’s an implementation detail Actually, I think it’s a better solution than my typescript jiu jitsu
1 Like
David22
Final implementation
New types
Added the MergePackages utility. It simply removes the name property from each package and returns the intersection of all of them, as specified in the implementation proposal. It is exported from frontity/types.
import { MergePackages, Derived } from "frontity/types";
import Source from "@frontity/source/types";
import Router from "@frontity/router/types";
interface MyPackage {
state: {
// Derived prop that depends on source and router packages.
someProp: Derived<Packages>;
};
}
// All packages merged together.
type Packages = MergePackages<Source, Router, MyPackage>;
Moved the frontity namespace from Package to the new Frontity interface. Now, if you need to access properties like state.frontity.url, state.frontity.rendering, etc. you have to import Frontity from frontity/types and include it in MergePackages as any other package.
import { Frontity, MergePackages, Derived } from "frontity/types";
interface MyPackage {
state: {
// Derived prop that depends on the frontity namespace.
someProp: Derived<Packages>;
};
}
// All packages merged together.
type Packages = MergePackages<Frontity, MyPackage>;
Other changes
Adapted all packages to use MergePackages and Frontity types if needed.
Refactored the Settings type to improve package types resolution.