Tag Archives: webpack

Creating loader component and set up one CSS per brand.

branch-name:  
Click To Copy

 

Previous solution was great but not perfect. We still add a CSS of two brands in one CSS file and loading all CSS for all brands per each component.

Could we do it better and load only the necessary CSS for each component for the particular branch ? Yers we could. The idea about bundle splitting is that one CSS file will be created per each component.

So in order to have one CSS file per brand we have to

Create a wrapper component per each brand.

Let’s start with

The Home component.

We are going to move the component’s code from index.js to a new file called renderer.js and repurpose index.js to be a wrapper component, which will load brand specific sub component, that will pass the style object back to the renderer component in renderer.js. If it sounds a bit confusing don’t worry. Follow that tutorial and it will get clear.

./src/components/Home/renderer.js

import React from 'react';

const Renderer = ({styles, title}) => {
  return (
    <div>
      <div className={styles.wrapper}>{title}</div>
    </div>
  );
}

export default Renderer;

The code in renderer file is pretty similar to that we had in index.js with one exception: we are going to pass the css as a property to this component.
And just to demonstrate how we could render different layout per each brand we are going to pass the title property as well.

Now the index.js will become our Home component wrapper, which will dynamically load either ./brands/one or ./brands/two sub component, which on the other hand will load our ./renderer.js component, passing the appropriate CSS for the selected brand.

./src/components/Home/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';

