Pro Yearly is on sale from $80 to $50! »

Half Stack Fest: Webpack

Aea964cf59c0c81fff752896f070cbbb?s=47 Jack Franklin
November 18, 2016

Half Stack Fest: Webpack

Aea964cf59c0c81fff752896f070cbbb?s=128

Jack Franklin

November 18, 2016
Tweet

Transcript

  1. None
  2. The modern JS developer's workflow

  3. None
  4. The modern developer's webpack workflow

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

  6. None
  7. 3. Copy and paste

  8. 4. It works, for now

  9. 5. But then you need to tweak it

  10. None
  11. None
  12. Boilerplates are great, once you know the tool

  13. None
  14. It doesn't have to be like this

  15. Firstly: do you actually need webpack?

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

  17. What is webpack?

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

  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
  20. webpack can do this right at the beginning And create

    a more optimised bundle, saving the browser (and user) time
  21. None
  22. webpack is not just for JavaScript

  23. None
  24. None
  25. None
  26. webpack Terminology to tell your friends

  27. A module Any file that your application uses. Not just

    JavaScript. CSS, images, text files, anything.
  28. Chunk A file, or a group of files that webpack

    has squashed together
  29. Entry point A file webpack will search from to find

    dependencies and modules
  30. A loader A function that takes a file's source and

    transforms it to return a new source file
  31. None
  32. Let's do it

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

  34. npm install --save-dev webpack@2.1.0-beta.26 Run locally with ./node_modules/.bin/webpack (Or add

    ./node_modules/.bin to your $PATH)
  35. index.html <!DOCTYPE html> <html> <head><title>Github</title></head> <body><script src="dist/main.js"></script></body> </html>

  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) })
  37. Configuring webpack » Where should webpack start looking? » Where

    should it output to?
  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/' } }
  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]
  40. None
  41. Watching for changes webpack --watch "scripts": { "build:dev": "webpack --watch"

    }
  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]
  43. Refreshing is so much effort npm install --save-dev webpack-dev-server@v2.1.0- beta.11

  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] ...
  45. None
  46. "scripts": { "build:dev": "webpack --watch", "start": "webpack-dev-server" },

  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))
  48. Babel npm install --save-dev babel-core npm install --save-dev babel-preset-es2015 npm

    install --save-dev babel-loader
  49. webpack.config.js module.exports = { entry: path.resolve('src', 'main.js'), output: { ...

    }, module: { rules: [ ] } }
  50. webpack rules Apply transformations to certain files.

  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: [{ }] }
  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'] } }] }
  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))
  54. Restart dev server

  55. None
  56. None
  57. npm install --save whatwg-fetch

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

    },
  59. None
  60. None
  61. Let's make it a bit more interactive

  62. <div id="app"> <form id="github-form"> <input type="text" id="input-box" value="jackfranklin" /> <button

    type="submit">Go!</button> </form> <div id="results"></div> </div> <script src="dist/main.js"></script>
  63. const form = document.getElementById('github-form') form.addEventListener('submit', e => { e.preventDefault() const

    username = document.getElementById('input-box').value fetchPerson(username) })
  64. const fetchPerson = username => fetch(`https://api.github.com/users/${username}`) .then(d => d.json()) .then(displayPerson)

  65. const displayPerson = user => document.getElementById('results').innerHTML = ` <h1>${user.name}</h1> <h3>${user.company}</h3>

    <p>${user.bio || 'No Bio :('}</p> `
  66. None
  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)
  68. form { width: 200px; margin: 10px auto; } #results {

    width: 300px; margin: 0 auto; border: 1px solid #111; background: #ddd; }
  69. import './style.css' const username = 'jackfranklin' ...

  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
  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
  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
  73. None
  74. None
  75. None
  76. None
  77. None
  78. None
  79. None
  80. None
  81. None
  82. Restart webpack-dev-server

  83. None
  84. Deploying to Production

  85. Configuring webpack differently

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

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

  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'
  89. Starting point npm run build:prod - main.js: 24.9kb (All CSS

    is contained within main.js and dynamically inserted)
  90. Plugins A webpack plugin will typically work on the bundle

    as a whole, rather than on individual files.
  91. Minifying var webpack = require('webpack') ... output: { ... },

    plugins: removeEmpty([ ifProduction(new webpack.optimize.UglifyJsPlugin()) ]), module: { ... }
  92. None
  93. None
  94. None
  95. npm run build:prod - main.js: 11.4kb

  96. Nice, but why have the CSS injected with JavaScript? npm

    install --save-dev extract-text-webpack-plugin@v2.0.0-beta.4
  97. var ExtractTextPlugin = require('extract-text-webpack-plugin') ... plugins: removeEmpty([ ifProduction(new webpack.optimize.UglifyJsPlugin()), new

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

  99. None
  100. Deep breaths, lots of progress! npm run build:prod - main.js:

    7.46kb - style.css: 140bytes
  101. Finally: code splitting / lazy loading

  102. We should keep our first page load super quick »

    download JS » parse JS » execute JS This takes time.
  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 = ` <h1>${user.name}</h1> <h3>${user.company}</h3> <p>${user.bio || 'No Bio :('}</p> `
  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) })
  105. We only need fetchPerson if the user clicks the button

  106. None
  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)) })
  108. None
  109. And now we can build to production:

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

    style.css: 140bytes
  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.
  112. None
  113. No Lazy loading bundle.app.193adde67586dd61304b.js 444 kB

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

    kB
  115. 400kb first load saving

  116. None
  117. None
  118. None
  119. Lazy loading: not a silver bullet But when you do

    want it, webpack makes it easy :)
  120. Bonus: dead code elimination webpack 2 can parse ES2015 modules,

    that is: import { x } from './y' export default function foo() {...}
  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!
  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)
  123. Stop Babel converting modules Stop Babel converting: use: [{ loader:

    'babel-loader', options: { presets: [['es2015', { modules: false }]] } }]
  124. webpack 2 + ES2015 modules = smaller builds, for free!

  125. I've barely scratched the surface.

  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
  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