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

A Webpack Survival Guide for Rails Developers

A Webpack Survival Guide for Rails Developers

Moving from Webpack basics to using it in production means understanding how it works. We'll demystify how Webpack bundles assets for the browser, how it differs from the Rails asset pipeline, and highlight common challenges that may occur coming from Sprockets.

This is the talk we would have wanted to see before recently adopting Webpack in our own Rails app.

B0169a78f851962058d63337ad0147d6?s=128

Ross Kaffenberger

April 10, 2018
Tweet

Transcript

  1. LOOK BEFORE YOU import ROSS KAFFENBERGER @rossta WEBPACK FOR RAILS

    DEVELOPERS SURV I VA L G UI D E A
  2. None
  3. ROSS KAFFENBERGER @rossta WEBPACK WIT HOUT LOSIN G YOUR HAIR

    HOW TO
  4. None
  5. None
  6. ROSS

  7. R O S S uby pen ource oftware

  8. ROSS conf

  9. learnzillion.com/careers

  10. None
  11. I really mean:

  12. None
  13. #poop

  14. #poop

  15. None
  16. Last year…

  17. None
  18. None
  19. 3 scenarios

  20. 3 scenarios 6 problems

  21. 3 scenarios 6 problems 7 survival tips

  22. First…

  23. Sprockets Webpack

  24. Sprockets Webpack Packaging assets for the browser

  25. Sprockets Webpack

  26. Sprockets Webpack File concatenation

  27. Sprockets Webpack File concatenation Static module bundling

  28. None
  29. Better dev tools

  30. Better dev tools Better dependency management

  31. Better dev tools Better dependency management Huge ecosystem

  32. Better dev tools Better dependency management Huge ecosystem ES6*

  33. Better dev tools Better dependency management Huge ecosystem ES6* Dynamic

    code splitting
  34. None
  35. None
  36. None
  37. None
  38. https://webpack.wtf

  39. •Webpack WTF? •screenshots •quotes

  40. None
  41. Webpack tutorials SurviveJS Webpack Book - Juho Vepsäläinen survivejs.com/webpack Webpack

    Learning Academy - Sean Thomas Larkin webpack.academy Webpack from Nothing - David Bryant Copeland what-problem-does-it-solve.com/webpack/index.html
  42. SURVIVAL SCENARIOS

  43. SURVIVAL SCENARIOS ORGANIZING CODE

  44. SURVIVAL SCENARIOS ORGANIZING CODE TAMING DEPENDENCIES

  45. SURVIVAL SCENARIOS ORGANIZING CODE TAMING DEPENDENCIES PREDICTABLE CACHING

  46. SURVIVAL SCENARIOS >ORGANIZING CODE TAMING DEPENDENCIES PREDICTABLE CACHING

  47. Organizing code Directory structure

  48. sprockets

  49. sprockets Organizing code

  50. sprockets Organizing code app/ assets/ javascripts/ application.js admin.js my_function.js my_module.js

    components/ component_a.js component_b.js
  51. sprockets Organizing code app/ assets/ javascripts/ application.js admin.js my_function.js my_module.js

    components/ component_a.js component_b.js public/ assets/ javascripts/ application-abc123...js admin-def456...jss
  52. sprockets Organizing code app/ assets/ javascripts/ application.js admin.js my_function.js my_module.js

    components/ component_a.js component_b.js public/ assets/ javascripts/ application-abc123...js admin-def456...jss Rails.application.configure do config.assets.precompile += %w(admin.js) end
  53. webpacker+

  54. webpacker+ Organizing code app/ assets/ javascripts/ application.js admin.js my_function.js my_module.js

    components/ component_a.js component_b.js
  55. webpacker+ app/ webpacker+ assets/ javascripts/ my_function.js my_module.js components/ component_a.js component_b.js

    application.js admin.js ... javascript/ packs/ Organizing code
  56. webpacker+ app/ webpacker+ entry assets/ javascripts/ my_function.js my_module.js components/ component_a.js

    component_b.js application.js admin.js ... javascript/ packs/ Organizing code
  57. webpacker+ app/ public/ assets/ javascripts/ ... packs/ application-abc123...js admin-def456...js webpacker+

    entry assets/ javascripts/ my_function.js my_module.js components/ component_a.js component_b.js application.js admin.js ... javascript/ packs/ Organizing code
  58. webpacker+ app/ public/ assets/ javascripts/ ... packs/ application-abc123...js admin-def456...js webpacker+

    output entry assets/ javascripts/ my_function.js my_module.js components/ component_a.js component_b.js application.js admin.js ... javascript/ packs/ Organizing code
  59. webpacker+ app/ public/ assets/ javascripts/ ... packs/ application-abc123...js admin-def456...js webpacker+

    output entry “packs” auto-configured assets/ javascripts/ my_function.js my_module.js components/ component_a.js component_b.js application.js admin.js ... javascript/ packs/ Organizing code
  60. Organizing code

  61. Organizing code

  62. Organizing code

  63. Organizing code

  64. Organizing code

  65. webpacker+ app/ webpacker+ assets/ javascripts/ my_function.js my_module.js components/ component_a.js component_b.js

    application.js admin.js ... javascript/ packs/ Organizing code
  66. Organizing code webpacker+ app/ assets/ javascripts/ my_function.js my_module.js components/ component_a.js

    component_b.js application.js admin.js ... javascript/ packs/
  67. Organizing code webpacker+ app/ assets/ javascripts/ my_function.js my_module.js components/ component_a.js

    component_b.js application.js admin.js ... javascript/ packs/
  68. Organizing code webpacker+ $ bin/webpack

  69. Organizing code webpacker+ $ bin/webpack

  70. Organizing code webpacker+ $ bin/webpack

  71. Organizing code webpacker+ $ bin/webpack

  72. Quick fix Keep entry js separate

  73. webpacker+ app/ assets/ javascripts/ ... javascript/ my_function.js my_module.js components/ component_a.js

    component_b.js packs/ application.js admin.js public/ assets/ javascripts/ ... packs/ application-abc123...js admin-def456...js Organizing code webpacker+ ✅
  74. Organizing code webpacker+

  75. Organizing code webpacker+

  76. Survival tip #1 Don’t assume Webpacker conventions match Sprockets conventions

  77. Organizing code How to “require_tree”?

  78. sprockets Organizing code // app/assets/javascripts/application.js //= require_tree ./some_directory

  79. webpacker+ Organizing code // app/javascript/packs/application.js import 'some_directory'

  80. webpacker+ require.context Organizing code // app/javascript/packs/application.js import 'some_directory'

  81. webpacker+ require.context Organizing code // app/javascript/some_directory/index.js const requireModule = require.context('.',

    true, /\.js$/) // app/javascript/packs/application.js import 'some_directory'
  82. webpacker+ require.context Organizing code // app/javascript/some_directory/index.js const requireModule = require.context('.',

    true, /\.js$/) // app/javascript/packs/application.js import 'some_directory' requireModule.keys() // ['./module_a.js', ‘./module_b.js', ...] .filter(filename => filename !== './index.js') .forEach(requireModule)
  83. webpacker+ Organizing code // app/javascript/packs/application.js import { SomeComponent } from

    'some_directory'
  84. webpacker+ Organizing code // app/javascript/some_directory/index.js import camelCase from 'lodash/camelCase' const

    requireModule = require.context('.', false, /\.js$/) const modules = {} requireModule.keys().forEach(fileName => { if (fileName === './index.js') return const moduleName = camelCase( fileName.replace(/(\.\/|\.js)/g, '') ) modules[moduleName] = requireModule(fileName) }) export default modules
  85. webpacker+ Organizing code // app/javascript/some_directory/index.js import camelCase from 'lodash/camelCase' const

    requireModule = require.context('.', false, /\.js$/) const modules = {} requireModule.keys().forEach(fileName => { if (fileName === './index.js') return const moduleName = camelCase( fileName.replace(/(\.\/|\.js)/g, '') ) modules[moduleName] = requireModule(fileName) }) export default modules
  86. webpacker+ Organizing code // app/javascript/some_directory/index.js import camelCase from 'lodash/camelCase' const

    requireModule = require.context('.', false, /\.js$/) const modules = {} requireModule.keys().forEach(fileName => { if (fileName === './index.js') return const moduleName = camelCase( fileName.replace(/(\.\/|\.js)/g, '') ) modules[moduleName] = requireModule(fileName) }) export default modules
  87. webpacker+ Organizing code // app/javascript/some_directory/index.js import camelCase from 'lodash/camelCase' const

    requireModule = require.context('.', false, /\.js$/) const modules = {} requireModule.keys().forEach(fileName => { if (fileName === './index.js') return const moduleName = camelCase( fileName.replace(/(\.\/|\.js)/g, '') ) modules[moduleName] = requireModule(fileName) }) export default modules
  88. webpacker+ Organizing code // app/javascript/some_directory/index.js import camelCase from 'lodash/camelCase' const

    requireModule = require.context('.', false, /\.js$/) const modules = {} requireModule.keys().forEach(fileName => { if (fileName === './index.js') return const moduleName = camelCase( fileName.replace(/(\.\/|\.js)/g, '') ) modules[moduleName] = requireModule(fileName) }) export default modules
  89. None
  90. None
  91. Survival tip #2 In Webpack, accept control > friendliness

  92. SURVIVAL SCENARIOS ORGANIZING CODE✔ >TAMING DEPENDENCIES PREDICTABLE CACHING

  93. Taming dependencies Code splitting

  94. sprockets Taming dependencies app/ assets/ javascripts/ application.js vendor.js sprockets

  95. sprockets Taming dependencies app/ assets/ javascripts/ application.js vendor.js sprockets //

    app/views/layouts/application.html.erb <%= javascript_include_tag "vendor" %> <%= javascript_include_tag "application" %>
  96. sprockets Taming dependencies app/ assets/ javascripts/ application.js vendor.js sprockets //

    app/assets/javascripts/vendor.js //= require jquery-rails // app/views/layouts/application.html.erb <%= javascript_include_tag "vendor" %> <%= javascript_include_tag "application" %>
  97. sprockets Taming dependencies app/ assets/ javascripts/ application.js vendor.js sprockets //

    app/assets/javascripts/vendor.js //= require jquery-rails // app/assets/javascripts/application.js //= require chosen-js //= require slick-carousel // app/views/layouts/application.html.erb <%= javascript_include_tag "vendor" %> <%= javascript_include_tag "application" %>
  98. sprockets Taming dependencies app/ assets/ javascripts/ application.js vendor.js sprockets //

    app/assets/javascripts/vendor.js //= require jquery-rails // app/assets/javascripts/application.js //= require chosen-js //= require slick-carousel manual code splitting // app/views/layouts/application.html.erb <%= javascript_include_tag "vendor" %> <%= javascript_include_tag "application" %>
  99. webpacker+ Taming dependencies app/ javascript/ packs/ application.js vendor.js

  100. webpacker+ Taming dependencies app/ javascript/ packs/ application.js vendor.js // app/views/layouts/application.html.erb

    <%= javascript_pack_tag "vendor" %> <%= javascript_pack_tag "application" %>
  101. webpacker+ Taming dependencies app/ javascript/ packs/ application.js vendor.js // app/javascript/packs/vendor.js

    import 'jquery' // app/views/layouts/application.html.erb <%= javascript_pack_tag "vendor" %> <%= javascript_pack_tag "application" %>
  102. webpacker+ Taming dependencies app/ javascript/ packs/ application.js vendor.js // app/javascript/packs/vendor.js

    import 'jquery' // app/javascript/packs/application.js import 'chosen-js' import 'slick-carousel' // app/views/layouts/application.html.erb <%= javascript_pack_tag "vendor" %> <%= javascript_pack_tag "application" %>
  103. webpacker+ Taming dependencies app/ javascript/ packs/ application.js vendor.js // app/javascript/packs/vendor.js

    import 'jquery' // app/javascript/packs/application.js import 'chosen-js' import 'slick-carousel' // app/views/layouts/application.html.erb <%= javascript_pack_tag "vendor" %> <%= javascript_pack_tag "application" %> We added “$” to global scope
  104. webpacker+ Taming dependencies // app/javascript/packs/vendor.js import 'jquery'

  105. webpacker+ Taming dependencies // app/javascript/packs/application.js // app/javascript/packs/vendor.js import 'jquery'

  106. webpacker+ Taming dependencies // app/javascript/packs/application.js // app/javascript/packs/application.js import 'chosen-js' //

    app/javascript/packs/vendor.js import 'jquery'
  107. webpacker+ > typeof $.fn.chosen “function" Taming dependencies // app/javascript/packs/application.js //

    app/javascript/packs/application.js import 'chosen-js' // app/javascript/packs/vendor.js import 'jquery'
  108. webpacker+ > typeof $.fn.chosen “function" Taming dependencies // app/javascript/packs/application.js //

    app/javascript/packs/application.js import 'chosen-js' // app/javascript/packs/application.js import 'chosen-js' import 'slick-carousel' // app/javascript/packs/vendor.js import 'jquery'
  109. webpacker+ > typeof $.fn.chosen “function" > typeof $.fn.chosen “function" >

    typeof $.fn.slick "function" Taming dependencies // app/javascript/packs/application.js // app/javascript/packs/application.js import 'chosen-js' // app/javascript/packs/application.js import 'chosen-js' import 'slick-carousel' // app/javascript/packs/vendor.js import 'jquery'
  110. webpacker+ > typeof $.fn.chosen “function" > typeof $.fn.chosen “function" >

    typeof $.fn.slick "function" Taming dependencies // app/javascript/packs/application.js // app/javascript/packs/application.js import 'chosen-js' // app/javascript/packs/application.js import 'chosen-js' import 'slick-carousel' > typeof $.fn.chosen "function" > typeof $.fn.slick "function" > typeof $.fn.chosen "undefined" // app/javascript/packs/vendor.js import 'jquery'
  111. None
  112. None
  113. webpacker+ Taming dependencies $ yarn add webpack-bundle-analyzer

  114. webpacker+ Taming dependencies // config/webpack/development.js const environment = require('./environment') const

    BundleAnalyzerPlugin = require(‘webpack-bundle-analyzer').BundleAnalyzerPlugin environment.plugins.append( 'BundleAnalyzerPlugin', new BundleAnalyzerPlugin() ) $ yarn add webpack-bundle-analyzer
  115. webpacker+ Taming dependencies

  116. webpacker+ Taming dependencies

  117. webpacker+ Taming dependencies

  118. webpacker+ Taming dependencies

  119. webpacker+ Taming dependencies

  120. webpacker+ Taming dependencies import 'chosen-js'

  121. webpacker+ (function() { // ... $ = jQuery $.fn.extend({ chosen:

    function(options) { // ... } }) // ... }).call(this) Taming dependencies import 'chosen-js'
  122. webpacker+ (function() { // ... $ = jQuery $.fn.extend({ chosen:

    function(options) { // ... } }) // ... }).call(this) Taming dependencies import 'chosen-js' assumes “jQuery” in global scope
  123. webpacker+ Taming dependencies import 'slick-carousel'

  124. webpacker+ Taming dependencies (function(factory) { if (typeof exports !== 'undefined')

    { module.exports = factory(require('jquery')) } else if (typeof define === 'function' && define.amd) { define(['jquery'], factory) } else { factory(jQuery) } })(function($) { // library code }) import 'slick-carousel'
  125. webpacker+ Taming dependencies (function(factory) { if (typeof exports !== 'undefined')

    { module.exports = factory(require('jquery')) } else if (typeof define === 'function' && define.amd) { define(['jquery'], factory) } else { factory(jQuery) } })(function($) { // library code }) import 'slick-carousel' “Universal Module Definition”
  126. webpacker+ Taming dependencies (function(factory) { if (typeof exports !== 'undefined')

    { module.exports = factory(require('jquery')) } else if (typeof define === 'function' && define.amd) { define(['jquery'], factory) } else { factory(jQuery) } })(function($) { // library code }) import 'slick-carousel' “Universal Module Definition” adds jQuery as a dependency
  127. webpacker+ Taming dependencies

  128. Quick fix Code splitting? Let Webpack do the work

  129. webpacker+ Taming dependencies // config/webpack/environment.js const webpack = require('webpack') const

    { environment } = require('@rails/webpacker') environment.plugins.append( 'CommonsChunkVendor', new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', }) )
  130. webpacker+ Taming dependencies // config/webpack/environment.js const webpack = require('webpack') const

    { environment } = require('@rails/webpacker') environment.plugins.append( 'CommonsChunkVendor', new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', }) ) Extracts common modules into one bundle
  131. webpacker+ Taming dependencies // config/webpack/environment.js const webpack = require('webpack') const

    { environment } = require('@rails/webpacker') environment.plugins.append( 'CommonsChunkVendor', new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', }) ) Extracts common modules into one bundle Changes in Webpack 4
  132. webpacker+ Taming dependencies

  133. webpacker+ Taming dependencies ✅

  134. Survival tip #3 Know your dependencies

  135. Taming dependencies Module shimming

  136. Taming dependencies (Applying transformations)

  137. webpacker+ Taming dependencies

  138. webpacker+ Taming dependencies $ yarn add expose-loader

  139. webpacker+ Taming dependencies // config/webpack/environment.js const { environment } =

    require('@rails/webpacker') environment.loaders.append('jquery', { test: require.resolve('jquery'), use: [{ loader: 'expose-loader', options: '$', }], }) $ yarn add expose-loader
  140. webpacker+ Taming dependencies // config/webpack/environment.js const { environment } =

    require('@rails/webpacker') environment.loaders.append('jquery', { test: require.resolve('jquery'), use: [{ loader: 'expose-loader', options: '$', }], }) $ yarn add expose-loader
  141. webpacker+ Taming dependencies // config/webpack/environment.js const { environment } =

    require('@rails/webpacker') environment.loaders.append('jquery', { test: require.resolve('jquery'), use: [{ loader: 'expose-loader', options: '$', }], }) $ yarn add expose-loader exposes “$” to global scope
  142. webpacker+ (function() { // ... $ = jQuery $.fn.extend({ chosen:

    function(options) { // ... } }) // ... }).call(this) Taming dependencies import 'chosen-js'
  143. webpacker+ (function() { // ... $ = jQuery $.fn.extend({ chosen:

    function(options) { // ... } }) // ... }).call(this) Taming dependencies import 'chosen-js'
  144. webpacker+ Taming dependencies $ yarn add imports-loader

  145. webpacker+ // config/webpack/environment.js const { environment } = require('@rails/webpacker') environment.loaders.append('chosen-js',

    { test: require.resolve('chosen-js'), use: [{ loader: 'imports-loader', options: 'jQuery=jquery,$=jquery,this=>window', }] }) Taming dependencies $ yarn add imports-loader
  146. webpacker+ // config/webpack/environment.js const { environment } = require('@rails/webpacker') environment.loaders.append('chosen-js',

    { test: require.resolve('chosen-js'), use: [{ loader: 'imports-loader', options: 'jQuery=jquery,$=jquery,this=>window', }] }) Taming dependencies $ yarn add imports-loader
  147. webpacker+ // config/webpack/environment.js const { environment } = require('@rails/webpacker') environment.loaders.append('chosen-js',

    { test: require.resolve('chosen-js'), use: [{ loader: 'imports-loader', options: 'jQuery=jquery,$=jquery,this=>window', }] }) Taming dependencies inject dependencies into legacy modules $ yarn add imports-loader
  148. Survival tip #4 Legacy dependencies? fix or fire

  149. SURVIVAL SCENARIOS ORGANIZING CODE✔ TAMING DEPENDENCIES✔ >PREDICTABLE CACHING

  150. Predictable caching Fingerprints

  151. webpacker+ Predictable caching

  152. webpacker+ Predictable caching

  153. webpacker+ Predictable caching // app/javascript/packs/application.js import myFunction from 'my_function' myFunction()

  154. webpacker+ Predictable caching // app/javascript/packs/application.js import myFunction from 'my_function' myFunction()

  155. webpacker+ new dependency Predictable caching // app/javascript/packs/application.js import myFunction from

    'my_function' import myModule from 'my_module' myFunction() console.log('MyModule', myModule)
  156. webpacker+ Predictable caching // app/javascript/packs/application.js import myFunction from 'my_function' import

    myModule from 'my_module' myFunction() console.log('MyModule', myModule) // app/javascript/my_module.js export default { name: 'MyModule', render() { return '<div>MyModule</div>' }, }
  157. webpacker+ Predictable caching // app/javascript/packs/application.js import myFunction from 'my_function' import

    myModule from 'my_module' myFunction() console.log('MyModule', myModule)
  158. webpacker+ Predictable caching // app/javascript/packs/application.js import myFunction from 'my_function' import

    myModule from 'my_module' myFunction() console.log('MyModule', myModule)
  159. webpacker+ application-6abd5649df983f5ce62c.js vendor-1f142b407ddab8388a61.js before… Predictable caching

  160. webpacker+ application-6abd5649df983f5ce62c.js vendor-1f142b407ddab8388a61.js application-3f225177efeb4e965518.js vendor-0434e3b09308803b8702.js before… after… Predictable caching

  161. webpacker+ application-6abd5649df983f5ce62c.js vendor-1f142b407ddab8388a61.js application-6abd5649df983f5ce62c.js vendor-1f142b407ddab8388a61.js application-3f225177efeb4e965518.js vendor-0434e3b09308803b8702.js application-3f225177efeb4e965518.js vendor-0434e3b09308803b8702.js before…

    after… Predictable caching
  162. webpacker+ application-6abd5649df983f5ce62c.js vendor-1f142b407ddab8388a61.js application-6abd5649df983f5ce62c.js vendor-1f142b407ddab8388a61.js application-3f225177efeb4e965518.js vendor-0434e3b09308803b8702.js application-3f225177efeb4e965518.js vendor-0434e3b09308803b8702.js application-3f225177efeb4e965518.js

    vendor-0434e3b09308803b8702.js application-6abd5649df983f5ce62c.js vendor-1f142b407ddab8388a61.js before… after… Surprise! Both bundles changed! Predictable caching
  163. None
  164. None
  165. webpacker+ Anatomy of a bundle .js A brief cartoon interlude…

  166. webpacker+ Anatomy of a bundle .js Entry

  167. webpacker+ Anatomy of a bundle .js .js .js .js .jpg

    .css .js Entry
  168. webpacker+ Anatomy of a bundle

  169. webpacker+ Anatomy of a bundle

  170. webpacker+ Anatomy of a bundle

  171. webpacker+ Runtime typical-bundle.js Anatomy of a bundle

  172. webpacker+ Runtime typical-bundle.js 0 1 2 3 4 5 6

    Anatomy of a bundle
  173. webpacker+ Anatomy of a bundle vendor.js application.js

  174. webpacker+ vendor.js 0 1 2 3 4 application.js 5 6

    7 8 Anatomy of a bundle
  175. webpacker+ vendor.js 0 1 2 3 4 application.js 5 6

    7 8 9 Anatomy of a bundle
  176. webpacker+ vendor.js 0 1 2 3 4 application.js 5 6

    7 8 9 Anatomy of a bundle
  177. Quick fix More configuration (yay)

  178. webpacker+ // config/webpack/environment.js const webpack = require('webpack') const { environment

    } = require('@rails/webpacker') environment.plugins.append( 'CommonsChunkVendor', new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', }) ) Predictable caching
  179. webpacker+ // config/webpack/environment.js … continued Predictable caching

  180. webpacker+ // config/webpack/environment.js … continued Predictable caching // config/webpack/environment.js …

    continued environment.plugins.append( 'CommonsChunkManifest', new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, }), ) extract the runtime into its own bundle
  181. webpacker+ // config/webpack/environment.js … continued Predictable caching // config/webpack/environment.js …

    continued environment.plugins.append( 'CommonsChunkManifest', new webpack.optimize.CommonsChunkPlugin({ name: 'runtime', minChunks: Infinity, }), ) use consistent module ids for runtime extract the runtime into its own bundle environment.plugins.append( 'HashedModuleIds', new webpack.HashedModuleIdsPlugin() )
  182. webpacker+ vendor.js 0 1 2 3 4 application.js 5 6

    7 8 Anatomy of a bundle
  183. webpacker+ vendor.js FA C1 3D A6 33 application.js 2F EC

    4B D8 Anatomy of a bundle runtime.js
  184. Survival tip #5 Understand the runtime

  185. None
  186. None
  187. Predictable caching Asychronous bundles

  188. webpacker+ import $ from 'jquery' $('.pdf-canvas').each(() => { const url

    = $(this).data('url') Predictable caching PDFJS.getDocument(url).then(pdf => { // ... render pdf data to canvas }) }) import PDFJS from 'pdfjs/webpack'
  189. webpacker+ import $ from 'jquery' $('.pdf-canvas').each(() => { const url

    = $(this).data('url') Predictable caching PDFJS.getDocument(url).then(pdf => { // ... render pdf data to canvas }) }) import PDFJS from 'pdfjs/webpack'
  190. webpacker+ Predictable caching import $ from 'jquery' $('.pdf-canvas').each(() => {

    const url = $(this).data('url') PDFJS.getDocument(url).then(pdf => { // ... render pdf data to canvas }) import('pdfjs/webpack').then((PDFJS) => { }) })
  191. webpacker+ Predictable caching import $ from 'jquery' $('.pdf-canvas').each(() => {

    const url = $(this).data('url') PDFJS.getDocument(url).then(pdf => { // ... render pdf data to canvas }) import('pdfjs/webpack').then((PDFJS) => { }) })
  192. webpacker+ Predictable caching import $ from 'jquery' $('.pdf-canvas').each(() => {

    const url = $(this).data('url') PDFJS.getDocument(url).then(pdf => { // ... render pdf data to canvas }) import('pdfjs/webpack').then((PDFJS) => { }) }) Webpack’s sweet spot!
  193. webpacker+ Predictable caching import( 'pdfjs/webpack').then(…)

  194. webpacker+ Predictable caching import( 'pdfjs/webpack').then(…) /* webpackChunkName: "pdfjs" */

  195. webpacker+ Predictable caching import( Use a “chunk name” for consistent

    bundle caching 'pdfjs/webpack').then(…) /* webpackChunkName: "pdfjs" */
  196. webpacker+ Predictable caching import( Use a “chunk name” for consistent

    bundle caching 'pdfjs/webpack').then(…) /* webpackChunkName: "pdfjs" */
  197. webpacker+ Predictable caching import( Use a “chunk name” for consistent

    bundle caching 'pdfjs/webpack').then(…) /* webpackChunkName: "pdfjs" */
  198. Survival tip #6 Prioritize async bundles

  199. SURVIVAL SCENARIOS ORGANIZING CODE✔ TAMING DEPENDENCIES✔ PREDICTABLE CACHING✔

  200. www.codementor.io/help/rails-with-webpack-not-for-everyone-feucqq83z

  201. None
  202. Survival tip #7 Webpack and Sprockets: fundamentally different

  203. Prerequisite Positive attitude

  204. @rossta rossta.net/talks ross@rossta.net