const one = Loadable({
  loader: () => import ('./brands/one'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/two'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {
  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

what we just did:
– we crated a wrapper component, that will conditionally load the helper component for the current brand (lines 5 and 10)
-we render the appropriate sub component, based on the brand name.

Let’s create the helper sub components that will load the brand specific CSS and pass it to the renderer component and render it.

These components will look quite similar:

./src/components/Home/brands/one/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

export default () => {
  return (
    <Renderer styles={styles} title="This is my home section rendered for One!" />
  )
}

./src/components/Home/brands/two/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

export default () => {
  return (
    <Renderer styles={styles} title="This is my home section rendered for Two!" />
  )
}

what we just did:
– we imported brand specific CSS in each of the components (line 2)
– imported the renderer component (line 3)
– rendered the renderer component, passing the CSS and the title property (line 7)

Open the ./dist folder and look at 1.css and 2.css contents:

./dist/1.css

.one-wrapper{background-image:url(/dist/images/b5c0108b6972494511e73ad626d1852f-home.png);height:500px}.one-wrapper h2{color:#000}

./dist/2.css

.two-wrapper{background-image:url(/dist/images/a005b97826d5568577273d214dd5f89a-home.png);height:800px}.two-wrapper h2{color:#00f;font-size:50px}

Webpack created two files with the corresponding CSS: one-wrapper and two-wrapper containing only the CSS needed for each brand.

Open the browser and give it a try. The result should be what we saw in the previous chapter, but this time only the brand specific CSS is loaded.

Nice! Now we have Webpack created these two CSS files, but the class names are one-wrapper and two-wrapper which comes from the lead folder name, which now instead of been ./Home is ./Home/one and /Home/two  What will happen if we want to make another component brand specific?

The Greetings component

Let’s do the same changes:

./src/components/Greetings/renderer.js

import React, { Component } from 'react';
import { connect } from 'react-redux';


const CHANGE_USERNAME = 'CHANGE_USERNAME';
const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE';

class Greetings extends Component {

  constructor(props) {
    super(props); 
  }
  

  doneEditUsername() {
    let newName = document.querySelector('#inputField').value;
    this.props.changeUserName(newName);
    this.props.toggleLogInPopup();
  }

  usernameChanged(el) {
    let newName = el.target.value;    
    this.props.changeUserName(newName);
  }

  onToggleEditMode() {
    this.props.toggleLogInPopup();
  }

  render() {
    let element = <h2 onClick={() =>{   this.onToggleEditMode()  }}>Hello:  {this.props.userName}</h2>;
    if(this.props.editMode)
      element = <h2>Type new name:<input type="text" id='inputField' value={this.props.userName} onChange={(el) => { this.usernameChanged(el);}} /><button onClick={() =>{ this.doneEditUsername() }}>done</button></h2>
    return (
      <div>
        <div className={this.props.styles.wrapper}>
          {element}
        </div>
      </div>);
  }
}

const mapStateToProps = ( storeState ) => {
  return {  
    userName: storeState.user.userName,
    editMode: storeState.user.editMode
  }
}

const mapDispatchToProps = dispatch => {
  return {
    toggleLogInPopup: () => {
      dispatch({type: TOGGLE_EDIT_MODE});
    },
    changeUserName: (userName) => {
      dispatch({type: CHANGE_USERNAME, data: userName});
    }
  }
};


export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Greetings);

what we just did:
– we just moved the code from ./index.js to ./renderer.js
– we removed the CSS import since we are passing CSS as a property
– we changed the div class name to come from the passed property (line 36)

The rest is the same like in Home component.

The index component will actually look exactly the same:

./src/components/Greetings/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';



const one = Loadable({
  loader: () => import ('./brands/one'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/two'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {

  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

Let’s load different ‘home’ pictures for each brand.
Move the home picture in ./images/home.png to ./images/one/home.png and add another image for ./images/two/home.png (you could either download some png or use the one in this branch)

./src/components/Greetings/brands/one/styles.scss

.wrapper {
  background-image:url('../../../../images/one/home.png');
  height: 500px;
  h2 {
    color: black;
  }
}

./src/components/Greetings/brands/two/styles.scss

.wrapper {
  background-image:url('../../../../images/two/home.png');
  height: 800px;
  h2 {
    color: blue;
    font-size: 50px;
  }
}

Here we have to adjust the relative path to the images since this component goes two levels deeper and we moved the images into a brands folders (line 2)

And the helper sub components are the same like in Home component.

./src/components/Greetings/brands/one/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

const One = () => {
  return (
    <Renderer styles={styles} />
  )
}

export default One;

./src/components/Greetings/brands/two/index.js

import React from 'react';
import styles from './styles.scss';
import Renderer from '../../renderer.js'

const One = () => {
  return (
    <Renderer styles={styles} />
  )
}

export default One;

Start the server and go to http://one.localhost:3006/home and you will notice that the Home component height increased. Why this happened?

Let’s open http://one.localhost:3006/dist/1.css and look at the class names:

.one-wrapper{background-image:url(/dist/images/b5c0108b6972494511e73ad626d1852f-home.png);height:500px}.one-wrapper h2{color:#000}

Somehow the one-wrapper has background-image:url(/dist/images/b5c0108b6972494511e73ad626d1852f-home.png) and height:500px that belongs to the Greetings component.

Why this is happening? Because of the way how we set up class name structure in Css-Loader. If you look at webpack.base.config.js  you will see that the localIdentName which is the CSS className is constructed by adding the folder name, and the actual local identifier ‘[folder]-[local]’

 ./src/webpack.base.config.js

...
      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
...

But the folder name now is ‘one’ or ‘two’ for both components since it takes only the leaf folder name. Let’s fix this by

Make brand component names unique.

Go to src/Home/brands/one and rename it to src/Home/brands/OneHome and src/Home/brands/two to be src/Home/brands/TwoHome
and do the same for the greetings component: src/Greetings/brands/one => src/Greetings/brands/OneGreetings and src/Greetings/brands/Two => src/Greetings/brands/TwoGreetings

Next let’s make the appropriate changes in both: Home and Greeting component:

./src/Home/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';

const one = Loadable({
  loader: () => import ('./brands/OneHome'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/TwoHome'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {

  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

and

./src/Greetings/index.js

import React from 'react';
import Loadable from 'react-loadable';
import Loading from '../Loading';



const one = Loadable({
  loader: () => import ('./brands/OneGreetings'),
  loading: Loading
});

const two = Loadable({
  loader: () => import ('./brands/TwoGreetings'),
  loading: Loading
});

const components = {
  one,
  two
}

const Home = ( {subDomain} ) => {

  const Component = components[subDomain];
  return (
      <Component />
  )
}
export default Home;

Run the project and check /dist/3.css 

.OneHome-wrapper--t4U5b{background:#8d8dac;color:#fff;text-align:center;font-family:MyFont}

it contains only CSS for the Home component.

Adding a hash in CSS class names

As an extra step we could also add a hash for each class name. This will make class names unique per component, so if accidentally happened to have two components with the same names their CSS won’t colide.

This could be achieved by simply adding a hash directive in localIdentName in CSS-loader config [folder]-[local]–[hash:base64:5]

const getEnvironmentConstants = require('./getEnvironmentConstants');
const webpack =require('webpack');

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist/',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]--[hash:base64:5]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } ),  
  ]
};

Run the project again and the home component should look like before. Open http://one.localhost:3006/dist/1.css and you will see how the has was appended to the class names.

branch-name:  
Click To Copy

 

Adding environment variables file.

branch-name:  
Click To Copy

 

Although Wepack already comes with two modes: development and production, It comes handy to have different environment variables for development and production and to store them in different files.

Let’s create .env file which will be our development environment variable file.

Creating the .env file

./.env

APP_NAME=Webpack React Tutorial
GRAPHQL_URL=http://localhost:4001/graphql

As it looks like .env is not a standart JavaScriupt object so we can’t use the variables out of the box. We will need two modules Dotenv and Dotenv-expand.

Adding and configuring Dotenv to load variables into process.env.

Dotenv is module that loads environment variables from a .env file into process.env.

yarn add dotenv dotenv-expand

Let’s give it a try and print out GRAPHQL_URL in the backend. Let’s do this using server-api.js config. All that we need to do is to ‘tell’ Dotenv module to load variables into process.env

./src/server-api.js

import WebpackDevServer from 'webpack-dev-server';
import webpack from 'webpack';
import config from './webpack.api.config.js';
require('dotenv').config();

console.log(">>>" + process.env.GRAPHQL_URL);

const compiler = webpack(config);
const server = new WebpackDevServer(compiler, {
  hot: true,
  publicPath: config.output.publicPath,
  historyApiFallback: true
});
server.listen(8080, 'localhost', function() {});

Run the project and in the backend console you will see GraphQL url printed out.

Now, let’s create getEnvironmentConstants helper method that will use Dotenv to load variables, and in addition we will add a filter that will load only these variables to the front end that we specify in frontendConstants . This way important variables that we need in the backend like passwords to the database won’t be exposed in the source code.

./getEnvironmentConstants.js

const fs = require('fs');

// Load environment variables from these files
const dotenvFiles = [
  '.env'
];


// expose environment variables to the frontend
const frontendConstants = [
  'APP_NAME',
  'GRAPHQL_URL'
];

function getEnvironmentConstants() {
  
  dotenvFiles.forEach(dotenvFile => {
    if (fs.existsSync(dotenvFile)) {
      require('dotenv-expand')(
        require('dotenv').config({
          path: dotenvFile,
        })
      );
    }
  });
  
  const arrayToObject = (array) =>
  array.reduce((obj, item, key) => {
    obj[item] = JSON.stringify(process.env[item]);
    return obj
  }, {})

  return arrayToObject(frontendConstants);      
}

module.exports = getEnvironmentConstants;

Once we have the object in place we could use the DefinePlugin to pass them to the frontend.

Adding the DefinePlugin.

if we go back in this tutorial we will remember that we showed how to configure Webpack in three different ways: using CLI, the webpack API and the server middleware.
Now the best place to add DefinePlugin will be in webpack.base.config so all three Webpack set-ups will take advantage of it. Let’s import getEnvironmentConstants and pass it as a parameter to DefinePlugin.

./webpack.base.config.js

import getEnvironmentConstants from './getEnvironmentConstants';
import webpack from 'webpack';

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',    
    './src/index.js',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } )
  ]
};

 what we just did:
– (line 1 and 2) we imported getEnvironmentConstants and Webpack since we will need it to instantiate the plug in.
– (line 72-74) we added the plug in.

We have to do one more change in order to have the plug-in working for all Webpack configs:

./webpack.api.config.js

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.entry = [
  '@babel/polyfill',    
  './src/index.js',
  'webpack/hot/dev-server',
  'webpack-dev-server/client?http://localhost:8080/',       
];

config.plugins = [... [new webpack.HotModuleReplacementPlugin()], ... config.plugins ];

module.exports = config;

and

./webpack.middleware.config

const webpack = require('webpack');
let config = require('./webpack.base.config.js');

config.entry = [
  '@babel/polyfill',    
  './src/index.js',
  'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',        
];

config.plugins = [... [new webpack.HotModuleReplacementPlugin()], ... config.plugins ];

module.exports = config;

 what we just did:
Since we added config.plugins array in webpack.base.config we don’t want to override it here and lose the changes. That’s why we are merging the array using spread operator.
If you are unfamiliar with the spread operator you might read the link above. Basically what it does is to ‘spread’ two or more arrays into the current array.

var a = [1,2,3];

var b = [...a, ...[4,5,6]];

console.log(b);

result: (6) [1, 2, 3, 4, 5, 6]

Accessing env variables in the front-end.

And let’s load these variables. We could replace the hardcoded GraphQL url with the one from the .env file.

And using the variables on the back end is straight forward: we just include .env file and use the variables, but passing them to the front end requires a little bit more effort. We have to use the DefinePlugin which will allow us to create global constants which can be configured at compile time.

./src/components/App/index.js

import React, { Component } from 'react';
import PageLayout from '../../containers/PageLayout';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore} from 'redux';
import reducers from '../../reducers';

import styles from './styles.scss';

const store = createStore(reducers, {});
export default class App extends Component {
  render() {
    const GRAPHQL_URL = process.env.GRAPHQL_URL;
    const client = new ApolloClient({
      link: new HttpLink({ uri:  GRAPHQL_URL }),
      cache: new InMemoryCache()
    });  
    return (
      <div className={styles.appWrapper}>
        <Provider store={store}>
          <ApolloProvider client={client}>
            <Router>
              <Switch>
              <Route exact path="*" component={PageLayout} />  
              </Switch>
            </Router>
          </ApolloProvider>
        </Provider>
      </div>        
    );
  }
}

 

And we could also print the APP name in the header section process.env.APP_NAME

./src/components/Header/index.js

import React from 'react';
import { Link } from 'react-router-dom';
const styles = require('./styles.scss');
const Header = ( {title} ) => (
  <div>
    <div className={styles.wrapper}>      
      <h2>{ title } { process.env.APP_NAME } </h2>
      <ul>
        <li><Link to='/home'>HOME</Link></li>
        <li><Link to='/greetings'>GREETINGS</Link></li>       
        <li><Link to='/dogs-catalog'>DOGS CATALOG</Link></li>
        <li><Link to='/about'>ABOUT</Link></li>
      </ul>
    </div>
  </div>
);
export default Header;

Now, start the server using yarn start-api and if everything works fine you will see the “Webpack React Tutorial” in the header.

 

branch-name:  
Click To Copy

 

 

Using GraphQLSchema to construct the schema programmatically.

For most of the cases defining a fixed schema when the application starts, by adding Query and Mutation types solely using schema language is good enough. But sometimes we might need to define a dynamic schema and we can achieve this by creating a new JS objects.

Construct dynamic schema for ‘User Greeting’ example.

Let’s get again to the ‘Greetings user’ example, because of it’s simplicity and define a Query with field named greetingUser which will accept userName and bornMonth parameters, first of type string and the second of type int and return userType.

And the userType will return greetingOne  which will simply say “Hello [userName]” and greetingTwo  that will let the user know how many months left till their next birthday. Both of type string.

./src/server.js

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the User type
var userType = new graphql.GraphQLObjectType({
  name: 'UserType',
  fields: {
    greetingOne: { type: graphql.GraphQLString },
    greetingTwo: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    greetingUser: {
      type: userType,
      args: {
        userName: { type: graphql.GraphQLString },
        bornMonth: { type: graphql.GraphQLInt }
      },
      resolve: function (_, {userName, bornMonth}) {
        var date = new Date();
        var daysLeft = bornMonth - (date.getMonth() + 1);
        daysLeft = daysLeft < 0 ? daysLeft + 12 : daysLeft;
        return {
          greetingOne: `Hello ${userName}`,
          greetingTwo: `Your birthday is comming in ${daysLeft} month(s)`
        };
      }
    }
  }
});

var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

what we just did:
– we defined the user type which is pretty self explanatory (Lines 8-14)
– then we created the query type, that has these parameters:
type which is the return type, in this case userType
args is the input parameter types.
resolve is the resolver function.

Transform Dogs catalog to use dynamic schema.

Creating the dog type

var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

Creating the query type

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    }
  }
});

Creating a mutation type

...
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    } 
...

