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. View Slide

  2. The modern JS developer's
    workflow

    View Slide

  3. View Slide

  4. The modern developer's webpack
    workflow

    View Slide

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

    View Slide

  6. View Slide

  7. 3. Copy and paste

    View Slide

  8. 4. It works, for now

    View Slide

  9. 5. But then you need to tweak it

    View Slide

  10. View Slide

  11. View Slide

  12. Boilerplates are great, once you
    know the tool

    View Slide

  13. View Slide

  14. It doesn't have to be
    like this

    View Slide

  15. Firstly: do you actually
    need webpack?

    View Slide

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

    View Slide

  17. What is webpack?

    View Slide

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

    View Slide

  19. 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 Slide

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

    View Slide

  21. View Slide

  22. webpack is not just for JavaScript

    View Slide

  23. View Slide

  24. View Slide

  25. View Slide

  26. webpack Terminology to tell your
    friends

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  31. View Slide

  32. Let's do it

    View Slide

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

    View Slide

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

    View Slide

  35. index.html


    Github


    View Slide

  36. 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 Slide

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

    View Slide

  38. 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 Slide

  39. 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 Slide

  40. View Slide

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

    View Slide

  42. 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 Slide

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

    View Slide

  44. 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 Slide

  45. View Slide

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

    View Slide

  47. 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 Slide

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

    View Slide

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

    View Slide

  50. webpack rules
    Apply transformations to certain files.

    View Slide

  51. {
    // 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 Slide

  52. {
    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 Slide

  53. 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 Slide

  54. Restart dev server

    View Slide

  55. View Slide

  56. View Slide

  57. npm install --save whatwg-fetch

    View Slide

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

    View Slide

  59. View Slide

  60. View Slide

  61. Let's make it a bit more interactive

    View Slide




  62. Go!




    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  66. View Slide

  67. 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 Slide

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

    View Slide

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

    View Slide

  70. 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 Slide

  71. 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 Slide

  72. 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 Slide

  73. View Slide

  74. View Slide

  75. View Slide

  76. View Slide

  77. View Slide

  78. View Slide

  79. View Slide

  80. View Slide

  81. View Slide

  82. Restart webpack-dev-server

    View Slide

  83. View Slide

  84. Deploying to Production

    View Slide

  85. Configuring webpack differently

    View Slide

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

    View Slide

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

    View Slide

  88. 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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

  92. View Slide

  93. View Slide

  94. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  99. View Slide

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

    View Slide

  101. Finally: code splitting / lazy
    loading

    View Slide

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

    View Slide

  103. 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 Slide

  104. 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 Slide

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

    View Slide

  106. View Slide

  107. 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 Slide

  108. View Slide

  109. And now we can build to
    production:

    View Slide

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

    View Slide

  111. 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 Slide

  112. View Slide

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

    View Slide

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

    View Slide

  115. 400kb first load saving

    View Slide

  116. View Slide

  117. View Slide

  118. View Slide

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

    View Slide

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

    View Slide

  121. 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 Slide

  122. 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 Slide

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

    View Slide

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

    View Slide

  125. I've barely scratched the surface.

    View Slide

  126. 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 Slide

  127. 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 Slide