I managed to hit 5 articles this week šŸ™Œ Iā€™m already finding a lot of value re-reading all of the blurbs on Fridays. This week I focused on Webpack and some Async/Await fun.

#1: A Beginnerā€™s Guide to Webpack 4 and Module Bundling - Part 1

Webpack: module bundler for JS but can be used for HTMl, CSS and images
- can give you more control over the number of HTTP requests your app is making
- can easily consume packages from nom
Can create an npm script for running webpack -> npm run develop (dev mode) or npm run build for production
- production is minified and file size is much smaller - optimizes for execution speed at runtime and output file size
- dev mode optimizes for build speed and debugging

ES Modules (split your program into many small, self-contained programs
- importing modules from npm donā€™t uses a relative path but all of your own imports do need a relative path

Tree Shaking
* in dev mode, webpack includes whole packages but in production mode all of the unused modules are removed from bundle

Loaders
* let you run preprocessors (eg. babel) on files as they are imported - allows you to bundle resources beyond js
* loaders can be chained to gather into a series of transforms

CSS in JS
import ā€˜./style.scssā€™
* needs the style-loader npm package which outputs the string tiny an embedded <style> tag
* Makes it easier to bundle assets together (images, css, HTML)
* dead code elimination: when the JS component is no longer loaded, the CSS file will also not be loaded
* css modules: local CSS with unique classes to avoid css overwriting
* this allows us to decrease HTTP requests by bundling/splitting code cleverly

Images (eg package: file-loader)
* store the source of images as strings inside js - preloads them and the browser doesnā€™t have to fetch them with separate requests later

import imageExample from ā€˜./image-exampleā€™
img.src = imageExample
  • this will include an image where the src attribute contains a dataURI of the image itself
  • background images in CSS are also processed by file-loader

#2: A Beginnerā€™s Guide to Webpack 4 and Module Bundling - Part 2

Dependency Graph
- loaders build a tree of dependencies among your assets and compiles single static assets

Code Splitting
- allows you to split code into bundles that can be loaded on demand or in parallel (faster load time)

button.onclick = () => {
  import(/* webpackChunkName: "chat" */ "./chat").then(chat => {
    chat.init()
  })
}

We can import the webpack chunk when we click the button (lazy loading)

Plugins
* loaders operate transform on single files, plugins operate across larger chunks of code
* help split code in clever ways and optimize for production
* webpack plugin "mode" lets us use dev and production specific plugins (eg: UglifyJsPlugin)

Production
* need a config file for dev, and prod (webpack.common.js, webpack.dev.js, webpack.prod.js - use common file in both dev and prod file)

Split CSS
* best practise is to split CSS from JS when bundling for production (use ExtractTextWebpackPlugin) and add the css file to your html as a link
* allows for parallel loading of JS and CSS so it will be faster

Generating HTML
* html-webpack-plugin updates our index.html file to reference new file paths

Development
* webpack-dev-server provides a simple web server with live reloading
* HotModuleReplacement swaps module at runtime without the refresh

HTTP/2
* webpack gives you control over how assets are fetched as well
* HTTP/2 allows multiple files to be delivered in a single request (so concatenation isnā€™t a silver bullet anymore)
* Could be more performant to cache several small files

#3: Code Splitting

Code splitting allows you to split code into various bundles to load them on demand or in parallel

3 Approaches:

  1. Entry Points - manually split code using entry configuration
  2. in the webpack.config.js file, list the modules by their js files
module.exports = {
    mode: ā€˜developmentā€™,
    entry: {    
        index: ā€˜./src/index.jsā€™,
        another: ā€˜./src/another-module.jsā€™
    }

Pitfalls:
* If there is duplicated modules between chunks, they will be included in both bundles
* Canā€™t be used to dynamically split code

  1. Prevent Duplication - use the SplitCHunks to deduce and split chunks
    • extract common dependencies into a separate chunk
optimization: {
    splitChunks: {
        chunks: ā€˜all
    }
}
  • other code splitting modules: mini-css-extract-plugin (split css out of main app), bundle-loader (split code and lazy load the resulting bundles), promise-loader (similar to bundle-loader but uses promises)

  • Dynamic Imports - Split code via inline function calls within modules

    • use import() syntax (uses promises so need the promise polyfill for older browsers)
      return import(/ webpackChunkName: "lodash" / 'lodash').then(_
    • webpackChunkNAme in the component will cause our separate bundle to be named loads.bundle.js
    • import() returns a promise so ti can be used with async functions

Prefetching/Preloading modules
Use inline directives when declaring imports allows webpack to output "Resource Hint" which tells the browser that for prefetch (probably needed for nav sometime in the future) and preload (resource might be deemed in the current nav)

import(/* webpackPrefetch: true */ 'LoginModal');

This creates: <link rel="prefetch" href="login-modal-chunk.js"> (browser will prefetch)

Prefetch: webpack will add the prefetch hint once the parent chunk has been loaded

Preload vs prefetch (browser support could be different!)
* preload chunk loads in parallel to parent and prefetch loads after parent
* preloaded has medium priority and is instantly downloaded, prefetch loads in browser idle time
* preloaded instantly requested by parent, prefetch is used anytime in the future

Bundle Analysis:
* useful to analyze code splits to check where modules end up (use like webpack-chart and webpack-visulizer or webpack-bundle-analyzer)

#4: Async Function

An async function operates asynchronously via the event loop - uses a promise to return its result

function doesTheWork() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(ā€˜resolvedā€™);
        }, 2000);
    });
}

async function asyncCall() {
    var result = await doesTheWork();
    console.log(result) // "resolved"
}

asyncCall()

Async functions can contain an await expression that pauses the execution of the async function and waits for the passed Promiseā€™s resolution, and then resumes the async functionā€™s execution

The await keyword is only valid inside async functions - you will get a SyntaxError otherwise

Options:

//sequential start - takes 2 + 1 seconds

async function() {
    const slow = await resolveAfter2Seconds(); 

    // if the value of the expression following the 
   //await operator is not a Promise, itā€™s converted to a resolved Promise
    const fast = await resolveAfter1Second();
}

//concurrent start - takes 2 seconds total - this can also be done with Promise.all (might be better if you have
//to wait for more than 2 promises 

async function() {
    const slow = resolveAfter2Seconds();
    const fast = resolveAfter1Second();

    console.log(await slow);
    console.log(await fast); // doesnā€™t log until slow is done!
}

The async function is implicitly wrapped in Promise.resolve so you donā€™t have to put an await on the return statement in an async function

#5: Async/await

async: a function will always return a promise (js will automatically wrap the return statement in a promise
await: only works inside the async function - makes js wait until that promise is resolved, and then evaluates the expression

  • the waiting doesnā€™t cost CPU resources because the engine can do other jobs in the meanwhile
  • similar to promise.then but easier to read/write
// wait 3 seconds
await new Promise((resolve, reject) => setTimeout(resolve, 3000));

await accepts thenables (any function that 

class methods can also be async:
class Waiter {
    async wait() {
        return await Promise.resolve(1);
    }
}

new Waiter().wait().then(alert); //1

Error Handling:
- await Promise.reject(new Error("Whoops!"))
- await waits for the promise to resolve then throws the error
- Can use in a try/catch as per usual
- if not in a regular try/catch, the promise is rejected and we can do something like: f().catch(alert);