#1 Quick Start
Friday, May 4, 2018
In this article we will create the minimal Webpack configuration file that we need in order to begin developing an application using Typescript, Pug (formerly known as Jade) and SCSS. We will use Webpack to transpile our source files to file types the browser can understand, namely Javascript, HTML, and CSS, and create bundles that can be served to our pages which will result in drastically reducing the number of HTTP calls our pages need to make to the server.
Using NPM to install Node packages
-
WebUi
- package.json
As just about all of the projects that I work on we will start by navigating a console to the root of the project and entering the command
npm init -y
. This will create a bare package.json
file that we can
use to install the packages that we need using NPM. Once that is done you can either
copy the command located in (a) into the same console and press enter or add the
devDependencies
property from (b) into the package.json
file and executing the command npm install
in the console. Either way will cause NPM to install the need
packages. Which ever way you choose to
proceed don't forget to add the "build": "set NODE_ENV=development&&webpack --progress"
property
to the scripts
object.
npm install (1)
npm install --save-dev
autoprefixer
css-loader
extract-text-webpack-plugin
html-loader
node-sass
postcss-loader
pug-html-loader
raw-loader
sass-loader
style-loader
ts-loader
webpack
webpack-cli
package.json
file by saving the name and version of all of the packages that we install.
package-json (1)
{
"name": "web-ui",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "set NODE_ENV=development&&webpack --progress",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^8.4.1",
"css-loader": "^0.28.11",
"extract-text-webpack-plugin": "^3.0.2",
"html-loader": "^0.5.5",
"node-sass": "^4.9.0",
"postcss-loader": "^2.1.4",
"pug-html-loader": "^1.1.5",
"raw-loader": "^0.5.1",
"sass-loader": "^7.0.1",
"style-loader": "^0.21.0",
"ts-loader": "^4.2.0",
"webpack": "^4.6.0",
"webpack-cli": "^2.1.2"
}
}
package.json
file should look like after all of
our packages have been installed.
We need a webpack.config.js file
-
WebUi
- webpack.config.js
The next thing to do is to create our webpack.config.js
file, once again at the root of our project. Our first step
is to define an environment variable and import Webpack itself, autoprefixer and the
extract text plugin. Following this we need to specify the entry point for
our bundles. To start with we just have to entry points. One for our site wide typescript file, ./Source/main.site.ts
and one for our site wide styles, ./Source/main.site.scss
. The next thing to specify is our module object which contains the
rules that we want Webpack to follow when it encounters a specific file. Not only do the rules tell Webpack what to do when it encounters a typescript or pug
file but they also differentiate between a site scss file and a component scss file. The difference between the two is that we want a site stylesheet to be
extracted out of the javascript bundle and put into a css bundle. We do this by using the extract text plugin that we imported earlier. The optimization
object is used to configure how webpack treats common code and takes the place of the
common chunks plugin. In this case we will just test if the code that is being required
is coming from the node modules folder and if it is it will be included in a separate bundle. Once a bundle is created we need to specify where the output
should be saved and we do that using the output object. Next we specify the plugins that we would like to use. We are using the extract
text plugin, which we discussed previously, the loader options plugin to apply autoprefixer to our css styles, and the define plugin so that we can know
whether we are in development or production mode within our typescript files. The resolve object just allows us to leave off the extension for our
typescript and javascript files when we require them. Now that we have all of the pieces we just have to export the module. The code in
(c) contains all that is necessary to start using webpack and can simply be copied and pasted.
webpack.config.js (1)
/*********************************
* Environment and imports
*********************************/
const environment = process.env.NODE_ENV || "development";
const autoprefixer = require("autoprefixer");
const webpack = require("webpack");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
/*********************************
* Entry
*********************************/
const entry = {
"main": ["./Source/main.site.ts", "./Source/main.site.scss"]
}
/*********************************
* Module
*********************************/
const _module = {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
use: [
"ts-loader"
]
},
{
test: /\.pug$/,
exclude: /node_modules/,
use: [
"raw-loader",
"pug-html-loader"
]
},
{
test: /\.component.scss$/,
exclude: ["node_modules", "0-bourbon", "1-neat", "2-base"],
use: [
"raw-loader",
"postcss-loader",
"sass-loader"
]
},
{
test: /\.site.scss$/,
exclude: ["node_modules", "0-bourbon", "1-neat", "2-base"],
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
"css-loader",
"postcss-loader",
"sass-loader"
]
})
}
]
}
/*********************************
* Optimization
*********************************/
const optimization = {
splitChunks: {
cacheGroups: {
commons: { test: /[\\/]node_modules[\\/]/, name: "common", chunks: "all" }
}
}
};
/*********************************
* Output
*********************************/
const output = {
filename: "[name].bundle.js",
path: __dirname + "/wwwroot/js/",
pathinfo: true
};
if (environment === "production") {
output.filename = "[name].bundle.min.js";
output.pathinfo = false;
} else if (environment === "development") {
output.publicPath = "/js/";
}
/*********************************
* Plugins
*********************************/
const plugins = [
new ExtractTextPlugin({
filename: environment === "production" ? "../css/[name].bundle.min.css" : "../css/[name].bundle.css",
disable: false,
allChunks: false
}),
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
autoprefixer()
]
}
}),
new webpack.DefinePlugin({
"process.env": {
"NODE_ENV": JSON.stringify(process.env.NODE_ENV)
}
})
];
/*********************************
* Resolve
*********************************/
const resolve = {
extensions: [".ts", ".js"]
}
/*********************************
* Exports
*********************************/
module.exports = {
entry: entry,
output: output,
resolve: resolve,
mode: environment,
module: _module,
optimization: optimization,
plugins: plugins
}
webpack.config.js
code needed to get up and running.
Specify the use of autoprefixer and the typescript compiler
-
WebUi
- postcss.config.js
- tsconfig.json
- tslint.json
In order to use autoprefixer we must specify a postcss.config.js
file. The minimally required code is shown in
(d). We also need to provide a configuration file for the typescript compiler which is shown in
(e). I also like to make changes to the typescript linter which is done by specifying a
tslint.json
file which is shown in (f).
postcss.config.js (1)
module.exports = {
plugins: {
"autoprefixer": {}
}
}
tsconfig.json (1)
{
"compileOnSave": false,
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true,
"outDir": "wwwroot/js",
"lib": [ "es2017", "dom" ]
},
"exclude": [
"node_modules",
"wwwroot"
]
}
tslint.json (1)
{
"extends": "tslint:latest",
"rules": {
"no-constructor-vars": true,
"no-string-literal": false
},
"rulesDirectory": [
]
}
Time to add our site wide entry point files
-
WebUi
-
Source
- main.site.scss
- main.site.ts
-
Source
As we showed when created the webpack config file we need to specify two files for our site wide typescript and scss needs. In both (g) and (h) we are just including code to test and make sure that our resulting bundles are working as intended.
main.site.scss (1)
body {
font-size: 96px;
}
main.site.ts (1)
document.body
.appendChild(document
.createElement("div")
.appendChild(document
.createTextNode("Hello World!")));
With all of that done its time to give our build command a try so in a console that is located at the root of our project we just need to
execute the npm run build
command. Depending on when you are reading this you may or may not have just
gotten the error shown in (i) when you ran the build command.

