Markd 2 - Total Re-Write using React, Mobx-State-Tree and Parse-Server
I last wrote about Markd back in September of last year so I think its about time I gave you an update on our latest progress on the project.
Just a quick reminder about Markd; its a super cool project that I worked on for my long-term client and friend Brandon Wu.
At its core, its a chrome-extension that lets you “bookmark people”. So for example, say you stumble across a cool games developer on Twitter and you want to make a note of him for a project you have planned in the future. You can simply click the Markd icon in Chrome to “mark” them. They are then added to your searchable collection for the future.
Check out the video below for a quick demonstration:
As mentioned previously the tech stack in Markd 1 was a combination of Aurelia for the extension with Microsoft’s ASP.net Core on the backend and hosted on Azure.
The problem was that I was never very happy with the way things were on the code level. There was no sharing between the extension and the client which led to many issues and bugs in conversion from one data format to another. I also had problems building Aurelia as every time I went back to it, everything had changed and I could no longer build. None of the docs matched what I had and it was generally a nightmare.
I decided I need a do-over, this time I decided that I wanted the entire thing in the same language so I could share the code between the client, server and extension.
Just a quickly; one of the new Typescript features I took advantage of for Markd2 was “non-nullable types“. To be honest this feature is so great it should be in every language, I strongly recommend reading up on it as it totally destroys an entire class of bugs.
I have been using React for years now, and IMO its pretty much how the UI should be done. I wont talk about it here because well.. if you dont know about React yet then you are doin’ it wrong.
For state management I started off by using my favourite, MobX but discovered there was an update on their sister project MobX State Tree (MST) that I had been following for a while so decided to give it a whirl and fell in love.
MST is a state management library similar to Redux except unlike Redux the properties in your model are reactive. That is you can listen to them and create “computed” variables that react when that property changes.
So for example here is part of the model for a Mark:
As you can see we can define a number of properties in a strongly typed manner in the “model”, then we can declare computed properties such as
hasBeenSavedToServer in the “views” then we can define actions that can mutate the model in “actions”. Those types are checked at compile time (thanks to Typescript) and runtime (during development) so you can catch even more of those pesky Javavascript typing issues.
Its a nice little system that combines the best of MobX and Redux.
I used MST to great effect in both the client and extension. I was able to reuse a lot of code between the two which made for less bugs.
For the backend I decided to go with Parse Server. Why go with a defunct project I hear you cry? Well since Facebook decided to shut down Parse back in Feb of this year Parse has actually continued to grow in its Open-Source form and is constantly being added to.
I know Parse very well having used it before on many projects. With Parse you get a lot of things right out of the box such as Authentication, Data Storage, REST Endpoints, Live Object Updates and many others. All stuff I would have had to build up myself (and likely gotten wrong).
One of the other benefits of using Parse is you get a pre-built dashboard on your database which I find incredibly helpful:
Sure, you could say that editing databases manually is a bad idea but during development it can be quite the godsend.
Writing tests is almost as nice as C#. The above example is one test for the Mark model I used above. We are able to mock the return of the service, await the return of an async operation and assert the result, pretty sweet!
One solution that has evolved over the yeas is lerna. Lerna attempts to manage the dependencies between packages automatically. The idea is that you structure your projects inside the solution directory such as:
Lerna then manages the dependencies between the packages by using symlinks (
npm link). This makes me shudder every time. I cant believe this is 2017 and we are using symlinks for dependency management 😢
I had issues when using Lerna. Libraries were getting included twice in my build and other various issues. In the end I ended up using the very new yarn workspaces. Yarn workspaces attempts to formalize the concepts in Lerna by baking them into the dependency management tool itself.
Im not sure exactly how or why this worked for me, but it did so im not going to argue with it and spend any more time on it (can you tell im frustrated with this crap?).
Ive got a fair bit to say about this so have moved it out into its own blog post.
For the “client” (the SPA website https://markd.co) I decided to go with the incredibly popular create-react-app this took away much of the build for dev / build for productuon production headaches that I usually faced with JS build tools.
Because I use typescript I had to use a slight variant on the project, if you want to do the same I reccomend starting with this: https://github.com/Microsoft/TypeScript-React-Starter
For this rewrite I decided to go with static hosting using a combination of Route53, Cloudfront and S3 to host the static part of the website (client) then used Heroku backed by mLab for the server and API. That’s a fair few moving pieces but in the end it gives us total flexibility with minimal cost.
Because the static part of site is on S3 and Cloudfront CDN its really quick.
Im really happy with the result of Markd2 the site is considerably faster, thanks to being hosted on Cloudfront and Heroku. Its less bug-prone because there is lots of shared code, plus im using the latest and greatest Typescript features. Its more reliable because its got a bunch of unit tests to ensure changes dont break anything. Its more flexible because we control the entire stack.
It took me quite a bit longer than I had hoped to get to this stage but now its finally there, it should be a solid foundation moving forward.