Adding the query schema

...
var schema = new graphql.GraphQLSchema({query: queryType});

...
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
...

Putting it all together

var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var dogs = require('./src/models/mock_data/dogs.js');
const graphql = require('graphql');

// Define the dogs type
var dogType = new graphql.GraphQLObjectType({
  name: 'dogType',
  fields: {
    id: { type: graphql.GraphQLString },
    breed: { type: graphql.GraphQLString },
    displayImage: { type: graphql.GraphQLString },
  }
});

// Define the Query type
var queryType = new graphql.GraphQLObjectType({
  name: 'Query',
  fields: {
    getDogByBreed: {
      type: dogType,
      args: {
        breed: { type: graphql.GraphQLString }
      },
      resolve: function (_, {breed}) {

        var result = dogs.find(function(dog){
          return breed == dog.breed;
        });
        return result;
      }
    },
    addDogBreed: {
      type: graphql.GraphQLString,
      args: {
        id: { type: graphql.GraphQLString },
        breed: { type: graphql.GraphQLString },
        displayImage: { type: graphql.GraphQLString }
      },
      resolve: function (_, {id, breed, displayImage}) {
        dogs.push({
          id: id,
          breed: breed,
          displayImage: displayImage
        });
        return "OK!";
      }
    }    
  }
});


