ScandiPWA Migration to TypeScript
If you just landed here and haven’t heard of ScandiPWA, let me introduce you: ScandiPWA is the first complete PWA theme for Magento 2, developed within Scandiweb. It is a fast, offline-ready Progressive Web App solution. The theme is based on React and Redux, leveraging technologies like GraphQL, Varnish and Redis to improve the performance of your website.
Phase 2: Major development
ScandiPWA is maintained by the Core Team that, in April 2022, decided it was time to put into action the second phase of major development for the product. After tracing a roadmap, our developers started refactoring and refining the code, aiming for a smooth experience and fast performance, and switching to newer technologies, such as Typescript.
Today we will cover the benefits of migrating to Typescript and describe some unexpected discoveries that led to needing more time than estimated at first.
Benefits of migrating to Typescript
- Typescript offers better IntelliSense suggestions, which greatly improves the developer experience and performance. With proper suggestions, we know what exactly data we have, what we have to pass to functions, and what we get in return.
- It assists during upgrades to the next version, refactoring, etc. With strict types, we definitely know what exactly was updated and what wasn’t that could lead to issues.
- Using Typescript can prevent many possible issues and make the project code more stable and predictable.
Diving into the development process
During the migration process, our team encountered several issues. First, we decided to fetch our backend GraphQL schema and generate TS type from it. This was supposed to help us reuse the data structures we get from the API. The issue is that the GraphQL schema is a tree. You can divide it into sub-types and make a big tree-like type of it. It works well until you have aliases in your requests. With aliases, you have a data type from a schema that doesn’t match the requested data type. Because of this, we had to skip this idea and add typings to all our requests manually.
The team then decided to start the TS migration from small, primitive functions and slowly move to more complex functions and classes. The idea was that simpler functions are easier to type and they would already show what exactly they accept and what they return. When we realized that this result wasn’t as expected, we decided to go the opposite way. We knew the exact data structure we get from the API (we make requests from the parent classes, dispatchers, etc). So this is where we would start, and then go deeper.
Along the way, new issues were discovered:
1. The manner to make the code in JS and TS is very different. While JS is flexible and allows us to do anything, TS will argue until we find the only acceptable way.
2. We aren’t allowed to refactor code to have backward compatibility with the previous release. So we could make only some minor adjustments to match types, nothing else.
3. We wanted to reuse the same types throughout the project. This isn’t possible, because there are many data transformations. So you get data and pass it to a function. The function transforms the data to another structure and returns the same data, but the type is different.
4. There is a lot of dynamic code where we cannot actually know what data we have at a certain point.
5. There are a lot of fallbacks in the code. When we didn’t get the value, we assigned an empty string, empty object, an empty array, or got the value from a different source instead.
6. There is a lot of prop drilling but we cannot be sure how many types that prop can actually have. Since a part of the object properties is the same and a part is different, the fallback will work and the component will still be rendered.
7. If you pass a string or array of strings, JS will not have any issue when you concatenate such value to another string. While TS will alert about it. Same with other types.
8. Having reusable components, reusable types, data transformations between the components, and a deep prop drilling led to the issue that when you adjust the type for one component, it immediately breaks the other one.
9. React TS types don’t work well with generic class components. Since in ScandiPWA all components must be extendable, this issue is yet to be fixed.
Estimations vs. Work complexity
The estimations were that the migration to TypeScript would take a bit more than one month, and we would be done by the end of May.
Right now we are at the beginning of July, so it turns out it exceeded the estimate some x2. This is because we heavily underestimated the amount of work and its complexity. The product migration alone took 3 - 4 weeks, which is a lot. ScandiPWA code was never made to be typed, so it caused a lot of problems.
The cool thing is, if we previously worked with all products being the same, now we know that despite being the same variable, each product is different, be it configurable type, simple type, etc. This makes it all much easier to work with from now on, but takes ages to implement.
We also stumbled across the general complexity of React classes for typization. They just did not want to be easily extended! It took some time to provide additional typings for containerProps, containerFunctions, and all minor things we need here and there :)
In general, the migration is almost ready, and ScandiPWA will see exciting updates in the upcoming releases!
Want to know more?
If you’re interested in hearing more about this, watch our demo videos that are recorded weekly by ScandiPWA maintainers: Demo Videos.