Using Angular 2 + Webpack

What is Webpack?

“webpack is a module loader” that “takes modules with dependencies and generates static assets”. Additionally, it provides a plugin system and methods for processing files.
for more information about webpack you can redirect to this awesome ng-conf video https://www.youtube.com/watch?v=4tQiJaFzuJ8

Module Loading, File Processing, Using NPM as a Task Runner

So, what does this mean?
example :
  • app.js is dependent on 1.js and 2.js
  • 2.js depends on 1-2.js
So we have Webpack running just on app.js, and it will compile a file that contains all files. webpack find statements in app.js that indicate a dependency (import or a require).
In app.js, we have:
const one= require(‘./1.js’);const two= require(‘./2.js’);
Webpack knows that needs to find those files, read them, find any of their dependencies, and repeat the process until get a file with no dependencies.
The file 1.js is such a file. In 2.js, though, we have:
const oneTwo= require(‘./1-2.js’);
So, Webpack finds 1-2.js with no dependencies, and stops its processing.
What we end up with is all 4 files, compiled together and usable in the browser. In a nutshell, this is what module loading does.
 Webpack provides a processing mechanism called loaders.
what loaders do?
Say we have a TypeScript file. To compile a TypeScript file to JavaScript, it needs to be run through the TypeScript compiler.
There are Webpack loaders that will do just that. We can tell Webpack that, when encounters .ts files, it should run those files through the compiler.
The same can be done for virtually any file type .
This allows us to use Webpack as a  build system to get Angular 2 into the browser.
 
So…we could use Webpack to perform many of our build process functions, but not how to invoke them.
We have a lot of task runners (Grunt, Gulp, Broccoli,….) so why not use one of those?
b/c, NPM, which we already use to install our project dependencies, provides a simple way for running tasks.
Each project that uses NPM needs a package.json file.
On package.json we have an script section, this section is a json object with keys and values, where keys are the name of our tasks and values are runnable scripts

So, if we have the following in our package.json:


“scripts”: {
      “task1”: “node ./scripts/task3.js”,
      “task2 “: “node node_modules/task2”,
      “task3 “: “baz some/config.file”
  }…

To run these tasks, all we need to do is say npm run [task name]. To run foo, we’d just do:

npm run task1
and the command node ./scripts/task1.js would be run.

Creating the NPM Project

create an NPM project steps:
  1. Create a directory.  e.g. ng2-with-webpack,
  2. cd ng2-with-webpack
  3. Run npm init -f. This will create a  package.json file.

Installing Dependencies

The following commands should all be run from the directory you created in step 1.
  1. Angular Dependencies
    1. Angular 2 is broken into a lot of packages under the @angular organization in NPM.
      We can get them throw a single install operation:
      npm i -S @angular/common @angular/compiler @angular/core @angular/platform-browser @angular/platform-browser-dynamic es6-shim reflect-metadata rxjs@5.0.0-beta.6 zone.js
  2. TypeScript Dependencies
    1. Since TypeScript is going to be used in this project, we’ll also need to pull it in as a dependency.
      npm i -D typescript tslint typings
  3. Webpack Dependencies
    1. npm i -D webpack webpack-dev-server html-webpack-plugin raw-loader ts-loader tslint-loader
  4. Unit Testing Dependencies
    1. we’ll be using Karma as our test runner with Jasmine
      phantomjs-prebuilt karma-sourcemap-loader karma-webpack

Configurations

TypeScript Configuration

Type Definitions
First, we’ll need to create the typings.json file by running a command from the root of our project:
./node_modules/.bin/typings init
          It will contain the name of the project and an empty dependencies object. We’ll use the install command to fill that object.
