collectTotals in Magento 2

If we were to have a custom quote object which we were adding items to, we can re-calculate the totals in the cart using the following method.

$shippingAddress = $preorderQuote->getShippingAddress();
$shippingAddress->unsetData('cached_items_all');            
$quote->setTotalsCollectedFlag(false)->collectTotals();
$this->quoteRepository->save($quote);

We unset the cached item on the quote address object to force Magento to re-load the items for the address. This is necessary as Magento will sometimes not update this automatically, leading to an incorrect zero value quote and quote items.

Webpack – Assets

By default, Webpack only deals with javascript. For other filetypes, we need to add loaders to our config. We define these in the rules section of our config file, providing a regex for the test key for the files to be dealt with (typically file extension).

{
...
 rules: [
            {
                test: /\.css$/
                ...
            }
        ]
...
}

If we want to exclude files for any reason, we can add an exclude key, which is also a regex

 rules: [
            {
                test: /\.css$/,
                exclude: /file-not-to-be-processed.css/
                ...
            }
        ]  

CSS

Let’s add a CSS file to our project in src/css/style.css

body{
    background: red;
}

Now we need to include this file in our src/index.js file.

import './css/style.css';

In our webpack.config.js file, we can now add a definition for different filetypes which match on a filename regex:

const path = require('path');

module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            }
        ]
    }
};

And then install the css-loader and style-loader

npm install style-loader css-loader -D

When processing files, the use array is processed from top to bottom.
The CSS Loader loads CSS files, and deals with any assets within the file. Then the result is passed to the Style Loader which injects the CSS onto the page using a style tag.

In the example above, the the loaders are passed in order as strings in an array. If we need to configure the loaders with options, for example to enable source maps, we can use an object instead of a string, and provide an options key:

{
...
 rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'style-loader'
                    },
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true
                        }
                    }
                ]
            }
        ]
...
}

We will now be able to see files and line numbers when inspecting elements in the dev tools.

By default, the style-loader will create a separate style tag for each file being imported. To coalesce them into a single style tag, we can provide a singleton option key:

...
{
    loader: 'style-loader',
    options: {
        sourceMap: true,
        singleton: true
    }
}
...

Outputting CSS To A File

Instead of including all of the CSS in style tags, we can instead output them to a file. We’ll use the MiniCssExtractPlugin to achieve this.

npm install -D mini-css-extract-plugin

And then include it at the top of our config file

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

We can then need to use this as a plugin which will output our file

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

{
...
 plugins: [
        new MiniCssExtractPlugin({
            filename: "style.css"
        })
    ],
...
}

This also requires a loader to be added to the process chain. This plugin, will replace the style-loader which we used earlier.

{
...
    rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader
                    },
                    { 
                        loader: "css-loader"
                    }  
                ]
            }
        ]
...
}

We can now link to dist/styles.css from within our HTML file

<link rel="stylesheet" href="dist/style.css">

Dynamically Including CSS

In the example above, we have to hardcode our output CSS file within our HTML file. This would be cumbersome if we were using a hashed filename which would re-generate dynamically. We can achieve dynamic importing using the HTML Webpack Plugin

npm install -D html-webpack-plugin

Now we can remove any hardcoded script or CSS tags from our index.html, and move it to the ./src folder. Next, we can add the plugin definition

{
...
 plugins: [
        ...
        new MiniCssExtractPlugin({
            filename: "style.css",
        }),
        new HTMLWebpackPlugin({
            filename: '../index.html',
            template: './src/index.html'
        })
    ],
...
}

File Hashing

By default, the index.html file will be built in our dist folder, we’ll place this one level above in this example. We are now able to introduce file hashing to our application, as the CSS files are being injected dynamically. This means that if any of our file contents change, such as adding a new CSS rule, the filename will change and the user will always request the newest version.

There are two ways to do this, we can either use the html-webpack-plugin to append a querystring hash to the end of our static filenames, or we can use the mini-css-extract-plugin to create a hashed filename for our CSS. We can also use a combination of the two.

{
...
 plugins: [
        new CleanWebpackPlugin(['dist']),
        new MiniCssExtractPlugin({
            filename: '[contenthash].css'
        }),
        new HTMLWebpackPlugin({
            filename: '../index.html',
            template: './src/index.html',
            hash: true
        })
    ],
...
}

