Exploring Typescript Internal and External Modules and Webpack
Lately, I’ve been trying to upgrade the code base for a personal project with some legacy code in it. Specifically, it’s a web application built with Typescript that:
- Doesn’t leverage CommonJS modules (instead, there are globally namespaced variables)
The purpose of this post is to discuss some of the lessons I learned when transforming the code base from using legacy features to using some better Typescript features. Specifically, I’ll highlight the difference between Typescript internal and external modules, as well as explain the benefit of using a module bundler like Webpack.
For the purpose of this case study, I’ll create a starting point that mimics the project I was working on but simplified for our purpose. I’ve created a Github repo for it — you can see it here (be sure to check out the “no-bundler” branch).
I’ll go ahead and re-produce the 4 main files below as well:
If you want to see the results, be sure to clone down the repo and then run the following 2 commands on the command line:
- npm install
- npm run build
Let’s talk a little bit about the way these files are currently set up.
- Something subtle to notice that has a HUGE impact is the ordering in which the <script> tags are loaded in the index.html file. Because all the functions are loaded into the same global namespace, and because app.ts relies on both functions being available, app.ts must be loaded last (if not, an error will be thrown in the console). In our simple example, it doesn’t matter whether module1.ts or module2.ts is loaded first because neither of these files have other dependencies. This is an important point though because things get a lot more complicated in a realistic scenario where module1.ts and module2.ts also depend on other files. And its these realistic yet complicated scenarios that lead to the need for a module system like CommonJS and a module bundler like Webpack.
For my simplistic starting scenario above, you’re right that the code can probably be left this way and still be ok. But you can already see that this code isn’t ideal because:
- We’re polluting the global namespace
- We need to start worrying about the order in which files are loaded into the <script> tags in our index.html. The files have “dependencies” on each other, and as these dependencies start to grow with application size, we’ll really be in trouble.
So let’s fix things up!
If you’re itching to see how the improved version looks like, check out the same previous Github repo but look at the “master” branch. Remember to clone down the repo, checkout the master branch, and then run the same 2 commands in your command line:
- npm install
- npm run build
But first, let’s look at the same 4 files as before:
Let’s do a step-by-step comparison for these files across the 2 versions:
- The “module1.ts” file is identical to the previous version EXCEPT that it includes the keyword “export” before the “sayHello” function. This is Typescript’s support for modules :) We are now treating this file as a module, and in order to use this module within other files, we’ll need to import this module. This is going to sound anti-climatic but Typescript’s concept of External Modules is exactly this — the ability to create modules that can export different functionality and then import those modules into other modules.
- The major consequence of the changes to module1.ts and module2.ts is that the “sayHello” function and the “add” function are NOT available in the global namespace. This is absolutely fantastic news because we no longer have to worry about naming collisions and polluting the global namespace! As I mentioned in point 1 above, if we want to use the “sayHello” and “add” functions , we’ll now need to import each of them into the file that uses it before we have access to them.
- And this is exactly what we’re doing in “app.ts” now. Compared to the previous version of “app.ts”, we now have 2 import statements at the top of the file which exposes the “sayHello” function on an object called “SomeModule” and the “add” function on an object called “AnotherModule”. If you try calling “sayHello” or “add” without preceding them with their respective module name, you’ll see an error!
At this point, if you load up your browser and check out the index.html file, you should see the same console logs as the previous scenario. To summarize, by making the simple changes in our ideal scenario, we don’t have to worry about:
- Variables polluting the global namespace
- Injecting TONS of <script> tags into our index.html file, which saves us on the number of HTTP requests made and thus reduces the latency of our application