Few days ago, I wanted to build a static website with simple SCSS and ES6 which I can host somewhere. So I decided to setup a simple project which can convert my SCSS to CSS and ES6+ code to ES5. I decided to use Webpack 4. This article is based on this learning. Code base for this article can be found in my . Github Agenda Introduction to Webpack 4 Core terminology of Webpack Prerequisite Getting ready for Webpack Setting up config file from scratch Working with HTML Webpack dev server Working with ES6+ and Typescript Working with SCSS and CSS Loading static resources Working with different environments Introduction to Webpack 4 As per the official website At its core, webpack is a static module bundler for modern JavaScript applications. When webpack processes your application, it internally builds a dependency graph which maps every module your project needs and generates one or more bundles. Our modern JavaScript application can have different modules and these modules can be dependent on each other. Webpack take these modules with dependency and generate bundles out of it. Webpack 4 by default is zero config(We will see about it when we start with project). Being zero config does not mean you can not configure anything in Webpack. It is highly configurable to suit your need. Core terminology of Webpack 4 Before we start with actual installation, we need to know some basic terminology which is involved in Webpack. Entry For building dependency graph Webpack need to know where it should start. From there onward it starts looking for other modules on which your root module depends. Default entry is But you can set it to different module also: ./src/index.js module.exports = { entry: './path/to/my/entry/file.js'}; Multiple entry points is also possible: module.exports = { entry: { main: './src/index.js', vendor: './src/vendor.js' }}; 2. Output Output config tell webpack where to write final files. Usually this is or folder. This can be named whatever you want. dist/ builds/ const path = require('path');module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'main.bundle.js' }} In case of multiple entry point we need to we to slightly different config: const path = require('path'); module.exports = { entry: { main: './src/index.js', vendor: './src/vendor.js' }, output: { filename: '[name].bundle.js', path: __dirname + '/dist' }}; Above configuration for output will result in two files in folder named as and dist main.bundle.js vendor.bundle.js 3. Loaders Loaders allows us to transform our files for example: Transpile files to or to . There is verity of loaders available in Webpack ecosystem like etc. .ts .js .scss css file-loader, url-loader 4. Plugins Plugins play very important role in Webpack configuration. Loaders are limited to converting modules from one form to another whereas plugins are responsible for Build optimization and asset management. We will look into it in more detail later in the article. So this is just some basic terminology before we start with actual configuration. If you want to read more about core concepts you can refer to official page of . Webpack 3. Prerequisite Before we start we need to have node installed in our machine. To check whether it is installed in your machine. Run node -v If it does not show any version it means node is not installed.To install node go to and download and install. Node JS 4. Getting ready for Webpack Let’s create a folder called webpack-starter mkdir webpack-startercd webpack-starter Now initialize npm npm init After successful run you can see a file in your root folder. package.json webpack-starter Now we will download and from npm repository to run it. For this run webpack webpack-cli npm install webpack webpack-cli -D It will add these two dependencies in file. package.json Now we will create a directory and file inside it. src index.js mkdir src Let’s modify script in npm package.json "scripts": { "dev": "webpack" } Now run following command form your root folder npm run dev You will see a dist folder created with file in your root folder. main.js You will see a dist folder created with file in your root folder. main.js But there is a warning in console. WARNING in configurationThe 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/ This warning is coming because we need to set a mode. Lets do that and hit npm run dev "scripts": { "dev": "webpack --mode development" } Now you can see that there is no warning and chunk is generated. But wait !!! How is webpack doing it? I have not configured anything. Remember in the beginning I told you that Webpack 4 is zero configuration. Actually it is comes with default configuration which search for file inside folder and assumes that it is the entry point for building dependency graph. It also assumes that output folder is and emits converted files there. index.js src dist h 5. Setting up config file from scratc We will now look into how to write our own configurations so that we can have greater control on what webpack should do. Let’s create file in root directory. I have also created directory and . webpack.config.js src/app/ src/index.html Now open and put following code for entry and output. webpack.config.js const path = require('path'); module.exports = {entry: './src/index.js',output: {filename: 'main.js',path: path.resolve(__dirname, 'dist')}}; Delete the dist folder from root and hit command. You will find same result. npm run dev 6. Working with HTML You have noticed one thing that when we ran it is only putting file into dist folder. Wepack is not copying our file. For that we need to use a plugin So install npm run dev main.js index.html html-webpack-plugin . html-webpack-plugin npm i html-webpack-plugin -D Now plugins section in you file inside module.exports (Do not delete entry and output :P): webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = {....plugins: [new HtmlWebpackPlugin({title: 'Webpack 4 Starter',template: './src/index.html',inject: true,minify: {removeComments: true,collapseWhitespace: false}})]} Change index.html <!DOCTYPE html><html><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial- scale=1"></head><body><h3>Welcome to Webpack 4 starter </h3></html> <title> <%= htmlWebpackPlugin.options.title %> </title> We are using for adding title, which we have declared in our config file. Also put a log in your file htmlWebpackPlugin.options.title index.js console.log('This is index JS') Now delete your folder and hit command. dist npm run dev You will see file inside and title is replaced by our title of config file. You will also notice that webpack has already added a script tag containing generated script . You do not need to add script file manually. Isn’t that cool!!! index.html dist main.js If you open file in browser and open inspector, you will see following result. dist/index.html 6. Webpack dev server During the development you need a mechanism to serve your files from a local server and auto reload changes to avoid refreshing browser again and again after you make any changes. To address this Webpack has . So let’s setup our dev server. [webpack-dev-server](https://www.npmjs.com/package/webpack-dev-server) npm i webpack-dev-server -D Go to your and add following to your scripts : package.json "scripts": { "start": "webpack-dev-server --mode development", "dev": "webpack --mode development" } After this go to root directory in terminal and run .You can see this in your terminal npm start And if you go to You can see your welcome page. Now try changing something in and hit save, you can see your changes in browser without hitting refresh. :) For more information refer to http://localhost:8080/. /src/index.html webpack-dev-server . Resolving common extensions Sometimes we do not like to write extension while we import the modules. We can actually tell webpack to resolve some of the extensions if we do not write them. For that add following code in your webpack.config.js module.exports {.....resolve: { extensions: ['.js', '.ts'] }, .....} 7. Working with ES6+ and Typescript So with our dev server running now we will move to transpile our ES6+ code and Typescript to ES5 because majority of browsers can only understand ES5. a. Transpile ES6+ to ES5 For this we need a loader. Yes! finally !!! some loader. So we need babel-loader @babel/core @babel/preset-env npm i babel-loader @babel/core @babel/preset-env -D So update your config file as follows ...module.exports = {...... module: {rules: [{test: [/.js$/],exclude: /(node_modules)/,use: {loader: 'babel-loader',options: {presets: [' ']}}}]},...... @babel/preset-env } So what does this basically mean? We can write different rules to load modules inside . is basically a regex. here we are saying that match all the files which ends with and exclude . will actually transpile these modules/files to ES 5 using module test test: [/.js$/] .js node_modules babel-loader preset-env. So it is time to test that this rule is actually doing something. For this we will add in folder and add a code. header.js src/app ES 6 export class Header { constructor() { console.log(\`This is header constructor\`); } getFirstHeading() {return `Webpack Starter page`;} } Now we need to import this to our file. index.js import { Header } from './app/header'; let header = new Header(); let firstHeading = header.getFirstHeading(); console.log(firstHeading); We are simply importing the and making and object and calling its method. header getFistHeading Run and open in browser. You can see following result: npm start [http://localhost:8080](http://localhost:8080) b. Compile Typescript to ES 5 So everything is working as expected. Let’s move to compiling our to . For this we just need to install dependency and change the existing rule. First thing first, install the dependency typescript ES5 npm i @babel/preset-typescript typescript -D We change the rule we have just written for ES 6 conversion: module: { rules: [ { test: [/.js$|.ts$/], exclude: /(node_modules)/, use: { loader: 'babel-loader', options: { presets: \[ '@babel/preset-env', '@babel/typescript' \] } }}]} We also need to add a file in directory. Read more about . tsconfig.json root tsconfig.json here { "compilerOptions": { "target": "esnext", "moduleResolution": "node", "allowJs": true, "noEmit": true, "strict": true, "isolatedModules": true, "esModuleInterop": true, "baseUrl": ".", }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ] } And that’s it. It is ready to test. We will test it same way we tested Header. We will create a file in and add following content. footer.ts src/app/ export class Footer {footertext: string; constructor() {console.log(`This is Footer constructor`);this.footertext = `Demo for webpack 4 set up`;} getFooterText(): string {return this.footertext}} We need to import it in as we did with module. index.js header.js import { Footer } from './app/footer'; ...... let footer = new Footer(); let footerText = footer.getFooterText(); console.log(footerText); ....... Let’s run it. and navigate to npm start http://localhost:8080. 8. Working with CSS and SCSS For loading the CSS styles we need two loaders and . css-loader style-loader take all the styles referenced in our application and convert it into string. take this string as input and put them inside tag in . To install dependencies : css-loader style-loader style index.html npm i css-loader style-loader -D We need to write a rule that match all extensions and use these loaders to load them in file. .css index.html module: { ...... rules: [ .....{test: [/.css$/],use:['style-loader','css-loader']} ] ......} Let’s create a file inside folder and and put some styles in it. style.css src Now we need to import into . Put following code in style.css index.js index.js import '../src/style.css'; Now stop the dev server and run it again . Navigate to .You can see your to be red and if you inspect the DOM you will find a style tag added there with your styles in it. :) npm start http://localhost:8080 h3 Convert SCSS to CSS For this we need two dependencies to be added that is and . simply conver your files to using bindings. If you want you can read about node-sass more in depth . node-sass sass-loader sass-loader .scss .css node-sass here npm i node-sass sass-loader -D We need to change our rule to match a file. .scss module: { ...... rules: [ .....{test: [/.css$|.scss$/],use:['style-loader','css-loader','sass-loader']} ] ......} Always remember order in which loaders are being loaded matters. So do not change the order. Let’s create folder structure for our scss files. So create directory and file in it with code in screenshot. src/styles/scss main.scss Now import in into index.js import './styles/scss/main.scss'; Now its time to run the code. and go to You can see the results. npm start [http://localhost:8080](http://localhost:8080.) . Extract all styles into a single file Sometimes we do not want to add the styles in inline style tag rather we want it into a separate style file. For this we have a plugin called . mini-css-extract-plugin npm i mini-css-extract-plugin -D We need to update our file as usual. webpack.config.js ....const MiniCssExtractPlugin = require('mini-css-extract-plugin'); .... module.exports = { ....... module: { rules: [ ...... { test: [/.css$|.scss$/], use: [ MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader' ] } ] }, plugins: [ ......... new MiniCssExtractPlugin({filename: 'style.css'}) ] } Now if you run and navigate to . You can see following results: npm start http://localhost:8080 As you can see in inspector window that a link to external css file has been added instead of inline style tags. Adding post-css is help us in transforming css with js plugin. is popular post css plugin which help us to add browser prefixes. To install dependencies of both : Post css Auto prefixer npm i postcss-loader autoprefixer -D Create a inside the root directory and following code. postcss.config.js module.exports = {plugins: [require('autoprefixer')]} Now go to and update the styles. src/styles/scss/main.scss h3 h3 { background-color: $h3-bg-color; user-select: none; } prevents text selection and this property needs browser prefix. Now if you go to the terminal and run and go to generated you will see is auto prefixed. user-select npm run dev dist/main.css user-select 10. Loading static resources For loading static content we need a loader called . file-loader npm i file-loader -D Now we need to modify our rule in config file. rules: {.... { test: /\.(png|jpg|gif|svg)$/, use: [ { loader: 'file-loader', options: { name: '[name].[ext]', outputPath: 'assets/images' } } ] } ..... } For testing create a directory and put some image. I am putting a of webpack. You can download it from . src/assets/images .gif here Now we need to import it inside our . index.js import webpackgif from './assets/images/webpack.gif'; Let’s create a tag in . We will set attribute from file. Add following code into body tag. img index.html src javascript index.html <img id="webpack-gif" alt="webpack-gif"> Add following code to . index.js .....import webpackgif from './assets/images/webpack.gif';....... // setting the source of img document.getElementById('webpack-gif').setAttribute('src', webpackgif); Now if you run and navigate to can see following results: npm start [http://localhost:8080](http://localhost:8080.You) .You You might be wondering why I did not add of directly into the html? Why did I add it through JavaSript? Actually only load those assets which are referenced into our modules (in JavaScript or Typescript files). src img file-loader If I directly add it to html and delete image import. We will get following result: So to overcome this issue we need plugin called This will copy our static assets to the folder we specify. copy-webpack-plugin . npm i copy-webpack-plugin -D Update the config file: .... const CopyWebpackPlugin = require('copy-webpack-plugin'); ...... module.exports = {.....plugins:[{new CopyWebpackPlugin([{ from:'./src/assets/images', to:'assets/images' }])}..... } Now if you run and navigate to . You can see following results: npm start http://localhost:8080 11. Working with different environments When we prepare our assets and files for final deployment we need to keep our bundle size as less as possible. For this reason we need to minify our css and Javascript files. We can do that with one single config but it is good practice to divide config file into different files so that there will be clear separation of responsibilities. Due to this is reason its is common practice to have following files : a. webpack.common.config.js b. webpack.dev.config.js c. webpack.prod.config.js As the name suggest, common file will have all the common configs. We will use library to merge common config to dev and prod configuarations. So let’s install webpack-merge webpack-merge npm i webpack-merge -D Now we will create a directory in our root folder and create above mentioned files inside it. config We will copy all content of file to file. webpack.config.js webpack.common.config.js Update the output of file as follows: (dist is changed to ../dist) webpack.common.config.js output: { path: path.resolve(__dirname, '../dist'), filename: '[name].js' } Now we will put following code to webpack.dev.config.js const merge = require('webpack-merge') const webpackBaseConfig = require('./webpack.common.config.js') module.exports = merge(webpackBaseConfig, {}) Delete content of and put following code webpack.config.js const environment = (process.env.NODE_ENV || 'development').trim(); if (environment === 'development') { module.exports = require('./config/webpack.dev.config.js'); } else { module.exports = require('./config/webpack.prod.config.js'); } This code is basically saying that if the NODE_ENV is development then use else By default it will take . webpack.dev.config.js webpack.prod.config.js development Set environment variable To set the environment variable for and we need a npm package called . This npm package insures that environment variable is set properly in every platform. You can read more on this . dev prod cross-env here npm i cross-env -D Now change the npm scripts : "scripts": { "build:dev": "cross-env NODE_ENV=development webpack --mode development", "build:prod": "cross-env NODE_ENV=production webpack --mode production", "start": "webpack-dev-server --mode development" } Clearing Dist folder via plugin You might have noticed that I ask you to manually delete the folder whenever I run build commands. We can overcome this by using another plugin called dist clean-webpack-plugin . npm i clean-webpack-plugin -D We need to modify our common config: const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { ....plugins: [....new CleanWebpackPlugin() .....] } We will proceed to prod configuration in a bit. Let’s test dev configuration first. Now run .You can see folder is generated with all the assets required. npm run build:dev dist Prepare resources for Production For minifying the Javascript we need a plugin called uglifyjs-webpack-plugin npm i uglifyjs-webpack-plugin -D For css optimization we need another plugin called optimize-css-assets-webpack-plugin npm i optimize-css-assets-webpack-plugin -D After installing this update the : webpack.prod.config.js const merge = require('webpack-merge'); const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const webpackBaseConfig = require('./webpack.common.config.js'); module.exports = merge(webpackBaseConfig, { optimization: { minimizer: [ new UglifyJsPlugin(), new OptimizeCSSAssetsPlugin() ] } }); Now everything is done you can generate the build for prod via npm run build:prod You can see the difference between size of files generated in prod and dev build Size difference is very less as we do not have larger code base. We can do one final thing that we can implement hashing while generating the build. It is very simple we just need to add [chunkhash] in output files and mini-css-extract plugin. Update your common config as ....module.exports = {...output: { path: path.resolve(\_\_dirname, '../dist'), ** filename: '[name].[chunkhash].js'** } ..... plugins: [....new MiniCssExtractPlugin({ filename: 'style.[chunkhash].css' }), ...] } Now if you generate the build via you will see a hash appended at the end of files. npm run build:prod Kindly suggest any improvements and do share, subscribe and clap. Thanks :)