I've been working on a project for over a year now that was originally written in ES6 with (some async/await ES7) using BabelJS to transpile and really enjoying the experience of using the latest javascript.
ES6/7 was great at the beginning, when I had 1, then 2... then 5 files. The project was small, I could pretty much load the entire application into my own head at once, reason, tweak, plow forward.
It didn't take too long before the project grew beyond a single load into memory. Now I'm mentally paging out components from different files as I work on various sections of the application.
Just like anything, once the project grew to this point, it became a little more difficult to maintain. Now, I consider myself a fairly solid developer, and that's likely how I made it this far without a compiler as my components were small, surface areas tight and the interactions between them were well managed. I also had a decent number of in-app unit and integration tests because generally (but not always) I'm a test-first kind of guy.
However, that didn't stop me from breaking things, making mistakes or just out-right screwing up a javascript file here and there along the way.
While working on this project, it always niggled me that the project would keep growing without the ability for the most basic of unit-tests to run (the compiler). Almost a year ago I even remember trying to use Typescript but using it with JSPM and without having VisualStudio Code all together, it just never came together (or I just didn't try hard enough).
But this past week, I gave it another go, and while I'm not totally there (or where I'd like to end up), I'm quite happy with the results I've made so far and am impressed and quite happily working in a project that has completely been ported to Typescript from ES6/7 using BabelJS.
First some idea about the project.
Now, when it comes to large software projects, I'm pretty sure I shouldn't be calling this project a "large" as the subject of this post seems to label it... But for a system built only by me in some nights and weekends, it is the largest single app I've built alone, so that's where I'm defining "Large".
The project has just about a hundred javascript files/components/classes/modules and comes in just above 12,000
lines of code. That's not counting of course all the dependencies pulled in through JSPM (using both NPM and Github). In fact I really need to look at my dependencies and see where I can trim some fat, but that's not the subject of this post.
Porting from Babel to TypeScript
With some context about the project this is coming from out of the way, I thought it would be helpful to outline the steps (or stumbles) I took along the way to get my project up and running using TypeScript with JSPM.
Pre-migration steps
Below are steps I took to get this thing going. I doubt they're perfect or even apply to your or anyone elses projects, but here's hoping they're helpful
- In a fresh temp folder, I used the
jspm init
command to setup a fresh new project and selected the Typescript
transpiler option.
this allowed me to inspect what a "fresh" project from JSPM would look like with Typescript setup.
- The next thing I did was review the angular getting started guide to see what Typescript specific configurations they used.
Now, my project isn't Angular (it's actually React based), but I thought I could learn a little something along the way. I don't know if I actually gleaned anything while doing this (as I'm writing this post a ways after I actually did the work, but as an FYI, you might learn something reading it)
What steps did I take to port the project?
Looking back at the series of commits during my port, here's basically what I did. In some cases order doesn't matter below, but I left this list in the order of my projects git commit log.
- Renaming each file with the
.jsx
extension to .tsx
(Typescript's variant of JSX) (note: not renaming anything but code I wrote - so don't touch anything in jspm_packages
or node_modules
folders etc.
jspm install ts
<-- installing the Typescript jspm plugin
- Updated my
jspm.config.js
transpiler
flag with the following:
- transpiler: "plugin-babel",
+ transpiler: "Typescript",
+ TypescriptOptions: {
+ "tsconfig": true // indicates that a tsconfig exists that should be used
+ },
Then I updated my jspm.config.js
's app
section with the following.
packages: {
- "app": {
- "defaultExtension": false,
- "main": "bootstrap.jsx",
- "meta": {
- "*": {
- "babelOptions": {
- "plugins": [
- "babel-plugin-transform-react-jsx",
- "babel-plugin-transform-decorators-legacy"
- ]
- }
- }
- }
- },
+ "app": { // all files within the app folder
+ "main": "bootstrap.tsx", // main file of the package (will be important later)
+ "format": "system", // module format
+ "defaultExtension": "ts", // default extension of all files
+ "meta": {
+ "*.ts": { // all ts files will be loaded with the ts loader
+ "loader": "ts"
+ },
+ "*.tsx": { // all ts files will be loaded with the ts loader
+ "loader": "ts"
+ },
+ }
+ },
- Created a
tsconfig.json
file
{
"compilerOptions": {
"target": "es5",
"module": "system",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noImplicitAny": false,
"jsx": "react"
},
"exclude": [
"jspm_packages",
"node_modules"
],
"compileOnSave": false
- Renamed
*.js
files to *.ts
. (Similar to step 1 above with jsx -> tsx
but now just the plain JavaScript files)
- In all of my source code where I used to do this:
import foo from './foo.js'
I removed the .js
extensions like import foo from './foo'
- I did NOT remove the
.jsx
extension in my import
statements - but renamed them to tsx
so import foo from './foo.jsx'
became import foo from './foo.tjs'
- Next I added a file at the root of my client project called
globalTypes.d.ts
, this is where I could hack in some type definitions that I use globally in the project.
- Then I started adding my type definitions...
I used the typings tool to search for TypeScript type definitions. And if I found one, I would typically try to install them from npm
.
For example: searching for react
like typings search react
shows me that there is a DefinitelyTyped version of the type defs and I now know that we can use NPM to install them by typing npm install --save @types/react
So I installed a ton of external library typings.
- Next, started looking around my editor VisualStudio Code in hopes to see a bunch of typing errors reported, and was surprised to see very few. No, not because I'm so good at JavaScript that my TypeScript was perfect. Far from it... The problem I had was the
tsconfig.json
file was not at the root of my project (was at the root of my client site) - but it was nested several folders down from the root of my project. For some reason the editor wasn't picking it up until I opened the editor from the location the tsconfig.json
file was rooted, things didn't work.
Honestly, I don't know what the above was about - but was something I ran into. I can't say for certain if it is still an issue - I think I'm starting to see editor features load up regardless of what folder I open things - so your mileage may vary.
- Once the TypeScript editor features started lighting up in VS Code, my next steps were start to take the TypeScript's feedback and implement either typing work-arounds or fixing actual bugs the compiler found.
THE END - ish
Where am I now?
The above steps were really all I went through to port this project over to TypeScript and it was relatively seamless. That's not to say it was simple or easy, but definitely do-able, and worth it.
It's been a few weeks since I ported the project to TypeScript and I'm really kicking myself for not doing it sooner.
The editor assist with intellisense of function calls from internal and external modules and their usage/function signatures saves time researching external documentation.
Other observations since the move.
- Builds seem to be a little faster with TypeScript than Babel. I can't say I can prove this. I didn't do any actual tests on this, but just a feeling I got after migration.
- Sourcemaps seem to actually work. Whenever I used BabelJS, debugging and stepping through async/await it just never seemed to line up right for me. This was likely user-error or in-proper configuration of babel on my part, so who knows... but having working source-maps is AMAZING, especially with the
async/await
feature.
- One area of concern that I haven't yet worked through. Is the JSPM typescript loading up in the browser - or running
jspm bundle app
at the command line doesn't report any typescript errors - or fail any builds. However, I'm glad it doesn't because something isn't quite rite with my configuration as every import of a .tsx
file reports an error. So, for now I'm just relying on the red squigglies in my VS Code editor to help me catch typing errors.
If you go for this port in your own project, I hope this was helpful, and that your port goes well.
Happy TypeScripting!