var schema = new graphql.GraphQLSchema({query: queryType});


// Logger middleware
var logger = function(req, res, next) {
  console.log("GOT REQUEST >", req.ip);
  next(); // Passing the request to the next handler in the stack.
}

var app = express();
app.use(logger);
app.use('/graphql', graphqlHTTP({
  schema: schema,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

Adding React Library, SASS, Images and font loaders

branch-name:  
Click To Copy

 

React is cool frontend library that updates only the necessary portions of the html. Let’s add it to the project.

Adding React library

yarn add react react-dom

First, let’s keep things more consistent and do some house cleaning. Let’s rename ./src/app.js to ./src/index.js This will be the place where React app will be attached to our HTML and remove ./src/greeting.js

Also change Webpack entering point to reflect app.js filename change:

./webpack.config.js

entry: [
  './src/index.js'
],

Optionally but not necessarily  change the app.js to index.js in the package.json. It’s always a good practice to keep things consistent.

Alternatively you could simply ommit the name of the entrance and Webpack by default will look for src/index.js

Now Let’s do a react app. Remove ./src/app.js and ./src/greeting folder and let’s

Create simple react app

and place it into the components folder that we will create.

mkdir -p src/components/App

and add it’s index file

./src/components/App/index.js

import React, { Component } from 'react';


export default class App extends Component {

  render() {
    return (
      <div>
        <h1>React is running</h1>
      </div>
    );
  }
}

Attach React app to the main html

edit ./src/index.js remove it’s test contents and replace it with this:

./src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

ReactDOM.render(<App/>, document.getElementById('root'));

if (module.hot) {
  module.hot.accept();
}

What we just did:
– we imported React and React dom library. React dom is needed to attach React app to the HTML
– we imported newly created React app
– we attached the app to the root container.
– we also added the HMR activation script.

One last thing before we are ready to test is to add the html container where the react app will be attached.

./index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Babel Webpack Boilerplate</title>
    </head>
    <body>
        <h1>Babel Webpack React Boilerplate</h1>
        <div id="root"></div>
        <script type="text/javascript" src="dist/main-bundle.js"></script>
    </body>
</html>

 

Start the project yarn start

Oh snap! Dev server crashed with error message:
ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /Users/toninichev/Examples/WebpackReactReduxApolloTutorial/.babelrc: Error while parsing config – JSON5: invalid end of input at 1:1

Why this is happening? We replaced the plain JavaScript form ./src/index.js with JSX syntax that react is using but Babel doesn’t know how to transpile this. Let’s fix it and add in .babelrc config, the react preset plug-in, and install it.

yarn install @babel/preset-react --dev

./.babelrc

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

Right now we need only @babel/preset-react” and @babel/preset/env plug-ins, but in the feature we will have to use more of the advanced ES6 syntax and add more.

Give it another shot and do yarn start

At this point we should have working react app!

Create React component

Let’s re-create ‘greetings’ component but this time making it React component. Create Greetings folder  mkdir -p ./src/components/Greetings folder and add:

./src/components/Greetings/index.js

import React from 'react';

function Greetings(props) {
  return <div>Hello, {props.user}</div>;
}

export default Greetings;

and render the component.

./src/components/App/index.js

import React, { Component } from 'react';
import Greetings from '../Greetings';

export default class App extends Component {

  render() {
    return (
      <div>
        <h1>React is running</h1>
        <Greetings user="John" />
      </div>
    );
  }
}

What we just did:
– in ./src/greetings/index.js we created a new Greetings component that will take property of the user passed from the higher component and show it. Props stands short for properties.
– in ./src/components/app/index.js we are loading the Greetings component and passing the user name as a property to the Greeting component.

Webpack loaders.

Webpack loaders  allow us to bundle any static resource way beyond JavaScript.

Using SCSS and adding SASS loader.

So far so good. We could see our new React component rendering but no styles.
The simplest way to style our components could be to add css-loader and then to inject the css into the DOM using style-loader.

...
    {
      test: /\.(s)?css$/,
      use: [
        'style-loader',
        'css-loader'
      ]
    },
...

 

But it could be really nice if we could add specific styles only applying to this component.

Go to the terminal, and install these modules:

yarn add style-loader --dev
yarn add css-loader --dev
yarn add postcss-loader --dev
yarn add sass-loader --dev
yarn add node-sass --dev
yarn add autoprefixer --dev

Add the highlighted lines in webpack.config.js

./webpack.config.js

module.exports = {
  mode: 'development',
  entry: [
    '@babel/polyfill',    
    './src/index.js'
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },
      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true

            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      }      
    ]
  }
};

 

 what we just did:
– added sass-loader which will first convert SCSS to plain CSS
– added postcss-loader interprets @import and url() like import/require() and will resolve them.
– added style-loader simply inserts the css into the DOM

Let’s add some styling for our new greetings component:

./src/components/Greetings/styles.scss

.wrapper {
  background: blue;
  h2 {
    color: white;
  }
}