There are three files to install, but we need two commands:
./node_modules/.bin/typings install dt~jasmine env~node --save --global
./node_modules/.bin/typings install es6-promise --save
Your type definitions are now installed.
Configuring TypeScript
The TypeScript compiler requires a configuration file, tsconfig.json. This file have two sections: compilerOptions and exclude. There are other attributes in this object, but we will be talking about these two.
The compiler options:
“compilerOptions”: {
“emitDecoratorMetadata”: true,
    “experimentalDecorators”: true,
                              Angular 2 use decorators, and this rule make TypeScript know that it can use them. The reflect-metadata library we pulled in (dependencies section) is used with these rules to utilize decorators properly.
   “module”: “commonjs”,
    “moduleResolution”: “node”,
                    With this rules, the compiler knows we’ll be using CommonJS modules and that they should be resolved the way Node resolves its modules.
                        “noImplicitAny”: true,
    “suppressImplicitAnyIndexErrors”: true,
                    Using typing system, you can specify any as a type, the first attribute above prevents us from not specifying a type.
   “removeComments”: false,
                    When TypeScript compiles our code it will preserve any comments we write.
   “sourceMap”: true,    “target”: “es5”},
                    We’ll be compiling to ES5. So…we’re going to need TypeScript create sourcemaps for us in order to see in browser and use debugging tools.
   The exclude section:
         In this section we’ll tell the compiler what to ignore during compilation.
         we have a file section that not support globbing, so… we end up entering every file we need TypeScript to compile and thats is serious problem only after a few file
“exclude”: [    “node_modules”,    “typings/main”,    “typings/main.d.ts”]
               This will exclude the node_modules directory and the use of the type definitions found in the maindirectory, as well as the main.d.ts file of the typings directory.

Linting

