gunaprofile / webpack-react

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

webpack-react

  • Refer webpack intro from burgerbuilder webpack branch.
  • One we did "npm init" we have to add webpack dependencies
npm install --save-dev webpack webpack-dev-server
  • The second one is this development server we want to use so that we can test our application locally on the machine and webpack is well basically the build tool itself, webpack-dev-server wraps this build tool, they're both from the same team.
  • The scripts and any other imports generated by the workflow will be injected into this (index.html) file automatically.we'll add a configuration which does this to our set up
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
  • Now we need to add assets, component and container folder and other index.js, App.js and index.css file (Refer corresponding files)
  • With that above structure lets setup basic react app we can work with.
  • Once we have done with basic setup we have to add react dependencies to our package.json
npm install --save react react-dom react-router-dom
  • Now we have to add webpack configuration like latest js transformation, css and images..
  • Now we have to add new script which is start script in package.json
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server"
  },
  • Start script has to the name of the package "webpack-dev-server" , this start script will spin up the dev-server however the dev-server out of the box won't work, we need to configure it (webpack.config.js). This file should be named correct. webpack will access config file with the same name.
  • Now in this file we are going to setup "development" workflow. later we will setup production workflow.
const path = require('path');
module.exports = {
    devtool : 'cheap-module-eval-source-map',
    entry : './src/index.js',
    output: {
        path :path.resolve(__dirname, 'dist'),
        filename : 'bundle.js',
        publicPatch : ''
    },
    resolve: {
        extensions: ['.js', '.jsx'],
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            }
        ]
    }
}
  • Here entry is the file that webpack should access first and then it should access its other dependencies.

  • I'll add one additional configuration, it's called dev tool and here we can define which kind of source maps, if any webpack should generate and I'll use cheap-module-eval-source-map here.it is the best kind of source maps you can generate for the development.so this is what you should use here to get source maps which allow you to easily debug your original code in the browser.

  • path - where this should be stored,

  • filename - how our file should be named (bundle.js - since its only for js we will add loaders for css and images)

  • publicPatch - that's important for webpack to know where our files are put to and if that then is the root folder of the server in the end or if it's a nested folder.

  • Here for publicPatch we want to put to root folder so empty string. which means you store the files in a specific folder and you don't need to adjust any imports or adjust for anything, the file structure, the folder structure will be the folder structure as we deployed in the end

  • here const path = require('path'); is the default import from node.. no need seperate npm for this , if node installed we can access this.

  • The path object here has a resolve method which allows us to generate an absolute path in the end, here we can pass a special variable which we have available in node systems and webpack will use node behind the scenes.

  • One important thing we have to add upfront is to make sure that webpack automatically appends .js at the end of these imports here so that it is able to import that correctly.

  • Now to get it to behave like this, we have to configure it in a certain way, in our webpack config on the same level as entry output and so on, we add the resolve configuration.

  • That takes a javascript object and there we can resolve some extensions, so we can basically tell webpack that it should be aware of certain extensions and if it encounters an import without an extension, it should try these extensions and see if it finds a file of one of these.

  • Now the next and most important thing are our loaders though where I want to make sure that we do correctly handle different file types,this is done with a special configuration on the same level as output and resolve which is named module because I import a dependency is referred to as a module,so you could kind of translate module with file.

  • So here we set up what webpack should do with the individual files and we control this with a rules array.

  • inside we will add test scenarios like if anythinhg ends with .js, then I'll want to apply a certain loader,now a loader is like a third party plugin which does something to the file.

  • And in exlude we added node_modules, so it shouldn't try to transform anything in node modules since these are third party libraries we already do import,we don't want to transform them again,

  • Loaders - Well for javascript, we will use the babel loader,babel is the defacto standard for transpiling next generation javascript to current gen javascript,so let's install and configure babel.

npm install --save-dev babel-loader babel-core babel-preset-react babel-preset-env
  • babel-loader just have provide the hook for webpack, that don't have knowledge about how to transpile your javascript,so we also need to install babel-core which holds that knowledge.

  • We now also need some configurations to basically tell babel which kind of next generation javascript or which special javascript syntax we want to support and here, these are so-called presets, the packages

  • babel-preset-react to support jsx

  • babel-preset-env for an environment adjusting preset which basically has a look at the features we use and automatically transpiles everything correctly.

  • Once we installed babel we need to configure that for that add a file babel.rc

{
    "presets" : [
        ["env",{
            "targets":{
                "browsers": [
                    "> 1%",
                    "last 2 versions"
                ]
            }

        }],
        "react"
    ]
}
  • We finished babel config now we have to add babel loader to the webpack config

