Press "Enter" to skip to content

How to start using Webpack + TypeScript

I will show in this article how to use Webpack for bundling our front-end JavaScript projects. Webpack is a tool which allows us to code our JavaScript front-end application and divide our code on modules, separated files with view (html), logic (js) and styles (css), and gather all files into single html, javascript and css file with its transpilation (e.g. from TS/ES2015+ into ES5) and minification or uglyfication + creating  runtime server for development.

So generally, it provides everything what modern JavaScript project should have on board to make coding easier and faster. Let’s go and get our hands dirty ūüėČ

On our GITLAB HERE you can find full code of our Webpack project – also including tutorial from next article about Webpack + TypeScript, which you can read HERE.

Remember to install first the NodeJS from its official site.

  1. First we must initiate nodejs project in our directory by opening there a terminal (cmd) and writing command:
    npm init
  2. Next we must install webpack in our project:
    npm i --save-dev webpack webpack-cli
  3. Next open package.json file and add there:
    "scripts": {
      "start": "webpack --config webpack.config.js"
    }
    
  4. Next, of course, we must create webpack.config.js file in our project root directory and put there that code::
    module.exports = {
      entry: './src/app.js',
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
      }
    }
    

    First we must create app.js file with our JavaScript code. Let’s keep it stupid simple and let’s add there now only:
    alert('Hello coders!');

    This Webpack config means that it will take app.js file, move that to /dist folder and give the final name bundle.js.

    The problem here is that we need to manually create html files in dist and link them manually, e.g. at the end of body like:

    <!doctype html>
    <html>
     <head>
       <title>APP</title>
     </head>
     <body>
      <script src="bundle.js"></script>
     </body>
    </html>

     

  5. To solve that problem we must install Webpack HtmlWebpackPlugin  plugin and html pages with linked bundle script will be created automatically. Write down in terminal:
    npm i --save-dev html-webpack-pluginand next in webpack.config.js file we must add on the top of the content the following code:

    const HtmlWebpackPlugin = require('html-webpack-plugin');

    and add following config property “plugins”:

    module.exports = {
      entry: './src/app.js',
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Bricks - new APP'
        })
      ]
    }
    

    and run webpack by command in terminal:
    npm run start

    and in /dist directory you will see that webpack has created index.html file linked with bundle.js file.

  6. There is one big assumption in the background – that your JavaScript application to work need to have only empty HTML document with head and body inside it. It means that JavaScritp application will inject whole its HTML code inside this pure index.html file. Index.html created by plugin looks like below:
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>webpack App</title>
      </head>
      <body>
        <script src="index_bundle.js"></script>
      </body>
    </html>

    But if you want you can have more advanced HTML code inside generated page, you can inject template there. Check more on official documentation here.

  7. The last thing in this tutorial are webpack loaders. Loaders take the input files (like JavaScript) and works before bundling by adding some additional changes there. The best example it writing JavaScript code in EcmaScript2015+ syntax and its automatic transpilation into EcmaScript5 syntax, understandable by all browsers, also Internet Explorer 10. Let’s follow that example.First we must install wanted loaders in this example Babel:
    npm install -D babel-loader @babel/core @babel/preset-envNext we must add in webpack.config.js files rules, check it below:

    module.exports = {
      entry: './src/app.js',
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
      },
      plugins: [new HtmlWebpackPlugin({
            title: 'Bricks - new APP'
        })],
      module: {
        rules: [{
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }]
      }
    }

     

  8. We can extend app.js file with more sophisticated code within ExmaScript2015+ syntax:
    class Dog {
      constructor() {
        this.bark= "Bark bark!";
      }
     
      say () {
        alert(this.bark);
      }
    }
     
    const dogMimi = new Dog ();
     
    dogMimi .say();

    After that, an alert with a given text “Bark bark!” will be visible in alert box.

  9. To make our Webpack understanding the TypeScript we must install now the TypeScript transpilators. We installed already @babel/preset-env in step 7 abovefor ES2015+.
    Install typescript in your project:
    npm install --save-dev typescript
  10. Next we must add in a root directory the tsconfig.json file with the following content:
    {
      "compilerOptions": {
        "outDir": "./dist/",
        "noImplicitAny": true,
        "module": "es6",
        "target": "es5",
        "jsx": "react",
        "allowJs": true,
        "sourceMap": true
      },
      "include": [
        "src/**/*"
      ],
      "exclude": [
          "node_modules",
          "**/*.spec.ts"
      ]
    }
    

    Find more about TSCONFIG.JSON file here in official TS DOCS.

  11. And if we want to use in our project both JavaScript and TypeScript code here we have 2 ways to go:
    1. Keep @babel/preset-env for ES2015+ files and install babel loader
      npm install --save-dev @babel/preset-typescript
    2. Use Babel loader by extending webpack.config.js file as following:
      module: {
        rules: [{
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }, {
          test: /\.ts$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-typescript'],
              plugins: ["@babel/plugin-proposal-optional-chaining"]
            }
          }
        }]
      }
    1. Keep @babel/preset-env for ES2015+ files and install ts-loader npm install --save-dev ts-loader
      Use TS-loader by extending webpack.config.js file as following:

      module: {
        rules: [{
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }, {
          test: /\.ts$/,
          exclude: /node_modules/,
          use: 'ts-loader',
          exclude: /node_modules/
        }]
      }

    Both, Babel, and TS, loaders are doing the same work – transpile TypeScript .ts files into ES5 syntax. So since now, we can write our own TypeScript code.
    If you want to see whole code in our project just go to our GITLAB HERE.

    When installing third party libraries from npm, it is important to remember to install the typing definition for that library. These definitions can be found at TypeSearch.

  12. Last needed loader here is CSS + style loaders:
    npm install --save-dev css-loader style-loaderIt is recommended to use them together – css-loader can simply import CSS code into the JavaScript module. In contrast, style-loader can inject css-loader code into an HTML file using the link or style tags.Let’s extend the modules webpack definition by adding above loaders:

    {
      test: /\.css$/,
      use: ['style-loader', 'css-loader']
    },
  13. ¬†Next thing in our Webpack configuration is a SOURCE MAP. In final output of ES5 JavaScript files we have minified and uglified files, source map helps modern browsers to “recreate” original set of JavaScript files (modules) and do debugging there. So for debugging we do not use bundled JS files.So in webpack.config.js we have to add on very beginning:
    module.exports = {
      mode: 'development',
      devtool: 'inline-source-map',

    If you have some troubles, then check full code of webpack.config.js file on our gitlab here.

  14. To be able to import in TypeScript .ts files modules directly and not via import * as name ... statement we must add the configuration to tsconfig.json file :
    {
      "compilerOptions": {
        "esModuleInterop": true
      }
    }
    
  15. At the end we must add very important and useful functionality – add a DEVSERVER. This is a webpack plugin which track all pointed .ts, .js or .css files for changes (saves) and builds our application in the background in runtime and automatically refresh the browser page with our app.Let’s install that via command:
    npm install --save-dev webpack-dev-serverand in webpack.config.js file we must extend it by writing:

    devServer: {
      contentBase: './dist'
    },

    And of course, we must add a new npm commad in our main package.json file: "dev": "webpack-dev-server --open" and the whole “scripts” property will look like:

    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      "start": "webpack --config webpack.config.js",
      "dev": "webpack-dev-server --open"
    },
  16. Now when you write npm run dev command in terminal it will open the new browser window on e.g. 8000 port http://localhost:8080 , your JS/TS application will be transpilled into ES5 syntax, and new index.html fill will be created in background with linkage to bundled JS and CSS files and whole app will start working in your browser. When you make some changes in JS/TS/CSS files, DEV SERVER will detect it, and refresh your application in browser window just like that.
  17. You can find all files of that project in our repository on gitlab here and each most important file is listed below:webpack.config.js
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const path = require('path');
    
    module.exports = {
      mode: 'development',
      devtool: 'inline-source-map',
      devServer: {
        contentBase: './dist'
      },
      entry: './src/app.ss',
      //entry: './src/appTS.ts',
      output: {
        path: path.resolve(__dirname, './dist'),
        filename: 'bundle.js'
      },
      plugins: [
        new HtmlWebpackPlugin({
          title: 'Optional chaining - test'
        }),
        new CleanWebpackPlugin()
      ],
      module: {
        rules: [{
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        }, {
          test: /\.ts$/,
          //include: /\appBabel.ts$/, //remove or comment to work with optional chaining in TS
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-typescript'],
              plugins: ["@babel/plugin-proposal-optional-chaining"]
            }
          }
        },
            // TS-LOADER
            // {
          // test: /\.ts$/,
          // //include: /\appTsLoader.ts$/, //remove or comment to work with optional chaining in TS
          // exclude: /node_modules/,
          // use: 'ts-loader',
          // exclude: /node_modules/
        // }
            ]
      }
    }

    tsconfig.json

    {
      "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "noImplicitAny": true,
        "module": "es6",
        "target": "es5",
        "target": "esnext",
        "jsx": "react",
        "allowJs": true
      },
      "include": [
        "src/**/*"
      ],
      "exclude": [
          "node_modules",
          "**/*.spec.ts"
      ]
    }

    package.json

    {
      "name": "pure-webpack",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "webpack --config webpack.config.js",
        "dev": "webpack-dev-server --open"
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "@babel/core": "^7.8.3",
        "@babel/plugin-proposal-optional-chaining": "^7.8.3",
        "@babel/preset-env": "^7.8.3",
        "@babel/preset-typescript": "^7.8.3",
        "babel-loader": "^8.0.6",
        "clean-webpack-plugin": "^3.0.0",
        "html-webpack-plugin": "^3.2.0",
        "ts-loader": "^6.2.1",
        "typescript": "^3.7.4",
        "webpack": "^4.41.5",
        "webpack-cli": "^3.3.10",
        "webpack-dev-server": "^3.10.1"
      }
    }
    

    app.js – used before TypeScript was initialized in steps 9-11

    const house = {
      room: {
        desk: ['pencil', 'computer']
      }
    }
    
    const x = house.room.desk;
    const y = house.room?.desk;
    const z = house.toilet?.closet;
    
    document.querySelector('body').innerHTML += `
      <style>
        .text {
          width: 500px;
          margin: 0 auto;
          background: #ccc;
        }
      </style>
      <div class="text">
        <h2>BABEL PRESET-ENV</h2>
        Room Normal: ${x} <br>
        Room Chaining: ${y} <br>
        Toilet Chaining: ${z}
      </div>
    `;

    appTS.ts – used after TypeScript init in steps 9-11 above

    type Space = {
      desk?: string[],
      table?: string[],
      walls?: string[]
    };
    
    type Building = {
      room?: Space,
      toilet?: Space,
      Kitchen?: Space
    };
    
    const house: Building = {
      room: {
        desk: ['pencil', 'computer']
      }
    }
    
    const x = house.room.desk;
    const y = house.room?.desk;
    const z = house.toilet?.table;
    
    export const run = () => {
      document.querySelector('body').innerHTML += `
        <style>
          .text {
            width: 500px;
            margin: 0 auto;
            background: #ccc;
          }
        </style>
        <div class="text">
          <h2>BABEL TYPESCRIPT</h2>
          Room Normal: ${x} <br>
          Room Chaining: ${y} <br>
          Toilet Chaining: ${z}      
        </div>
      `;
    };
    
    run();

This is everything in this article. I hope that now you will be able to create your webpack bundling to your own custom JavaScritp project. But rememebr that this knowledge is useful not only in own custom made JS projects, but almost all modern JavaScript frameworks development environments like Vue CLI, NuxtJS or React-create-app are using webpack under the hood, so whenever you will need to customize how this environment works, than this knowledge will be priceless.