Lnting is the process of running a program that will analyse code for potential errors.
we’ll use the tslint to achieve this. It uses the file tslint.json to describe the rules for how code linting should behave:
{
    “class-name”: true,
This will ensure our classes to be Pascal-case
    “comment-format”: [true,”check-space”],
spaces between slashes and comment (// coment here).
    “indent”: [true,”spaces”],
This rule enforces a consistent indentation style. The default style is 4 spaces
    “no-duplicate-variable”: true,
This will prevent redeclaring variables in the same scope.
    “no-eval”: true,
This disables the use of eval.
    “no-internal-module”: true,
TypeScript’s module keyword causes confusions in the past, we’ll prevent its usage.
    “no-trailing-whitespace”: true,
This will ensure we’re not leaving spaces lines.
    “no-var-keyword”: true,
ES2015 allows variables to be block-scoped by using const and let. To help achieve this clarity, this attribute tells tslint to raise a flag when it sees that we’ve used the var keyword.
    “one-line”: [        true,        “check-open-brace”,        “check-whitespace”    ],
An opening brace must be on the same line as the statement it is for and it needs to be preceded by a space.
   “quotemark”: [        true,        “single”    ],
Checks all strings to be surrounded by single quotemarks. if you want to use double, change “single” to “double”.
   “semicolon”: true,
Ensures that our lines will end with a semicolon.
   “triple-equals”: [        true,        “allow-null-check”    ],
This tells us to use triple equals.
   “typedef-whitespace”: [true,{“call-signature”: “nospace”,”index-signature”: “nospace”,”parameter”: “nospace”,”property-declaration”: “nospace”,”variable-declaration”: “nospace”}],
In this rule when defining types there should’t be any spaces on the left side of the colon.
   “variable-name”: [        true,        “ban-keywords”,        “check-format”    ],
Prevent the usage of any TypeScript keywords and that variable names are camelCase and constants uppercase.
    “whitespace”: [        true,        “check-branch”,        “check-decl”,        “check-operator”,        “check-separator”,        “check-type”    ],
Branching statements checks.
 

Configuring Karma

Karma relies on its configuration file in which we specify which files should be tested and how.
The file, typically named karma.conf.js, is usually the only file needed.
In our setup, we’ll have a second file, named karma.entry.js that will contain extra setup to work with Angular 2 and Webpack.
We’re going to start developing our folder structure a little more here, to keep things clean as we proceed. Create a directory named karma in the root of your project.
Save the files described in the following two sections inside this directory.

Setting Up karma.conf.js

Karma configuration files export a single function which takes, the Karma configuration object as a parameter.
We’ll investigate some of the properties for this object below.
‘use strict’;
module.exports = (config) => {
    config.set({
        autoWatch: true, this will reload test when files changes
        browsers: [‘Chrome’, ‘PhantomJS’], this tells Karma in which browsers it should run the tests
        files: [ ‘../node_modules/es6-shim/es6-shim.min.js’,’karma.entry.js’], the files we’ll be asking Karma to track
        frameworks: [‘jasmine’],
        logLevel: config.LOG_INFO, we’ll see the output from console.infoconsole.warn, and console.error
        phantomJsLauncher: {        exitOnResourceError: true   }, shut down if Karma throws a ResourceError.
        preprocessors: {‘karma.entry.js’: [‘webpack’, ‘sourcemap’]},
        List of preprocessors on our karma.entry.js file, Webpack and  Sourcemap installed with karma-webpack and karma-sourcemap-loader.
Karma and Webpack work together to look for the dependencies starting with karma.conf.js and load sourcemaps as they run.
         reporters: [‘dots’], tells Karma to use the dots reporter, but if the test fails outputs the description.
         singleRun: false, Karma can keep running after it completes running all the tests
         webpack: require(‘../webpack/webpack.test’), Webpack configuration file is location
         webpackServer: {            noInfo: true        } Webpack for use with Karma
    });};

Setting Up karma.entry.js

 karma.entry.js is the starting point of our test and application files when using Karma.
Webpack provides a file as an entry point, and then it looks for dependencies and loads them file-by-file.
We can tell Webpack to get just out test files,  which will all be suffixed .spec.js. Since we’re going to test from those test files, we’ll load all the files we need.
We need to pull in some dependencies. The ones you may not notice are those from zone.js. Zone is a library for doing change detection.
require(‘es6-shim’);
require(‘reflect-metadata’);
require(‘zone.js/dist/zone’);
require(‘zone.js/dist/long-stack-trace-zone’);
require(‘zone.js/dist/jasmine-patch’);
require(‘zone.js/dist/async-test’);
require(‘zone.js/dist/fake-async-test’);
require(‘zone.js/dist/sync-test’);
const browserTesting = require(‘@angular/platform-browser-dynamic/testing’);
const coreTesting = require(‘@angular/core/testing’);
coreTesting.setBaseTestProviders(
    browserTesting.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
    browserTesting.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
The first two are libraries we’ll need for testing provided by Angular.
They will let us set the base Angular providers we’ll need to run our application.
Then, we’ll use those imported libraries to set up the base test providers.
const context = require.context(‘../src/’, true, /\.spec\.ts$/);
context.keys().forEach(context);
here we start pulling in our .spec.ts files from our src directory.
The .context method comes from webpack.The second parameter tells Webpack to look in subdirectories for more files.
The context has a map where each key is the name of a file, so iterating over that array and as a result we have all the test we require to run
Error.stackTraceLimit = Infinity;jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
Jasmine setup. The timeout is used when we test asynchronous processes.If we don’t set this properly, some of our tests could hang forever.

Configuring Webpack

For our purposes, we will have just a webpack.dev.js and a webpack.test.js.
The .dev configuration will be used when spinning up the Webpack development server so that we can see our application in the browser.
In your project directory, create a sub-directory named webpack, which will house both of these files.

         Setting up webpack.test.js

‘use strict’;
const path = require(‘path’);
const webpack = require(‘webpack’);
The path library is a Node core library. We’ll use it for resolving full file paths.
We’ll also going to need to pull in Webpack to use it.
module.exports = {
devtool: ‘inline-source-map’,
defines that we’ll be using inline source maps as our debugging helper.
module: {
preLoaders: [
{ exclude: /node_modules/, loader: ‘tslint’, test: /\.ts$/ }
],
loaders: [
{ loader: ‘raw’, test: /\.(css|html)$/ },
{ exclude: /node_modules/, loader: ‘ts’, test: /\.ts$/ }
]
},
Loaders are transformations that are aplyed to a file in our application, this functions that are executed in nodejs takes as a parameter the src of a file and returns a different src.
Our first loader will take .css and .html files and pull them in raw, whithout doing any processing, but will pull them in as JavaScript modules.
We’ll then load all .ts files with the ts-loader we installed before, which is going to run each file through the TypeScript compiler.
The exclude attribute allows us to avoid compiling any third-party TypeScript files.
In this case, it will avoid pulling in any TypeScript files from the node_modules directory.
    resolve: {
extensions: [”, ‘.js’, ‘.ts’],
modulesDirectories: [‘node_modules’],
root: path.resolve(‘.’, ‘src’)
},
This section lets Webpack know which types of file extensions it should be loading.
The empty string is needed for pulling in Node modules which do not need to provide an extension.
The root directory for our modules is our src directory and that any external modules can be found in the node_modules directory.

tslint: {
emitErrors: true
}};
tslint-loader to display any errors it finds in the console.
     This file, working with the two Karma files created, will power all of our unit testing.

     Setting Up webpack.dev.js

‘use strict’;
const HtmlWebpack = require(‘html-webpack-plugin’);
const path = require(‘path’);
const webpack = require(‘webpack’);
const ChunkWebpack = webpack.optimize.CommonsChunkPlugin;
const rootDir = path.resolve(__dirname, ‘..’);module.exports = {
debug: true,
devServer: {
contentBase: path.resolve(rootDir, ‘dist’),
port: 9000
},
devtool: ‘source-map’,
The devServer attribute describes how we want webpack-dev-server to be set up.
This says that the location from which files are served will be the dist directory of our project and that we’ll be using port 9000.
Don’t worry about creating a dist directory, as the dev server is going to serve all of our files from memory.
You will actually never see a physical dist directory be created, since it is served from memory.
However, doing this tells the browser that the files are coming from another location.
          entry: {
app: [ path.resolve(rootDir, ‘src’, ‘bootstrap’) ],
vendor: [ path.resolve(rootDir, ‘src’, ‘vendor’) ]
},
Here, we’re telling Webpack that there are two entry points for our code. One is going to be src/bootstrap.ts, and the other will be src/vendor.ts.
The file vendor.ts will be our entry to load the third-party code, such as Angular, while bootstrap.ts is where our application code will begin.
You’ll notice that we don’t need to provide the .ts to the files.
          module: {
loaders: [
{ loader: ‘raw’, test: /\.(css|html)$/ },
{ exclude: /node_modules/, loader: ‘ts’, test: /\.ts$/ }
]
},
output: {
filename: ‘[name].bundle.js’,
path: path.resolve(rootDir, ‘dist’)
},
The name of each file will be its key in the entry section with a .bundle.js suffix. So, we’ll end up serving an app.bundle.js and a vendor.bundle.js.
plugins: [
new ChunkWebpack({
filename: ‘vendor.bundle.js’,
minChunks: Infinity,
name: ‘vendor’
}),
new HtmlWebpack({
filename: ‘index.html’,
inject: ‘body’,
template: path.resolve(rootDir, ‘src’, ‘app’, ‘index.html’)
})
],
The two plugins we pulled in earlier — ChunkWebpack and HtmlWebpack — are utilized in the plugins section.
The chunk plugin makes Webpack pull in the file which is referenced many times only once.
The HTML plugin keeps us from having to add <script> tags to our index.html. It just takes the bundles we created in the output section and injects them into the <body> of index.html.
          resolve: {
extensions: [ ”, ‘.js’, ‘.ts’ ]
          }};

Task Running Configuration (via NPM)

To execute any of the threads, we can run Node commands as follows:
node ./node_modules/.bin/webpack –config webpack/webpack.dev.jsWe’ll be creating the following tasks:
*
* Manual linting,
* Running the dev server,
* In-browser (Chrome) testing, and
* Headless browser (PhantomJS) testing.
Doing this is very simple, you can add the following to your scripts section of your package.json:
“lint”: “tslint ./src/**/*.ts”,
“start”: “webpack-dev-server –config ./webpack/webpack.dev.js”,
“test”: “karma start ./karma/karma.conf.js”,
“test:headless”: “karma start ./karma/karma.conf.js –browsers PhantomJS”
The test task will run the unit tests in both Chrome and PhantomJS, while the test:headless task will run them just in PhantomJS, as specified by the browsers flag.
 
NPM also has the concept of lifecycle events, each of which can be run through npm [event].
NPM will run Karma which will, in turn, leverage Webpack to load all of our test and application files.
Once our test and application modules are all loaded, Karma will execute our tests and let us know what succeeded and what failed.

Conclusion

We were able to set up TypeScript, Karma, Webpack, and our test running doing simple things!.

Related posts

Comments are closed.