Luckily when this happened to me after upgrading to webpack 4 it did not take too long to search for the cause and find the solution. If you
look in your package.json
file and you see "extract-text-webpack-plugin": "^3.0.2"
or something close this is the cause.
-
WebUi
- package.json
The solution is to uninstall our current version, npm uninstall --save-dev extract-text-webpack-plugin
, and install at
least version 4.0 which at the time of the writing of this is in beta and can be installed using
npm install --save-dev extract-text-webpack-plugin@next
. The result should be a modification of our
package.json
file shown in (j).
package.json (2)
{
...
"devDependencies": {
...
"extract-text-webpack-plugin": "^4.0.0-beta.0",
...
}
}
package.json
showing the current version of the
extract text plugin.
So everything is good to go now right?
Next we give the build another try npm run build
and once again we may or may not be greeted with another error
as shown in (k). This one though is unusually helpful in telling us what we need to do in order to
fix it. I have installed typescript using the npm install -g typescript
command which has installed it globally and in
order to use it within this project without also have to install it locally we need to create a symbolic link using
npm link typescript
command.

We have reached the promise land
With that change made it is again time to try building our bundles using the npm run build
command. When it is finished
we should be greeted with something very similar to the output shown in (l).

But do our bundles work in the browser?
-
WebUi
- index.html
It is now time to test and make sure that our bundles are working properly in the browser. In order to do this we just need to create a test html page which we will do in the root of our project. The content of the page is shown in (m).
index.html (1)
<!DOCTYPE html>
<html>
<head>
<title>Webpack 4 - Quick Start</title>
<link href="wwwroot/css/main.bundle.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<script src="wwwroot/js/main.bundle.js"></script>
</body>
</html>
When we open our test page in a browser we should see the result shown in (n).

I won't go into it here but in case someone is writing a Asp.Net application a minimal _Layout.cshtml
file
using our bundles is shown in (o).
_Layout.cshtml (1)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Webpack 4</title>
<environment include="Development">
<link rel="stylesheet" href="~/css/main.bundle.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="~/css/main.bundle.min.css" asp-append-version="true" />
</environment>
@RenderSection("Styles", required: false)
</head>
<body>
@RenderBody()
<environment include="Development">
<script src="~/js/common.bundle.js"></script>
<script src="~/js/main.bundle.js"></script>
</environment>
<environment exclude="Development">
<script src="~/js/common.bundle.min.js" asp-append-version="true"></script>
<script src="~/js/main.bundle.min.js" asp-append-version="true"></script>
</environment>
@RenderSection("Scripts", required: false)
</body>
</html>
_Layout.cshtml
file using the bundles created with webpack.