and let’s load it and style the component:

./src/components/Greetings/index.js

import React from 'react';

const styles = require('./styles.scss');

function Greetings(props) {
  return <div className={styles.wrapper}><h2>Hello, {props.user}</h2></div>;
}

export default Greetings;

What we just did:
– we added the new style that will be loaded by JavaScript into the new component (line 3)
– we applied the new style (line 6) by using JSX way of doing this.

If you are not familiar with React and JSX please read the React tutorial first.

Congratulations! Now you have React component that could really be in use!

Adding image loader.

Let’s add url-loader and file-loader and save them as a dev dependencies.

yarn add url-loader file-loader --save

Download a small Home Icon that we are going to add it to our greeting component, and save it into a new folder under ./src folder./src/images/home.png (Make sure that you renamed the file to home.png ). Now let’s add the Webpack config to load images:

./webpack.config.js

import webpack from 'webpack';
import getEnvironmentConstants from './getEnvironmentConstants';

module.exports = {
  mode: 'development',
  devtool: 'eval-source-map',
  entry: [
    '@babel/polyfill',
    './src/index.js',
    'webpack/hot/dev-server',
    'webpack-dev-server/client?http://localhost:8080/',
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  }, 
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({ 'process.env' : getEnvironmentConstants() } )
  ],   
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },


      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: false
            }
          }
        ],
      }, 
      
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      }      
      
    ],
  }
};

 what we just did:
– we are testing files for image type (line 64) (png, gpeg or gpg, svg)
– we are using url-loader to load the images.
– url-loader – limit: 8000 means that any image smaller than 8kb will be converted to base64 string and inlined in the bundle. Any other image will be passed to the loader that will pass it as a file.

An now let’s use the image in our Greetings component:

./src/components/Greetings.js

import React from 'react';
const styles = require('./styles.scss');
import homeIcon from '../../images/home.png';

function Greetings(props) {

  return (<div className={styles.wrapper}>
            <img height='75px' width='75px' src={homeIcon} /> 
            <h2>Hello, {props.user}</h2>
          </div>);
}
export default Greetings;

Navigate to the greeting component, and we should see the house image there.

Let’s also check if we can add the image url in the CSS. Remove the image tag from the file above, and add it in the greeting CSS

./src/components/Greetings/styles.scss

.wrapper {
  background-image:url('../../images/home.png');
  height: 500px;
  h2 {
    color: black;
  }
}

navigate to the page again and you will see a big background house in the component.

Adding fonts loader.

Loading fonts is as easy as adding another file-loader as it is shown below.

./webpack.config.js

module.exports = {
  mode: 'development',
  entry: [
    '@babel/polyfill',    
    './src/index.js'
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      },

      // SCSS
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true,
              importLoaders: 2,
              localIdentName: '[folder]-[local]',
              sourceMap: true
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              plugins: () => [require('autoprefixer')()],
              sourceMap: true
            },
          },
          {
            loader: 'sass-loader',
            options: {
              outputStyle: 'expanded',
              sourceMap: true
            }
          }
        ],
      },
      // images
      {
        test: /\.(png|jp(e*)g|svg)$/,  
        use: [{
            loader: 'url-loader',
            options: { 
                limit: 8000, // Convert images < 8kb to base64 strings
                name: 'images/[hash]-[name].[ext]'
            } 
        }]
      },
      //File loader used to load fonts
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader']
      }                    
    ]
  }
};

then we could download some font for example from fonts.google.com or here and add the font in the css using font face:

./src/components/App/styles.scss