Adding CSS support.

  • For CSS we have to add new rule, So instead of loader I can use "use" here, that's the long form, loader is the very short form if we just want to set up a loader without any config, if we want to set up multiple loaders or a loader with config, we should use use here.
  • here we want two loaders
npm install --save-dev css-loader style-loader
  • Need a loader which basically tells webpack what to do with these .css imports and the style loader. That's a loader which will then extract the css code from the css files and inject it at the top of our html file hence reducing the amount of file downloads we have to make.
{
                test: /\.css$/,
                use: [
                    {
                        loader : 'style-loader'
                    },
                    {
                        loader : 'css-loader',
                        options : {
                            importLoaders : 1,
                            modules : true,
                            localIdentName: '[name]_[local]_[hash:base64:5]'
                        }
                    },
                    {
                        loader : 'postcss-loader',
                        options : {
                            ident : postcss,
                            plugins: () => [
                                autoprefixer({
                                    browsers: [
                                        "> 1%",
                                        "last 2 versions"
                                    ]
                                })
                            ]
                        }
                    }
                ],
                exclude: /node_modules/
            }
  • webpack parses loaders in this use array and applies them from right to left,so from bottom to top if we write it like this. So it first takes the css loader which it should because that makes it understand the css imports and don't throw an error and then it applies the style loader on the extracted css code and that's exactly the order we need here.

  • Now we can also give the css loader which is responsible for parsing and handling the css another property which is called options. Now options is a javascript object where we can configure this loader, now here I want to set module to true to enable css modules and I'll set up the local ident name to define how the generated css classes due to css modules should look like. And here I'll take the name of the class we defined, two underscores then the module name, so the component name and then a hash to really have unique names, this is the same set up we added in the styling module to do the configuration create react app gave us.

  • We're not done with css though,I also want to add auto-prefixing

npm install --save-dev postcss-loader
  • now post css sounds like it does something to css after we parse that, here it'll actually run before the css loader, dive into the css file and adjust our code before css loader pulls it out and adjusts the class names and so on. The post css loader simply is a loader which allows us to transform the css,

  • however I will simply add it as another loader and therefore in third position because it should run before that css loader.

npm install --save-dev autoprefixer
  • plugins, that actually takes a function which returns an array and these are now steps we should apply or we want to apply to transform everything. that's a third party library which is able to auto prefix our css properties,

  • so here I'll add autoPrefixer and this is actually a function we need to execute and we pass a javascript object to it to configure it and there again we pass a list of browsers, so browsers is a list and we can take the list we set up in babelrc, that's the browser list I want to support, I'll just paste it into the browser's list for the autoPrefixer.So now we prefix css as required for these browsers we support and it will automatically figure out which browsers these are for us.

  • Now since we run one additional loader before css loader, we actually need to inform css loader about that, that's just a special set up css loader needs. So there I'll add import loaders and set this to one because we run one loader before css loaderis applied.

Creating rule for images.

{
    test: /\.(png|jpe?g|gif)$/,
    loader: 'url-loader?limit=7000&name=images/[name].[ext]' 
}
  • And for these files, I want to use a loader we have to install
sudo npm install --save-dev url-loader
  • Now the URL loader is a loader which will take our images and if they are below a certain limit we define, it will actually convert them into data 64 URLs which it can inline into our documents,so we don't have to download extra file.
  • so we don't have to download extra file. But for bigger files, it would be inefficient so files above that limit we specify will simply be copied to our output folder and it will then generate a link to these files and put that into our import we use in our components.
  • What we do here is we use the URL loader here but now we configure it with query params and we do it with query params so that the configuration here, it gets automatically passed onto a fallback we will use if the limit is exceeded.
  • Now that fallback is another loader we also need to install though
 npm install --save-dev file-loader
  • Now that is a loader which in the end simply copies the file you could say, because it copies it into a new direction and gives us a link to it and that is the fallback we'll use automatically here if we exceed the limit we now define here. As a query param, limit could be let's say 7000b.
  • So the image is going to get copied into that folder, in our dist folder because that output set up is taken into account even though we're not creating a bundle.js file but the path still is taken into account,

Lazy loading

  • now I want to have a look at plugins and then see if the setup actually works.
  • we don't need any for development workflow, we will add one later when we set this up for production, there I want to add some plugin but for now that is all, I won't add a plugin here.
  • if we run our start script which runs the dev server which I said would automatically take this config file.
  • We need to adjust our setup here to be able to create dynamically generated extra chunks of code,
  • lazy loading means that it's an extra bundle and not part of the main bundle which is downloaded initially, to support code splitting in webpack and code splitting is just a different name for lazy loading,I have to add chunkFileName here to my output config.

