Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Half Stack Fest: Webpack

Jack Franklin
November 18, 2016

Half Stack Fest: Webpack

Jack Franklin

November 18, 2016
Tweet

More Decks by Jack Franklin

Other Decks in Technology

Transcript

  1. The modern JS developer's
    workflow

    View full-size slide

  2. The modern developer's webpack
    workflow

    View full-size slide

  3. 1. Google: "how to do X in
    webpack"

    View full-size slide

  4. 3. Copy and paste

    View full-size slide

  5. 4. It works, for now

    View full-size slide

  6. 5. But then you need to tweak it

    View full-size slide

  7. Boilerplates are great, once you
    know the tool

    View full-size slide

  8. It doesn't have to be
    like this

    View full-size slide

  9. Firstly: do you actually
    need webpack?

    View full-size slide

  10. Glen Madern: https://frontend.center/

    View full-size slide

  11. What is webpack?

    View full-size slide

  12. "An ahead of time compiler for
    the browser"

    View full-size slide

  13. webpack does what the browser would do
    if given your app:
    » find all images in your CSS and download them
    » parse your JavaScript dependencies and download them
    » parse all CSS for imports and download them

    View full-size slide

  14. webpack can do this right at the beginning
    And create a more optimised bundle, saving the browser (and
    user) time

    View full-size slide

  15. webpack is not just for JavaScript

    View full-size slide

  16. webpack Terminology to tell your
    friends

    View full-size slide

  17. A module
    Any file that your application uses. Not just JavaScript. CSS,
    images, text files, anything.

    View full-size slide

  18. Chunk
    A file, or a group of files that webpack has squashed together

    View full-size slide

  19. Entry point
    A file webpack will search from to find dependencies and
    modules

    View full-size slide

  20. A loader
    A function that takes a file's source and transforms it to return a
    new source file

    View full-size slide

  21. npm init -y
    PSA: Don't install things globally!

    View full-size slide

  22. npm install --save-dev [email protected]
    Run locally with ./node_modules/.bin/webpack
    (Or add ./node_modules/.bin to your $PATH)

    View full-size slide

  23. index.html


    Github


    View full-size slide

  24. src/main.js
    fetch('https://api.github.com/users/jackfranklin')
    .then(function(d) { return d.json() })
    .then(function(data) {
    console.log('Got github data', data)
    })

    View full-size slide

  25. Configuring webpack
    » Where should webpack start looking?
    » Where should it output to?

    View full-size slide

  26. webpack.config.js
    var path = require('path')
    module.exports = {
    // where should it look?
    entry: path.resolve('src', 'main.js'),
    // where should it output to?
    output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.js',
    publicPath: '/dist/'
    }
    }

    View full-size slide

  27. run webpack
    Hash: c09c87fc891968a374a6
    Version: webpack 2.1.0-beta.26
    Time: 70ms
    Asset Size Chunks Chunk Names
    main.js 2.63 kB 0 [emitted] main
    [0] ./src/main.js 194 bytes {0} [built]

    View full-size slide

  28. Watching for changes
    webpack --watch
    "scripts": {
    "build:dev": "webpack --watch"
    }

    View full-size slide

  29. webpack is watching the files…
    Hash: c09c87fc891968a374a6
    Version: webpack 2.1.0-beta.26
    Time: 103ms
    Asset Size Chunks Chunk Names
    main.js 2.63 kB 0 [emitted] main
    [0] ./src/main.js 194 bytes {0} [built]
    Hash: d9aad0a1ca8f706975ad
    Version: webpack 2.1.0-beta.26
    Time: 11ms
    Asset Size Chunks Chunk Names
    main.js 2.6 kB 0 [emitted] main
    [0] ./src/main.js 163 bytes {0} [built]

    View full-size slide

  30. Refreshing is so much effort
    npm install --save-dev [email protected]
    beta.11

    View full-size slide

  31. webpack-dev-server
    Project is running at http://localhost:8080/
    webpack output is served from /dist/
    Hash: 1d366a627611ba06845c
    Version: webpack 2.1.0-beta.26
    Time: 947ms
    Asset Size Chunks Chunk Names
    main.js 247 kB 0 [emitted] main
    chunk {0} main.js (main) 233 kB [entry] [rendered]
    [0] ./~/inherits/inherits_browser.js 672 bytes {0} [built]
    [1] (webpack)/buildin/global.js 506 bytes {0} [built]
    [2] ./~/debug/browser.js 3.76 kB {0} [built]
    [3] ./~/process/browser.js 5.3 kB {0} [built]
    ...

    View full-size slide

  32. "scripts": {
    "build:dev": "webpack --watch",
    "start": "webpack-dev-server"
    },

    View full-size slide

  33. Loaders
    fetch('https://api.github.com/users/jackfranklin')
    .then(function(d) { return d.json() })
    .then(function(data) {
    console.log('Got github data', data)
    })
    Would be much nicer as:
    const username = 'jackfranklin'
    fetch(`https://api.github.com/users/${username}`)
    .then(d => d.json())
    .then(data => console.log('Got github data', data))

    View full-size slide

  34. Babel
    npm install --save-dev babel-core
    npm install --save-dev babel-preset-es2015
    npm install --save-dev babel-loader

    View full-size slide

  35. webpack.config.js
    module.exports = {
    entry: path.resolve('src', 'main.js'),
    output: { ... },
    module: {
    rules: [
    ]
    }
    }

    View full-size slide

  36. webpack rules
    Apply transformations to certain files.

    View full-size slide

  37. {
    // apply this rule to any files that end in ".js"
    test: /\.js$/,
    // only look for files in the src directory
    include: path.resolve('src'),
    // configure the loaders for this rule
    use: [{
    }]
    }

    View full-size slide

  38. {
    test: /\.js$/,
    include: path.resolve('src'),
    use: [{
    // for files that this rule applies to
    // run the babel-loader against them
    loader: 'babel-loader',
    // specific options for the babel loader
    options: {
    presets: ['es2015']
    }
    }]
    }

    View full-size slide

  39. src/main.js
    const username = 'jackfranklin'
    fetch(`https://api.github.com/users/${username}`)
    .then(d => d.json())
    .then(d => console.log('Got Github data', d))

    View full-size slide

  40. Restart dev server

    View full-size slide

  41. npm install --save whatwg-fetch

    View full-size slide

  42. Entry points
    entry: {
    main: [
    'whatwg-fetch',
    path.resolve('src', 'main.js')
    ]
    },

    View full-size slide

  43. Let's make it a bit more interactive

    View full-size slide

  44. const form = document.getElementById('github-form')
    form.addEventListener('submit', e => {
    e.preventDefault()
    const username = document.getElementById('input-box').value
    fetchPerson(username)
    })

    View full-size slide

  45. const fetchPerson = username =>
    fetch(`https://api.github.com/users/${username}`)
    .then(d => d.json())
    .then(displayPerson)

    View full-size slide

  46. const displayPerson = user =>
    document.getElementById('results').innerHTML = `
    ${user.name}
    ${user.company}
    ${user.bio || 'No Bio :('}
    `

    View full-size slide

  47. Let's get some CSS in here
    Remember, the goal of webpack is for it to manage all our assets,
    CSS included.
    This is weird at first but stick with me...
    (Also I suck at design, please forgive)

    View full-size slide

  48. form {
    width: 200px;
    margin: 10px auto;
    }
    #results {
    width: 300px;
    margin: 0 auto;
    border: 1px solid #111;
    background: #ddd;
    }

    View full-size slide

  49. import './style.css'
    const username = 'jackfranklin'
    ...

    View full-size slide

  50. ERROR in ./src/style.css
    Module parse failed: /github-app/src/style.css Unexpected token (1:5)
    You may need an appropriate loader to handle this file type.
    | form {
    | width: 200px;
    | margin: 10px auto;
    @ ./src/main.js 3:0-22
    @ multi main

    View full-size slide

  51. npm install --save-dev css-loader
    npm install --save-dev style-loader
    » CSS Loader: parses CSS files
    » Style Loader: dynamically inserts stylesheets into HTML

    View full-size slide

  52. Add another rule
    {
    test: /\.css$/,
    include: path.resolve('src'),
    use: [{
    loader: 'style-loader',
    }, {
    loader: 'css-loader',
    }]
    }
    Loaders are applied from right to left, or bottom to top

    View full-size slide

  53. Restart webpack-dev-server

    View full-size slide

  54. Deploying to Production

    View full-size slide

  55. Configuring webpack differently

    View full-size slide

  56. "scripts": {
    "build:dev": "webpack --watch",
    "build:prod": "NODE_ENV=production webpack",
    "start": "webpack-dev-server"
    },

    View full-size slide

  57. npm install --save-dev webpack-
    config-utils

    View full-size slide

  58. var { getIfUtils, removeEmpty } = require('webpack-config-utils')
    var {
    ifProduction,
    ifNotProduction
    } = getIfUtils(process.env.NODE_ENV || 'development')
    » removeEmpty: removes undefined from arrays
    » ifProduction: returns what it's given if NODE_ENV ===
    'production'
    » ifNotProduction: returns what it's given if NODE_ENV !==
    'production'

    View full-size slide

  59. Starting point
    npm run build:prod
    - main.js: 24.9kb
    (All CSS is contained within main.js and dynamically inserted)

    View full-size slide

  60. Plugins
    A webpack plugin will typically work on the bundle as a whole,
    rather than on individual files.

    View full-size slide

  61. Minifying
    var webpack = require('webpack')
    ...
    output: { ... },
    plugins: removeEmpty([
    ifProduction(new webpack.optimize.UglifyJsPlugin())
    ]),
    module: { ... }

    View full-size slide

  62. npm run build:prod
    - main.js: 11.4kb

    View full-size slide

  63. Nice, but why have the CSS injected with
    JavaScript?
    npm install --save-dev [email protected]

    View full-size slide

  64. var ExtractTextPlugin = require('extract-text-webpack-plugin')
    ...
    plugins: removeEmpty([
    ifProduction(new webpack.optimize.UglifyJsPlugin()),
    new ExtractTextPlugin('style.css'),
    ]),

    View full-size slide

  65. {
    test: /\.css$/,
    include: path.resolve('src'),
    loader: ExtractTextPlugin.extract('css-loader'),
    }

    View full-size slide

  66. Deep breaths, lots of progress!
    npm run build:prod
    - main.js: 7.46kb
    - style.css: 140bytes

    View full-size slide

  67. Finally: code splitting / lazy
    loading

    View full-size slide

  68. We should keep our first page load super
    quick
    » download JS
    » parse JS
    » execute JS
    This takes time.

    View full-size slide

  69. src/fetch-person.js
    export const fetchPerson = username =>
    fetch(`https://api.github.com/users/${username}`)
    .then(d => d.json())
    .then(displayPerson)
    const displayPerson = user =>
    document.getElementById('results').innerHTML = `
    ${user.name}
    ${user.company}
    ${user.bio || 'No Bio :('}
    `

    View full-size slide

  70. import './style.css'
    import { fetchPerson } from './fetch-person'
    const form = document.getElementById('github-form')
    form.addEventListener('submit', e => {
    e.preventDefault()
    const username = document.getElementById('input-box').value
    fetchPerson(username)
    })

    View full-size slide

  71. We only need fetchPerson if the
    user clicks the button

    View full-size slide

  72. import './style.css'
    const form = document.getElementById('github-form')
    form.addEventListener('submit', e => {
    e.preventDefault()
    const username = document.getElementById('input-box').value
    System.import('./fetch-person')
    .then(module => module.fetchPerson)
    .then(fetchPerson => fetchPerson(username))
    })

    View full-size slide

  73. And now we can build to
    production:

    View full-size slide

  74. npm run build:prod
    - main.js: 8.08kb
    - 0.main.js: 387bytes
    - style.css: 140bytes

    View full-size slide

  75. Wait, the build went up?
    This is a contrived example, because this app is tiny!
    You pay a small cost because webpack has code that it inserts for
    lazily loading modules, but if your pages are big enough you'll get
    still save.

    View full-size slide

  76. No Lazy loading
    bundle.app.193adde67586dd61304b.js 444 kB

    View full-size slide

  77. Lazily loading the graph component
    bundle.0.513a048a93c5b8f1687b.js 431 kB
    bundle.app.ce8bc2ebcec6edbff6a1.js 13.1 kB

    View full-size slide

  78. 400kb first load saving

    View full-size slide

  79. Lazy loading: not a silver bullet
    But when you do want it, webpack makes it easy :)

    View full-size slide

  80. Bonus: dead code elimination
    webpack 2 can parse ES2015 modules, that is:
    import { x } from './y'
    export default function foo() {...}

    View full-size slide

  81. ES2015 module imports and exports have to be static.
    So we can go through them and see which ones are used and
    which ones aren't needed.
    This means we can eliminate any code relating to ununsed
    exports!

    View full-size slide

  82. src/not-used.js
    export const SO_NOT_USED = 'I AM NOT USED BY ANYTHING EVER'
    export const SO_USED = 'I GET USED BY THINGS'
    src/main.js
    import { SO_USED } from './not-used'
    console.log(SO_USED)

    View full-size slide

  83. Stop Babel converting modules
    Stop Babel converting:
    use: [{
    loader: 'babel-loader',
    options: {
    presets: [['es2015', { modules: false }]]
    }
    }]

    View full-size slide

  84. webpack 2 + ES2015 modules =
    smaller builds, for free!

    View full-size slide

  85. I've barely scratched the surface.

    View full-size slide

  86. webpack 2
    » Much improved documentation & community engagement
    » Improved configuration with a nicer API and automatic
    validation of config
    » Tree shaking, easier code splitting and lazy loading
    » More performant

    View full-size slide

  87. Fin
    » Slides & code: https://github.com/jackfranklin/half-stack-
    webpack
    » webpack 2: webpack.js.org
    » Me: @Jack_Franklin, javascriptplayground.com,
    elmplayground.com
    Thanks to: Glen Maddern, Sean Larkinn, Kent C Dodds

    View full-size slide