The above example will do both, using [contenthash] as the filename and hash: true in the HTMLWeboackPlugin.

Taking this further, we can now use contenthash when including our javascript file in Webpack’s config, so instead of using just main.js, we can use

{
...
    output: {
        filename: '[name].[contenthash].js'
        ...
    }
...
}

contenthash should be used when we need to know if a particular piece of content changes. Using chunkhash or hash will regenerate when content outside of our context changes. E.g. If we change CSS, then the JS’s file hash will also change.

It should be noted, that using HTMLWebpackPlugin’s hash option uses webpack’s hash for the build, which will change when the entire build changes. Therefore, when we’re using contenthash to generate filenames, we should turn off this option to avoid unnecessary downloads.

{
...
   plugins: [
        new HTMLWebpackPlugin({
            filename: '../index.html',
            hash: false
        })
   ]
...
}

Minifying CSS

All of the CSS we’ve been generating so far has been un-minified. To minify it for production, we can use the OptimiseCssAssetPlugin.

npm install -D optimize-css-assets-webpack-plugin

We can then use it by adding its plugin to our definition

{
   ...
    plugins: [
        new CleanWebpackPlugin(['dist'], { watch: false }),
        new MiniCssExtractPlugin({
            filename: "[contenthash].css",
        }),
        new HTMLWebpackPlugin({
            filename: '../index.html',
            hash: false,
            cache: true
        }),
        new OptimizeCssAssetsPlugin({})
    ],
   ...
}

Adding a HTML Template

By default, our index.html file will be generated automatically for us. Using the HTMLWebpackPlugin, we can specify a template file for the plugin to use which may contain our initial HTML structure. Create a file in src/index.html and then add a template key to our HTMLWebpackPlugin config

{
...
    plugins: {
    ...
        new HTMLWebpackPlugin({
            filename: '../index.html',
            template: './src/index.html',
            hash: false,
            cache: true
        }),
   ...
   }
...
}
<!doctype html>
<html>
 <head>
   <title>Getting Started</title>
 </head>
 <body>
    <header>HEADER</header>
 </body>
</html>

The CSS will now be added to the head of the template, and JS files will be added at the end of the HTML tag.

Sass

Sass compiled much the same way as standard CSS, but it uses two extra Webpack plugins to achieve it, Sass Loader, and Node Sass

npm install node-sass sass-loader -D

And then create a rule in our Webpack config

{
   ...
 rules: [
       ...
            {
                test: /\.scss$/,
                use: [
                    {
                        loader: MiniCssExtractPlugin.loader
                    },
                    {
                        loader: "css-loader"
                    },
                    {
                        loader: "sass-loader"
                    }
                ]
            }
      ...
     ]
}

We can then create a Sass file in our src directory, and import it in the same way as our previous CSS file. This will then be combined into our dist folder’s output CSS.

Images

Images in our CSS can be handled using the url-loader Webpack plugin.

npm install url-loader url-loader -D

And then add a loader rule to our config

test: /\.(png|svg|jpg|gif)$/,
use: {
    loader: 'url-loader',
    options: {
        name: '[path][name].[ext]',
        context: 'src',
        limit: 9000
    }
}

This will copy images used in our CSS or SASS and place them in the dist folder. The path folder by default will include our src directory, so specifying the context: src key, will tell url-loader not to include that path in our output directory. We can also specify a limit (in bytes), which will include any images under that size inline as a data-url in our application; this is used to limit server requests for small images.

This will work for images in CSS, however images imported using javascript will not have the correct path. To fix this, we can add a publicPath declaration to our Webpack output

module.exports = {
...
    output: {
        filename: '[name].[contenthash].js',
        path: path.resolve(__dirname, 'dist'),
        publicPath: '/dist/'
    },
...
}

We can now perform imports in JS files with the correct path names, such as the following

import image from './img/test.jpg';

const img = document.createElement('img');
img.src = image;
document.querySelector('body').appendChild(img);

ES 6

To transpile our ES6 Code such as arrow functions so that they work in browsers such as Internet Explorer, we can install Babel Loader.

npm install babel-loader @babel/core @babel/preset-env -D 

and then add a rule in our Webpack config

rules: [
    {
      test: /\.m?js$/,
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]