Injecting the Script into the index.html File

  • we need to connect our index.html file to the output files which are generated, when using the dev server they are only stored in memory but they're still generated. We need to install a webpack plugin for that,
sudo npm install --save-dev html-webpack-plugin
  • this is a special plugin which allows to connect our html file with our output and webpack will do that automatically and inject our bundled script and so on into that html file.
  • we have to add config for html-webpack-plugin in webpack.config.js
plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + '/src/index.html',
            filename: 'index.html',
            inject: 'body'
        })
    ]

Creating the Production Workflow

  • for production workflow let follow the below steps. in my package.json file, I want to have a second script, let's name it build.
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server",
    "build": "webpack"
  },
  • This will create a dist folder inside the root folder which will have all you image,css and other JS files.. this certainly isn't the code we want to ship.
  • The goal instead is to have a dedicated workflow for production which does some optimizations.
  • Let us add new config file webpack.prod.config.js
const path = require('path');
const autoprefixer = require('autoprefixer');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require(webpack);
module.exports = {
    devtool : 'cheap-module-source-map',
    entry : './src/index.js',
    output: {
        path :path.resolve(__dirname, 'dist'),
        filename : 'bundle.js',
        chunkFilename: '[id].js',
        publicPath : ''
    },
    resolve: {
        extensions: ['.js', '.jsx'],
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader : 'style-loader'
                    },
                    {
                        loader : 'css-loader',
                        options : {
                            importLoaders : 1,
                            modules: {
                                mode: 'local',
                                localIdentName: '[local]--[hash:base64:5]'
                            }
                        },
                    },
                    {
                        loader : 'postcss-loader',
                        options : {
                            ident: 'postcss',
                            plugins: () => [
                                autoprefixer({
                                    browsers: [
                                        "> 1%",
                                        "last 2 versions"
                                    ]
                                })
                            ]
                        }
                    },
                ],
                exclude: /node_modules/
            },
            {
                test: /\.(png|jpe?g|gif)$/,
                loader: 'url-loader?limit=7000&name=images/[name].[ext]' 
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + '/src/index.html',
            filename: 'index.html',
            inject: 'body'
        }),
        new webpack.optimize.UglifyJsPlugin()
    ]

}
  • the dev tool, here I'll remove the eval to create more optimal source maps which are less resource intensive,you can then always decide whether you want to deploy them or not but it's nice to have source maps here too to quickly find some bugs in the production workflow, if there are any.
  • now there we have a plugin for connecting the html file and I want to keep that, what I want to do now is I also want to uglify my output, I want to optimize it and that actually is a plugin that's built into webpack, we don't need to install it. its already there in webpack so..
const webpack = require('webpack');

 plugins: [
        new HtmlWebpackPlugin({
            template: __dirname + '/src/index.html',
            filename: 'index.html',
            inject: 'body'
        }),
        new webpack.optimize.UglifyJsPlugin()
    ]
  • For production add exact production config in build script.
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server",
    "build": "rimraf dist && webpack --config webpack.prod.config.js --progess --profile --color"
  },
  • Then we need to install one more plugin.
sudo npm install --save-dev rimraf
  • it allows us to delete a folder or files and I want to delete the dist folder at the start of every build process so that we create a brand new one.

Note

Adding babel-polyfill The current setup won't support all browsers theoretically supported by React. Features like Promises and Object.assign() are missing in older browsers - especially in IE of course.

If you need to support these browsers, you need to add a polyfill (a package which provides these features for older browsers). babel-polyfill is a great and easy-to-use choice.

Add it like this:

npm install --save babel-polyfill 

Add the following import to the top of your index.js file:

import 'babel-polyfill';

Change the config of your env babel preset in the .babelrc file:

"presets": [
    ["env", {
        "targets": {
            "browsers": [
                "> 1%",
                "last 2 versions"
            ]
        },
        "useBuiltIns": "entry"
     }],
     "stage-2",
     "react"
 ],

useBuiltIns was added and by setting it to 'entry' , the import in the index.js file (import 'babel-polyfill' ) is actually changed to import whatever features need to be supported for your chosen browsers and environment. More information can be found here: https://github.com/babel/babel-preset-env#usebuiltins-entry

About


Languages

Language:JavaScript 93.2%Language:HTML 3.8%Language:CSS 3.0%