Modulization and Bundling with TypeScript and Webpack for JavaScript Full Stack Project
Updated:
Modulization
In a Full Stack JS project, the essence is how to write code consistently and easy to share and maintain because code sharing between server and browser is unavoidably. Before talking about what strategy we are taking, let’s have a review on the trend of the modulization first.
Ancient history
There is no such thing and all things are global. This causes potentially naming conflict and it’s not good for maintenance.
1 | var logo = 'KanbanizeIT'; |
Package/Namespace Concept
Some well-known library, like ExtJS, takes package/namespace concept and they manage the source in this structure.
1 | Ext.ns('com.kanbanizeit'); |
Immediately-Invoked-Function-Expression (IIFE)
This approach reduce the hassle of long namespace and groups all internal stuffs encapsulated. Many small library adopts this style, e.g. jQuery.
1 | var kanbanizeIT = (function() { |
CommonJS/Node.js
This style went popular due to the popularity of Node.js. Each file is a module and all variables defined within that file are only visible in that file. If you have public API want to publish, you need to export it.
1 | // logo.js |
AMD/CMD
CommonJS is suited for server-side JS loading because it’s synchronous file loading in Node.js. But for browser, script loading is asynchronous and so other groups of people propose different styles target for browser-side JS loading.
RequireJS is the representative for AMD style.
1 | define(['a', 'b', function(a, b) { |
sea.js is the representative for CMD style.
1 | define(function(require, exports, module)) { |
ES6
The latest ECMAScript 6 standard has below syntax. However, this is not supported in most of the JS execution environment yet.
1 | // xxx.js |
My strategy
About 2 years ago, I wrote an article “Share code between Node.js and browser“ about this topic already. At that time, I explain why I decided to choose to write the code in Node.js style and then use tools like browserify (Now we has Webpack too) to do the bundling for browser. I still consider my choice is the right way to go.
Now I just changed to write all JavaScript in ES6 format no matter for browser-side or server-side, then do the bundling for browser. This choice enables me to have consistent coding style regardless which environment it’s targeted to. One more reason for me to abandon the hassle introduced from AMD or CMD is that when the HTTP2 era comes, I have no need to do the bundling anymore and just have to skip this step without changing any code.
But browser doesn’t support ES6 syntax now. No worry, you should use a transpiler. Checkout my article TypeScript vs Babel if you know nothing about it.
Bundling
To me, The No. 1 principle of bundling is Single Source of Truth.
How is that? Normally, we need to have some configuration to define a bundle name and file selectors to decide what files to be included in the bundle, like the JAWR used in my company. It is really ugly and error-prone.
Now, if we make use of the modulization and control the dependency through ES6 import
and export
, then we can base on this relationship to control the bundling as well. What we need to do is to specify a bootstrap JS file as entry point and let the tools to figure out all dependencies to be included. No other configuration should be needed unless special requirement is raised, such as bundle splitting for lazy load, etc.
Hence, the dependency specified in the source code itself should be the only single truth.
TypeScript config & folder structure
Sample tsconfig.json
setting is as below which compiles all TypeScript source to be compatible with ES5 standard and use CommonJS style for module usage. Folder structure is simply separated to common
, client
and server
for illustrating purpose. You can further arrange based on your preference.
The compiled source is output to build
directory and retains the original folder structure.

1 | { |
Webpack integration
When integrating Webpack with TypeScript support, we need to use ts-loader
to compile the .ts
file. Sample webpack.config.js
is as below when I am testing to bundle the client-side JavaScript for browser usage:
1 | var webpack = require('webpack'), |
There are two issues discovered after the testing:
- Any compile warning can possibly block the webpack build process. To fix this, we can add a
ts
config in webpack and ignore some particular errors.
1 | module.exports = { |
- Normally, we only need to bundle the code for browser, so the webpack
entry
only specify entries for client-side script. Even though thets-loader
still use thetsconfig.json
to compile all TypeScript, the compiled TypeScript is not output to theoutDir
specified intsconfig.json
and so the server-side TypeScript is not compiled to JavaScript. Then how can Node.js use the TypeScript then?
There is one approach to use typescript-require extension and put require(‘typescript-require’);
to the bootstrap entry js executed by Node.js, .ts
module can be loaded just like the .js
module.
However, I don’t want to go with it because it introduces some exception (Entry .js
file for Node.js and this special extension). I still want all TypeScript source be compiled out to a build folder with exact folder structure.
Gulp Integration
I have to bring in Gulp to control the process instead. First compiles all TypeScript and then calls Webpack to do the bundling by pointing to the build
folder instead of original src
folder. Hence, ts-loader
in webpack.config.js
is not necessary anymore.
gulpfile.js
1 | require('./gulp'); |
index.js
under gulp folder.
1 | ; |
typescript.js
under gulp folder.
1 |
|
webpack.js
under gulp folder. To be aware that the webpack
task has defined a dependency on typescript
task so that it will waits under the typescript
task to complete before it starts.
1 |
|
The execution is as below.
Till now, you should have basic idea on how to work with TypeScript and Webpack for JavaScript full stack project. But there is one more thing, how about CSS? Can it be the same?
CSS dependency
For example, my app uses AngularJS and Bootstrap and I want to include them all like below.
1 | // <reference path="typings/angularjs/angular.d.ts" /> |
How can I setup the Webpack and bundle all dependencies? Here is the complete webpack.config.js
.
1 | var webpack = require('webpack'), |
There are couples of setting took me a while to figure out.
alias
forbootstrap.css
is necessary so that it can be correctly resolved.Without the
require.resolve("url-loader")
, when parsing the CSS file, it complaintsCannot resolve module 'url-loader'
.ExtractTextPlugin
is used to output the CSS as a separated file instead of being embedded in the bundled JS and dynamically generated as a<style>
tag in HTML file during page load.HtmlWebpackPlugin
is used to automatically inject the generated CSS and JS files into the appropriate location in the entry HTML file as below.
I haven’t talked about how to include internal CSS or HTML yet. However, I think you can figure out yourself based on the sample and strategy described here.