@font-face {
  font-family: 'MyFont';
  src:  url('../../fonts/aclonica/aclonica-regular.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
}

.appWrapper {
  background: rgb(141, 141, 172);
  color: white;
  text-align: center;
  font-family: MyFont;
}

 

Modifying component to use latest ES syntax.

We want to be cool devs and to use the latest technologies so let’s modify our Greeting component to use the latest ES6 syntax and take advantage of Babel that we installed in the previous chapters.

Modify greetings index file as follows:

./src/components/Greetings/index.js

import React from 'react';
import styles from './styles.scss';

function Greetings(props) {
  return(<div className={styles.wrapper}>
          <h2>Hello, {props.user}</h2>
        </div>);
}
export default Greetings;

What we just did:
– (line 2) we refactored the component to use the latest EC6 syntax by replacing require with import
– we removed the image tag since now we are loading the image-background from CSS.

 

branch-name:  
Click To Copy

 

Adding Webpack-dev-server

branch-name:  
Click To Copy

Ading Webpack-dev-server

what is webpackdevserver ? Like it was said  “The webpackdevserver is a little Node.js Express server, which uses the webpackdev-middleware to serve a webpack bundle. It also has a little runtime script, which is connected to the server via Sock.js. The server emits information about the compilation state to the client, which reacts to those events.

Basically webpack-dev-server is handy dev tool that is not only an HTTP server but it could monitor your files for changes and rebuild the bundle and serve it from memory which speeds up the developing process.

Let’s add webpack-dev-server so we could open the project via http. Additionally  Webpack dev server adds a few more benefits like live reloading for the client side code.
Webpack dev server like it’s name suggests should be used only for development and never in production.

yarn add webpack-dev-server --dev

Let’s add a script to execute it through Yarn/NPM. Also, since we have the entry and the output set up in webpack.config.js we can simplify the build-dev and build-prod scripts as well.

  "scripts": {
    "start": "webpack-dev-server",
    "clean": "rm -rf ./dist",
    "lint": "eslint .",
    "build-dev": "webpack --mode development",
    "build-prod": "webpack --mode production"
  }

Let’s give it a try and start the server:

yarn start

and point the browser to http://localhost:8080/

If everything worked fine, you should be able to see the project and the console message.

But bundle file is still served statically from the ./dist` folder instead of bundled dynamically from Webpack-dev-server and serving it from memory,  and you could confirm this by deleting the ./dist folder, and restarting the server. And you will get this error message in the console.

GET http://localhost:8080/dist/main-bundle.js net::ERR_ABORTED 404 (Not Found)

As you see bundle file is not there, instead Webpack-dev-server is serving it from here:http://localhost:8080/main-bundle.js
Point your browser there and you will see the bundle file. Let’s fix this. Open ./webpack.config.js and add the highlighted lines:

./webpack.config.js

module.exports = {
  mode: 'development',
  entry: [
    '@babel/polyfill',
    './src/app.js'
  ],
  output: {
    filename: '[name]-bundle.js',
    publicPath: '/dist',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
};

This instructs Webpack-dev-server to serve the bundle from ./dist location but this time the bundle will be dynamically generated by Webpack-dev-server, instead of statically served from the ./dist location.

The advantage is that the bundle will re-generate on change and the location will be the same as the production location but just served from the memory.
Reload the browser and now the error message in the console is gone, and if you navigate to `http://localhost:8080/dist/main-bundle.js` you will see newly generated bundle.

 Webpack-dev-server is serving the bundle from memory. You could go ahed and delete ./dist folder and reload the server and the main bundle should still be accessible.

Set up Webpack to monitor your project’s folder for changes and rebuild the bundle.

Webpack-dev-server comes with very handy option --watch to monitor for changes, rebuild the bundle and restart the browser.
The god news is that by default the --watch` mode it turned on so if you go and do some changes into any file under ./src folder and save it, you will see in the console that Webpack-dev-server will re-generate the bundles and the browser will restart and reflect the changes.

There are two ways of setting up Webpack-dev-server to reload the page:

  • Iframe mode (page is embedded in an iframe and reloaded on change)
  • Inline mode (a small webpack-dev-server client entry is added to the bundle which refreshes the page on change)

But we could go even further.

Adding Hot Module Replacement plug-in.

Having Webpack-dev-server to reload our page and reflect the changes is cool but we still reload the whole page and components state is reset back to the initial state. We could tell Webpack-dev-server to use hot module replacement plugin (HMR) to re-load only the components that did change, but before doing this, let’s make some real component that will show something in the website instead of the console.

Remove ./src/test.js.

Let’s create greeting component that will simply show a greeting in the webpage.
create folder called ‘greeting’ and file in ./src/greeting/index.js with these contents:

./src/greeting/index.js

function doGreeting(name) {
  return "Hello " + name;
}
module.exports = doGreeting;

edit ./src/app.js to load the new component and attach the result to an HTML div component. We have to also add the highlighted if statement that will enable HMR.

./src/app.js

var greeting = require('./greeting');

var result = greeting("John");
document.querySelector('#root').innerHTML = result;


if (module.hot) {
  module.hot.accept();
}

and edit index.html to have a div component with id=”root” that  will be the entry point for our app and will show the result of our greeting component.

./index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Babel Webpack Boilerplate</title>
    </head>
    <body>
        <div id="root"></div>
        <script type="text/javascript" src="dist/main-bundle.js"></script>
    </body>
</html>

Last step is to tell Webpack-dev-server to use HMR.

./package.json

{
  "name": "babel-webpack-react-boilerplate",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.1.2",
    "@babel/preset-env": "^7.1.0",
    "@babel/preset-react": "^7.0.0",
    "babel-eslint": "^10.0.1",
    "babel-loader": "^8.0.4",
    "eslint": "^5.7.0",
    "eslint-loader": "^2.1.1",
    "eslint-plugin-react": "^7.11.1",
    "webpack": "^4.20.2",
    "webpack-cli": "^3.1.2",
    "webpack-dev-server": "^3.1.9"
  },
  "scripts": {
    "start": "webpack-dev-server --hot",
    "clean": "rm -rf ./dist",
    "lint": "eslint .",
    "build-dev": "webpack --mode development",
    "build-prod": "webpack --mode production"
  }
}

what we just did:
– we added --hot parameter to tell Webpack-dev-server to use HMR.

Go ahead, restart the server and give it a try and now if you change something in ./src folder the browser will reflect the changes without reloading. (you could observe the reload button and it will never show reload). Also if you look at browser’s console you will see the messages from HMR

[HMR] Waiting for update signal from WDS…
client:71 [WDS] Hot Module Replacement enabled.

Now development process could really speed up!

branch-name:  
Click To Copy

 

Adding Config file and Babel loader

branch-name:  
Click To Copy

Why adding Babel ?

Java Script is a language that evolves really fast, but unfortunately many people still use old browsers that doesn’t support the latest Java Script syntax.

Let’s say that we would like to be at the edge of the technology and use the latest ECMA Script 6 or (European Computer Manufacturers Association) or EC for shorter and we want to use the latest one EC6. The solution will be to `transpile` the new EC6 to older version, that is supported even with the old browsers.

But Webpack is just JS bundle creator and it doesn’t know how to translate the new EC6 syntax to the old one and here comes the Webpack loaders.

Webpack loaders simply pre-process the files which Webpack is going to bundle.

There are tons of loaders, you could check them here https://webpack.js.org/loaders/ but the one that we need for this particular task is called Babel

There are two ways of adding loaders: with a config file or from the package.json. Since doing it through a config file is more flexible we will explore this option.

Adding Babel loader and Webpack config file.

In order to use Babel loader, we have to tell Webpack about it.

Adding Webpack config file.

 Just creating webpack.config.js in the root folder of our project is enough to create Webpack config file, which Webpack will pick up automatically from this location.

But for our particular task we need to add Babel loader to our project. So first before we move any further, let’s do this:

Babel got split in a couple of sub-modules that we need to add:

  • babel-core
  • babel-loader
  • babel-preset-env – for compiling Javascript ES6 code down to ES5

yarn add @babel/core babel-loader @babel/preset-env

– create new file called webpack.config.js in the root of the project folder with these contents:

./webpack.config.js

module.exports = {
  mode: 'development',
  entry: [
    './src/app.js'
  ],
  output: {
    filename: '[name]-bundle.js',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
};

What we just did:
– we created a brand new file called webpack.config.js in the root folder of our project and Webpack will look there and pick it up.
– we added mode: “development” insted of passing it through the build script (line 2)
– added entry point to to tell webpack where to look for the bundle files.
we could have skip this parameter if we name our bundle entry point was named ./src/index.js and Webpack was going to pick it automatically, but for the purpose of understanding Webpack config we customized it with ./src/app.js
– added output parameter to specify Where webpack should create the bundle.
By default if this parameter is not specified Webpack will create ./dist/main.js bundle.
– we added a Babel module config, that is going to transpile all JS files matching this RegExp pattern /\.js$/ and make them old fashioned JS that all browsers understand.
– we told Webpack to exclude node_modules “exclude: /node_modules/,” (line 13) since this folder is where npm installs packages and it doesn’t need to be transpiled.

Now we have to tell Babel how to transpile our files to JavaScript. Create .babelrc in the root of the project, with these contents:

./.babelrc

{
  "presets": [
    "@babel/preset-env"
  ]
}

Presets are like plug-ins. They describe which ES features we are going to use so Babel could transpile back to ES5 that all browsers will understand. In our case we added @babel/preset-env so we could use latest ES coding, and since we are going to add React we added @babel/preset-react which under the hood includes:

so Babel will know how to deal with react-jsx files.

Let’s give it a try:

yarn build-dev

and check the ./dist folder. Looks good right ?

But if you try to run in on old IE you will notice that the site break. We still need babel-polyfill to  emulate a full ES2015+ environment (no < Stage 4 proposals). This woks on the front end only.

Adding Babel-polyfill.

Let’s install the library first: yarn add @babel/polyfill

There are two different ways to add it: we could use require(“@babel/polyfill”); or import “@babel/polyfill”; or we could use Webpack to add it.

Let’s use Webpack:

./webpack.config.js

module.exports = {
  mode: 'development',
  entry: [
    '@babel/polyfill',
    './src/app.js'
  ],
  output: {
    filename: '[name]-bundle.js',
  },  
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
};

Now we added support for all old browsers.

Adding another pre-processor: Eslint.

Now we know how to add Webpack modules, let’s add another one, that will help us identify problems with our code even before we compile it.

yarn add eslint eslint-loader babel-eslint eslint-plugin-react --dev

what we just did:
– we installed:
eslint – the core JS linter
eslint-loader – tells webpack that we are going to use eslint in our build
babel-eslint – provides linting for ES6 code.
eslint-plugin-react – Since we are going to use React, we have to install the React plugin for
eslint.

The easiest way to configure eslint is through .eslintrc config file. Let’s create one in the root folder with these contents:

./.eslintrc

{
  "parser": "babel-eslint",
  "plugins": [
    "react"
  ],
  "rules": {
    "no-undef": 1
  }
}

what we just did:
– we set up the parser to be babel-eslint (line 1)
– we added react plugin
– we added one rule “no-undef” with a value ‘1’. The possible values are: 0 = off, 1 = warn, 2 = error

Now in the root folder execute:
./node_modules/eslint/bin/eslint.js .

the output probably will be similar to this:

~/babel-webpack-react-boilerplate/dist/main-bundle.js
45:48 warning ‘Symbol’ is not defined no-undef
46:44 warning ‘Symbol’ is not defined no-undef

~/babel-webpack-react-boilerplate/src/test.js
3:3 warning ‘console’ is not defined no-undef

✖ 3 problems (0 errors, 3 warnings)

….

Let’s add a script to execute linter through npm.

"scripts": {
  "clean": "rm -rf ./dist",
  "lint": "eslint .",
  "build-dev": "webpack --mode development ./src/app.js -o ./dist/main-bundle.js",
  "build-prod": "webpack --mode production ./src/app.js -o ./dist/main-bundle.js"
},

And give it a try:
yarn lint

 NPM always looks first in project’s node_modules folder, so if you have eslint (or any other module) installed globally and locally, it will always pick the local version.

Adding .eslintignore
And we could add the files that we don’t want to be checked into
./.eslintignore

dist
webpack.config.js

Let’s run the linter yarn lint

/Users/toninichev/Downloads/webpack-test/src/test.js
2:3 warning ‘console’ is not defined no-undef

✖ 1 problem (0 errors, 1 warning)

✨ Done in 1.38s.

Let’s say that we don’t want to `fix` this since this is not a real problem, but we want to tell Eslint to ignore it we could add /* eslint-disable no-undef */

./src/test.js

/* eslint-disable no-undef */

const test = (msg) => {
  console.log(msg);
}
export default test;

Run Eslint again and you sould not see any issues.

branch-name:  
Click To Copy

 

Webpack – Zero Config

branch-name:  
Click To Copy

Development vs Production modes.

When you ran the code from the previous tutorial you might notice this message:
WARNING in configuration
The ‘mode’ option has not been set. Set ‘mode’ option to ‘development’ or ‘production’ to enable defaults for this environment.

Webpack 4 is coming with two predefined modes: “development” and “production” and automatically optimize the code for the corresponding build.
To enable the corresponding mode we simply need to call Webpack with –mode development or –mode production parameter.
Let’s go to package.json and add ‘build’ script in so we could call it with npm without passing all parameters:

./package.json

  "scripts": {
    "build": "rm -rf ./dist && webpack --mode development ./src/app.js -o ./dist/main-bundle.js"
  }

Let’s execute the script that we just added and create a new bundle.

yarn build

But why stopping there? We could configure Webpack with no parameters at all. Just passing –mode.

Setting Webpack with Zero config

Webpack by default will look for entry point in ./src/index.js and will output the bundle in ./dist/main.js

Change the script section in package.json as follows.

"scripts": {
  "clean": "rm -rf ./dist",
  "build": "webpack --mode development"
},

what we just did:
– we added a clean up script (line 2) that will take care of deleting the old bundle once executed.
– we added ‘build’ script, with almost 0 config parameters.

Now if we rename src/app.js to src/index.js Webpack will pick it ot automatically and will output the bundle code in dist/main.js

We also removed ‘rm -rf’ and added it into another script called clean.
Now in the console in the root of the project if we do:

yarn run clean
yarn run v1.3.2
$ rm -rf ./dist
✨ Done in 0.11s.
$ yarn run build
yarn run v1.3.2
$ rm -rf ./dist && webpack –mode development ./src/app.js -o ./dist/main-bundle.js
Hash: 3e7c9702fe472d8df3d2
Version: webpack 4.16.3
Time: 98ms
Built at: 08/01/2018 4:10:30 PM
Asset Size Chunks Chunk Names
main-bundle.js 4.48 KiB main [emitted] main
Entrypoint main = main-bundle.js
[./src/app.js] 55 bytes {main} [built]
[./src/test.js] 69 bytes {main} [built]
✨ Done in 1.06s.

We well see that Webpack prepared a bundle for us with Zero Config parameters.

Setting Webpack override

The Zero Config was great but if the complexity of the project grows, we will need to customize Webpack so let’s revert the changes back.
– Rename src/index.js back to src/app.js
– Change the scripts parameters in package.json to look like this:

"scripts": {
  "clean": "rm -rf ./dist",
  "build-dev": "webpack --mode development ./src/app.js -o ./dist/main-bundle.js",
  "build-prod": "webpack --mode production ./src/app.js -o ./dist/main-bundle.js"
},

What we just did:
– We added back “clean” script to remove ./dist folder.
– We added “build-dev” and “build-prod” build scripts.

Let’s give it a try. From the terminal run:

yarn clean

This will get rid of the ./dist folder.

yarn build-dev

and look at ./dist/main-bundle.js Nothing unusual there. The main-bundle.js was re-created.

Let’s check the production build script. Run:

yarn build-prod

now it gets interesting, if you look at ./dist/main-bundle.js you will notice that it is minified and prepared for production use. Webpack also uses UglifyJSPlugin when “–mode” is set up to “production”.

Now you have production build ready!

 What is far from true is that this is really production ready but we will figure out this later in this tutorial.

branch-name:  
Click To Copy

 

Webpack – Getting Started

How to use this tutorial ?

The table of contents of this tutorial is located on the right side

There are two projects that we need:
– the front-end website that we are going to build in this tutorial,
GraphQL server which we are going to use later in “Adding Apollo provider and introduction to making queries to GraphQL“.

There are two GitHub repositories with the complete projects located here:
GitHub:WebpackReactReduxApolloTutorial

And the GraphQL server located here
GitHub:graphql-boilerplate

Instructions of how to set up GraphQL server could be found HERE.

When you go through each chapter you could check out the appropriate branch which contains the chapter specific code, instead of looking at the complete project.
This way will be easier to follow the developing process. Each branch name could be found at the beginning and the end of the tutorial. Look for branch-name: XXXXX

In general it would be better if you follow the tutorial and build the website yourself but if you get stuck somewhere feel free to clone the repository and check out the appropriate branch.

Setting up the project

branch-name:  
Click To Copy

 

mkdir babel-webpack-boilerplate
cd babel-webpack-boilerplate
yarn init -y
yarn add webpack webpack-cli --dev

What we just did:
– line 1: created project’s folder
– line 2: navigate to project’s folder root
– line 3: executing yarn init -y initialized our new project. – y simply means ‘answer yes and use the default init settings’. A package.json is created, which is used to describe the npm project dependencies and executable scripts.
– line 4: we added Webpack as a dev dependency (–save-dev) in our project.

Creating project’s files

Using your favorite text editor create index.html in the root folder of the project, with plain html, and a script tag to include our bundled Java Script code (Line 9).

./index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Babel Webpack Boilerplate</title>
    </head>
    <body>
        <h1>Babel Webpack React Boilerplate</h1>
        <script type="text/javascript" src="dist/main-bundle.js"></script>
    </body>
</html>

Create folder to store the actual bundle files:

mkdir ./src

Create the actual bundle files:

src/app.js

import test from './test';
test('Babel is running!');

src/test.js

const test = (msg) => {
  console.log(msg);
}
export default test;

and dist folder to store compiled bundle:

mkdir ./dist

The project’s layout should look like this:

babel-webpack-react-boilerplate
├── dist
├── index.html
├── node_modules
├── package.json
├── src
|        ├── app.js
|       └── test.js
└─ package.lock

* package.lock is used to describe which versions of node modules are installed, and is create automatically after executing npm install, so don’t worry if it’s not there yet.

Running Webpack and creating Java Script bundle.

Now we have laid out a simple project, let’s create a bundle and run it.
In the terminal in the project’s root folder execute

./node_modules/webpack/bin/webpack.js ./src/app.js -o ./dist/main-bundle.js

What we just did:
– we called Webpack from the command line.
– passed the entry point ./src/app.js to create the bundle.
– and the output file -o ./dist/main-bundle.js

Now if we look at the ./dist folder, we will see that the bundle.js is created.

Testing the project

Open your browser and navigate to the the project’s root.

Look at the console and you will see ‘Babel is running!’.

Congratulations, you just created your first bundle!

branch-name:  
Click To Copy

 

 

where to go from here ?