Advertisement

#1 Quick Start

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
(a) The command that will install all of the packages that we need for Webpack. The command will also modify our 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"
  }
}
(b) This shows what our 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
}
(c) The entire 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": {}
    }
}
(d) Minimal file needed to be able to use 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"
  ]
}
(e) Configuration file that is used by the typescript compiler.

tslint.json (1)

{
  "extends": "tslint:latest",
  "rules": {
    "no-constructor-vars": true,
    "no-string-literal": false
  },
  "rulesDirectory": [
  ]
}
(f) Configuration file used to modify the rules used when linting a typescript file.

Time to add our site wide entry point files

  • WebUi
    • Source
      • main.site.scss
      • main.site.ts

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;
}
(g) Test code for our site wide scss.

main.site.ts (1)

document.body
    .appendChild(document
        .createElement("div")
        .appendChild(document
            .createTextNode("Hello World!")));
(h) Test code for our site wide typescript.

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.

Error trying to use the extract text plugin with a version less than 4.0.
(i) Error trying to use the extract text plugin with a version less than 4.0.
Advertisement

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",
    ...
  }
}
(j) Change to our 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.

Error received by having a global installation of typescript, no local version within the
            project, and not creating a symbolic link and trying to compile a typescript file.
(k) Error received by having a global installation of typescript, no local version within the project, and not creating a symbolic link and trying to compile a typescript file.

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).

Output showing that our bundles have been created successfully.
(l) Output showing that our bundles have been created successfully.

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>
(m) Html page used to test that are bundles were created correctly.

When we open our test page in a browser we should see the result shown in (n).

All is right in the world. A project has started with a tip of the hat
            to 'Hello World'.
(n) All is right in the world. A project has started with a tip of the hat to 'Hello World'.

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>
(o) Minimal _Layout.cshtml file using the bundles created with webpack.
Exciton Interactive LLC
Advertisement