We have already talked about building and testing TypeScript projects at the beginning of this book. In this section, we will go a little bit further for frontend projects, including the basis of using Webpack to load static assets as well as code linting.
Modularizing helps code keep a healthy structure and makes it maintainable. However, it could lead to performance issues if development-time code written in small modules are directly deployed without bundling for production usage. So static assets packaging becomes a serious topic of frontend engineering.
Back to the old days, packaging JavaScript files was just about uglifying source code and concatenating files together. The project might be modularized as well, but in a global way. Then we have libraries like Require.js, with modules no longer automatically exposing themselves to the global scope.
But as I have mentioned, having the client download module files separately is not ideal for performance; soon we had tools like browserify, and later, webpack - one of the most popular frontend packaging tools these days.
Webpack is an integrated packaging tool dedicated (at least at the beginning) to frontend projects. It is designed to package not only JavaScript, but also other static assets in a frontend project. Webpack provides built-in support for both asynchronous module definition (AMD) and commonjs, and can load ES6 or other types of resources via plugins.
To install webpack via npm
, execute the following command:
$ npm install webpack -g
Before we actually use webpack to load TypeScript files, we'll have a quick walk through of bundling JavaScript.
First, let's create the file index.js
under the directory client/src/
with the following code inside:
var Foo = require('./foo'); Foo.test();
Then create the file foo.js
in the same folder with the following content:
exports.test = function test() { console.log('Hello, Webpack!'); };
Now we can have them bundled as a single file using the webpack command-line interface:
$ webpack ./client/src/index.js ./client/out/bundle.js
By viewing the bundle.js
file generated by webpack, you will see that the contents of both index.js
and foo.js
have been wrapped into that single file, together with the bootstrap code of webpack. Of course, we would prefer not to type those file paths in the command line every time, but to use a configuration file instead.
Webpack provides configuration file support in the form of JavaScript files, which makes it more flexible to generate necessary data like bundle entries automatically. Let's create a simple configuration file that does what the previous command did.
Create file client/webpack.config.js
with the following lines:
'use strict'; const Path = require('path'); module.exports = { entry: './src/index', output: { path: Path.join(__dirname, 'out'), filename: 'bundle.js' } };
These are the two things to mention:
entry
field is not the filename, but the module id (most of the time this is unresolved) instead. This means that you can have the .js
extension omitted, but have to prefix it with ./
or ../
by default when referencing a file.__dirname
ensures it works properly if we are not executing webpack under the same directory as the configuration file.Now we are going to load and transpile our beloved TypeScript using the webpack plugin ts-loader
. Before updating the configuration, let's install the necessary npm packages:
$ npm install typescript ts-loader --save-dev
If things go well, you should have the TypeScript compiler as well as the ts-loader
plugin installed locally. We may also want to rename and update the files index.js
and foo.js
to TypeScript files.
Rename index.js
to index.ts
and update the module importing syntax:
import * as Foo from './foo'; Foo.test();
Rename foo.js
to foo.ts
and update the module exporting syntax:
export function test() { console.log('Hello, Webpack!'); }
Of course, we would want to add the tsconfig.json
file for those TypeScript files (in the folder client
):
{ "compilerOptions": { "target": "es5", "module": "commonjs" }, "exclude": [ "out", "node_modules" ] }
To make webpack work with TypeScript via ts-loader
, we'll need to tell webpack some information in the configuration file:
.ts
extensions. Webpack has a default extensions list to resolve, including ''
(empty string), '.webpack.js'
, '.web.js'
, and '.js'
. We need to add '.ts'
to this list for it to recognize TypeScript files.ts-loader
loading .ts
modules because it does not compile TypeScript itself.And here is the updated webpack.config.js
:
'use strict';
const Path = require('path');
module.exports = {
entry: './src/index',
output: {
path: Path.join(__dirname, 'bld'),
filename: 'bundle.js'
},
resolve: {
extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
},
module: {
loaders: [
{ test: /.ts$/, loader: 'ts-loader' }
]
}
};
Now execute the command webpack
under the client
folder again, we should get the compiled and bundled output as expected.
During development, we can enable transpile mode (corresponding to the compiler option isolatedModules
) of TypeScript to have better performance on compiling changing files. But it means we'll need to rely on an IDE or an editor to provide error hints. And remember to make another compilation with transpile mode disabled after debugging to ensure things still work.
To enable transpile mode, add a ts
field (defined by the ts-loader
plugin) with transpileOnly
set to true
:
module.exports = { ... ts: { transpileOnly: true } };
To take the advantage of code caching across pages, we might want to split the packaged modules as common pieces. The webpack provides a built-in plugin called CommonsChunkPlugin
that can pick out common modules and have them packed separately.
For example, if we create another file called bar.ts
that imports foo.ts
just like index.ts
does, foo.ts
can be treated as a common chunk and be packed separately:
module.exports = {
entry: ['./src/index', './src/bar'],
...
plugins: [
new Webpack.optimize.CommonsChunkPlugin({
name: 'common',
filename: 'common.js'
})
]
};
For multi-page applications, it is common to have different pages with different entry scripts. Instead of manually updating the entry
field in the configuration file, we can take advantage of it being JavaScript and generate proper entries automatically. To do so, we might want the help of the npm package glob
for matching page entries:
$ npm install glob --saved-dev
And then update the webpack configuration file:
const glob = require('glob');
module.exports = {
entry: glob
.sync('./src/pages/*/*.ts')
.filter(path =>
Path.basename(path, '.ts') ===
Path.basename(Path.dirname(path))
),
...
};
Splitting the code can be rather a complex topic for deep dive, so we'll stop here and let you explore.
As we've mentioned, webpack can also be used to load other static assets like stylesheet and its extensions. For example, you can use the combination of style-loader
, css-loader
and sass-loader
/less-loader
to load .sass
/.less
files.
The configuration is similar to ts-loader
so we'll not spend extra pages for their introductions. For more information, refer to the following URLs:
A consistent code style is an important factor of code quality, and linters are our best friends when it comes to code styles (and they also helps with common mistakes). For TypeScript linting, TSLint is currently the simplest choice.
The installation and configuration of TSLint are easy. To begin with, let's install tslint
as a global command:
$ npm install tslint -g
And then we need to initialize a configuration file using the following command under the project root directory:
$ tslint --init
TSLint will then generate a default configuration file named tslint.json
, and you may customize it based on your own preferences. And now we can use it to lint our TypeScript source code:
$ tslint */src/**/*.ts
As we've mentioned before, an advantage of using npm scripts is that they can handle local packages with executables properly by adding node_modules/.bin
to PATH
. And to make our application easier to build and test for other developers, we can have webpack
and tslint
installed as development dependencies and add related scripts to package.json
:
"scripts": { "build-client": "cd client && webpack", "build-server": "tsc --project server", "build": "npm run build-client && npm run build-server", "lint": "tslint ./*/src/**/*.ts", "test-client": "cd client && mocha", "test-server": "cd server && mocha", "test": "npm run lint && npm run test-client && npm run test-server" }
3.145.86.211