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.
- First we must initiate nodejs project in our directory by opening there a terminal (cmd) and writing command:
npm init
- Next we must install webpack in our project:
npm i --save-dev webpack webpack-cli
- Next open
package.json
file and add there:"scripts": { "start": "webpack --config webpack.config.js" }
- 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>
- 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-plugin
and next inwebpack.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.
- 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.
- 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-env
Next 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'] } } }] } }
- 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.
- 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
- 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.
- And if we want to use in our project both JavaScript and TypeScript code here we have 2 ways to go:
- Keep
@babel/preset-env
for ES2015+ files and install babel loader
npm install --save-dev @babel/preset-typescript
- 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"] } } }] }
- Keep
@babel/preset-env
for ES2015+ files and install ts-loadernpm 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.
- Keep
- Last needed loader here is CSS + style loaders:
npm install --save-dev css-loader style-loader
It 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'] },
- 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.
- To be able to import in TypeScript
.ts
files modules directly and not viaimport * as name ...
statement we must add the configuration totsconfig.json
file :{ "compilerOptions": { "esModuleInterop": true } }
- 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-server
and 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 mainpackage.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" },
- Now when you write
npm run dev
command in terminal it will open the new browser window on e.g. 8000 porthttp://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. - 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.