Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Webpack Encore Symfony Live 2017 San Francisco

1,860 views

Published on

Ready to write an amazing front-end for your app? There are *so* many great tools, like React, Vue.js, module loaders, Sass, LESS, PostCSS and more. But, they all have one thing in common: you need to configure a *build* system before you write a single line of code! Thankfully, there's Webpack: the leading tool for processing & bundling your JavaScript and CSS. There's just one problem: configuring Webpack is tough and requires a lot of Webpack-specific knowledge. Say hello to Webpack Encore: a library built by Symfony to quickly bootstrap a sophisticated asset setup, complete with minification, SASS processing, automatic versioning, Babel support and *everything* you need to start writing great JavaScript quickly. In this talk, we'll also learn about using JavaScript modules, how to bootstrap a framework (like React) and other important modern practices. Give your assets a huge boost with Webpack Encore!

Published in: Technology

Webpack Encore Symfony Live 2017 San Francisco

  1. 1. Webpack Encore: Pro JavaScript and CSS for all my Friends! … that means you!
  2. 2. > Lead of the Symfony documentation team
 > Writer for KnpUniversity.com > SensioLabs & Blackfire evangelist… Fanboy > Husband of the much more talented @leannapelham knpuniversity.com twitter.com/weaverryan Yo! I’m Ryan! > Father to my more handsome son, Beckett
  3. 3. … that means you! Webpack Encore: Pro JavaScript and CSS for all my Friends!
  4. 4. , ReactJS, webpack @weaverryan All of modern JavaScript in 45 minutes! ES6 the 12 new JS things they invent during this presentation , ES2015 , ECMAScript , Babel , NodeJS yarn , modules … … and of course …
  5. 5. Modern JavaScript is a lot like… @weaverryan
  6. 6. Game of Thrones @weaverryan
  7. 7. JavaScript @weaverryan GoT Countless libraries and competing standards fighting for influence Countless characters and completing factions fighting for influence
  8. 8. @weaverryan You spent 6 months building your site in “Hipster.js” only to read on Twitter that: “no self-respecting dev uses that crap anymore” That character you love and followed for 2 seasons, was just unceremoniously decapitated JavaScript GoT
  9. 9. JavaScript is a (weird) language IT JUST HAPPENS THAT BROWSERS CAN EXECUTE THAT LANGUAGE
  10. 10. @weaverryan // yay.js
 var message = 'I like Java...Script';
 
 console.log(message); > node yay.js I like Java...Script NodeJS: server-side JavaScript engine yarn/npm: Composer for NodeJS
  11. 11. // web/js/productApp.js
 var products = [
 'Sheer Shears',
 'Wool Hauling Basket',
 'After-Shear (Fresh Cut Grass)',
 'After-Shear (Morning Dew)'
 ];
 
 var loopThroughProducts = function(callback) {
 for (var i = 0, length = products.length; i < length; i++) {
 callback(products[i]);
 }
 };
 
 loopThroughProducts(function(product) {
 console.log('Product: '+product);
 }); {# app/Resources/views/default/products.html.twig' #} <script src="{{ asset('js/productApp.js') }}"></script> our store for sheep (baaaa)
  12. 12. class ProductCollection
 {
 constructor(products) {
 this.products = products;
 }
 }
 
 let collection = new ProductCollection([
 'Sheer Shears',
 'Wool Hauling Basket',
 'After-Shear (Fresh Cut Grass)',
 'After-Shear (Morning Dew)',
 ]);
 let prods = collection.getProducts();
 
 let loopThroughProducts = callback => {
 for (let prod of prods) {
 callback(prods);
 }
 };
 
 loopThroughProducts(product => console.log('Product: '+product)); what language is this? JavaScript
  13. 13. @weaverryan ECMAScript The official name of standard JavaScript ES6/ES2015/Harmony The 6th accepted (so official) version of ECMAScript
  14. 14. Proper class and inheritance syntax let: similar to var, but more hipster function (product) {
 console.log(product);
 } class ProductCollection
 {
 constructor(products) {
 this.products = products;
 }
 }
 
 let collection = new ProductCollection([
 'Sheer Shears',
 'Wool Hauling Basket',
 'After-Shear (Fresh Cut Grass)',
 'After-Shear (Morning Dew)',
 ]);
 let prods = collection.getProducts();
 
 let loopThroughProducts = callback => {
 for (let prod of prods) {
 callback(prods);
 }
 };
 
 loopThroughProducts(product => console.log('Product: '+product));
  15. 15. class ProductCollection
 {
 constructor(products) {
 this.products = products;
 }
 }
 
 let collection = new ProductCollection([
 'Sheer Shears',
 'Wool Hauling Basket',
 'After-Shear (Fresh Cut Grass)',
 'After-Shear (Morning Dew)',
 ]);
 let prods = collection.getProducts();
 
 let loopThroughProducts = callback => {
 for (let prod of prods) {
 callback(prods);
 }
 };
 
 loopThroughProducts(product => console.log('Product: '+product)); But will it run in a browser??? Maybe!
  16. 16. Now we just need to wait 5 years for the worst browsers to support this
  17. 17. @weaverryan Babel … or do we? A JS transpiler!
  18. 18. {
 "devDependencies": {
 "babel-cli": "^6.10.1"
 }
 } Babel is a NodeJS binary… … package.json > yarn add --dev babel-cli @weaverryan
  19. 19. > ./node_modules/.bin/babel web/js/productApp.js -o web/builds/productApp.js {# app/Resources/views/default/products.html.twig' #} <script src="{{ asset('builds/productApp.js') }}"></script> @weaverryan
  20. 20. > ./node_modules/.bin/babel web/js/productApp.js -o web/builds/productApp.js {# app/Resources/views/default/products.html.twig' #} <script src="{{ asset('builds/productApp.js') }}"></script> But, this made no changes js/productApp.js == builds/productApp.js @weaverryan
  21. 21. Babel can transpile anything CoffeeScript --> JavaScript Coffee --> Tea ES6 JS --> ES5 JS * Each transformation is called a preset
  22. 22. 1) Install the es2015 preset library 2) Add a .babelrc file > yarn add babel-preset-es2015 {
 "presets": [
 "es2015"
 ]
 } @weaverryan
  23. 23. > ./node_modules/.bin/babel web/js/productApp.js -o web/builds/productApp.js loopThroughProducts(
 product => console.log('Product: '+product)
 ); loopThroughProducts(function (product) {
 return console.log('Product: ' + product);
 });
 source: built:
  24. 24. But we can use new (or experimental) features now @weaverryan Modern JavaScript has a build step Big Takeaway #1:
  25. 25. @weaverryan New to ES6: JavaScript Modules!
  26. 26. The Classic Problem: If you want to organize your JS into multiple files, you need to manually include all those script tags! @weaverryan
  27. 27. // web/js/ProductCollection.js 
 class ProductCollection
 {
 constructor(products) {
 this.products = products;
 }
 
 getProducts() {
 return this.products;
 }
 
 getProduct(i) {
 return this.products[i];
 }
 }
 
 export ProductCollection;
 @weaverryan
  28. 28. // web/js/productApp.js
 
 import ProductCollection from './ProductCollection';
 
 var collection = new ProductCollection([
 'Sheer Shears',
 'Wool Hauling Basket',
 'After-Shear (Fresh Cut Grass)',
 'After-Shear (Morning Dew)',
 ]);
 
 // ... {# app/Resources/views/default/products.html.twig' #} <script src="{{ asset('builds/productApp.js') }}"></script> > ./node_modules/.bin/babel web/js/productApp.js -o web/builds/productApp.js
  29. 29. Module loading in a browser is hard to do @weaverryan
  30. 30. @weaverryan Introducing…
  31. 31. @weaverryan Webpack! • bundler • module loader • all-around nice guy
  32. 32. Install webpack > yarn add --dev webpack @weaverryan
  33. 33. // web/js/ProductCollection.js 
 class ProductCollection
 {
 // ...
 }
 
 export ProductCollection;
 // web/js/productApp.js
 
 import ProductCollection from ‘./ProductCollection';
 
 // ...
  34. 34. Go webpack Go! > ./node_modules/.bin/webpack web/js/productApp.js web/builds/productApp.js The one built file contains the code from both source files
  35. 35. Optional config to make it easier to use: // webpack.config.js
 module.exports = {
 entry: {
 product: './web/js/productApp.js'
 },
 output: {
 path: './web/build',
 filename: '[name].js',
 publicPath: '/build/'
 }
 };
 build/product.js {# app/Resources/views/default/products.html.twig' #} <script src="{{ asset('build/product.js') }}"></script>
  36. 36. > ./node_modules/.bin/webpack @weaverryan > yarn run webpack
  37. 37. Great! Now let’s add more features to Webpack! @weaverryan
  38. 38. @weaverryan Features, features, features, features, features • babel transpiling • dev server • production optimizations • CSS handling • Sass/LESS • PostCSS • React • Vue • versioning • source maps • image handling • extract text • shared entry • jQuery providing • TypeScript • friendly errors
  39. 39. No Problem @weaverryan
  40. 40. @weaverryan var path = require("path");
 var process = require('process');
 var webpack = require('webpack');
 var production = process.env.NODE_ENV === 'production';
 
 // helps load CSS to their own file
 var ExtractPlugin = require('extract-text-webpack-plugin');
 var CleanPlugin = require('clean-webpack-plugin');
 var ManifestPlugin = require('webpack-manifest-plugin');
 
 var plugins = [
 new ExtractPlugin('[name]-[contenthash].css'), // <=== where should content be piped
 
 // put vendor_react stuff into its own file
 // new webpack.optimize.CommonsChunkPlugin({
 // name: 'vendor_react',
 // chunks: ['vendor_react'],
 // minChunks: Infinity, // avoid anything else going in here
 // }),
 new webpack.optimize.CommonsChunkPlugin({
 name: 'main', // Move dependencies to the "main" entry
 minChunks: Infinity, // How many times a dependency must come up before being extracted
 }),
 new ManifestPlugin({
 filename: 'manifest.json',
 // prefixes all keys with builds/, which allows us to refer to
 // the paths as builds/main.css in Twig, instead of just main.css
 basePath: 'builds/'
 }),
 ];
 
 if (production) {
 plugins = plugins.concat([
 // This plugin looks for similar chunks and files
 // and merges them for better caching by the user
 new webpack.optimize.DedupePlugin(),
 
 // This plugins optimizes chunks and modules by
 // how much they are used in your app
 new webpack.optimize.OccurenceOrderPlugin(),
 
 // This plugin prevents Webpack from creating chunks
 // that would be too small to be worth loading separately
 new webpack.optimize.MinChunkSizePlugin({
 minChunkSize: 51200, // ~50kb
 }),
 
 // This plugin minifies all the Javascript code of the final bundle
 new webpack.optimize.UglifyJsPlugin({
 mangle: true,
 compress: {
 warnings: false, // Suppress uglification warnings
 },
 sourceMap: false
 }),
 
 // This plugins defines various variables that we can set to false
 // in production to avoid code related to them from being compiled
 // in our final bundle
 new webpack.DefinePlugin({
 __SERVER__: !production,
 __DEVELOPMENT__: !production,
 __DEVTOOLS__: !production,
 'process.env': {
 BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
 'NODE_ENV': JSON.stringify('production')
 },
 }),
 new CleanPlugin('web/builds', {
 root: path.resolve(__dirname , '..')
 }),
 ]);
 }
 
 
 module.exports = {
 entry: {
 main: './main',
 video: './video',
 checkout_login_registration: './checkout_login_registration',
 team_pricing: './team_pricing',
 credit_card: './credit_card',
 team_subscription: './team_subscription',
 track_organization: './track_organization',
 challenge: './challenge',
 workflow: './workflow',
 code_block_styles: './code_block_styles',
 content_release: './content_release',
 script_editor: './script_editor',
 sweetalert2_legacy: './sweetalert2_legacy',
 admin: './admin',
 admin_user_refund: './admin_user_refund',
 
 // vendor entry points to be extracted into their own file
 // we do this to keep main.js smaller... but it's a pain
 // because now we need to manually add the script tag for
 // this file is we use react or react-dom
 // vendor_react: ['react', 'react-dom'],
 },
 output: {
 path: path.resolve(__dirname, '../web/builds'),
 filename: '[name]-[hash].js',
 chunkFilename: '[name]-[chunkhash].js',
 // in dev, make all URLs go through the webpack-dev-server
 // things *mostly* work without this, but AJAX calls for chunks
 // are made to the local, Symfony server without this
 publicPath: production ? '/builds/' : 'http://localhost:8090/builds/'
 },
 plugins: plugins,
 module: {
 loaders: [
 {
 test: /.js$/,
 exclude: /node_modules/,
 loader: "babel-loader"
 },
 {
 test: /.scss/,
 loader: ExtractPlugin.extract('style', 'css!sass'),
 },
 {
 test: /.css/,
 loader: ExtractPlugin.extract('style', 'css'),
 },
 {
 test: /.(png|gif|jpe?g|svg?(?v=[0-9].[0-9].[0-9])?)$/i,
 loader: 'url?limit=10000',
 },
 {
 // the ?(?v=[0-9].[0-9].[0-9])? is for ?v=1.1.1 format
 test: /.woff(2)?(?v=[0-9].[0-9].[0-9])?$/,
 // Inline small woff files and output them below font/.
 // Set mimetype just in case.
 loader: 'url',
 query: {
 prefix: 'font/',
 limit: 5000,
 mimetype: 'application/font-woff'
 },
 //include: PATHS.fonts
 },
 {
 test: /.ttf?(?v=[0-9].[0-9].[0-9])?$|.eot?(?v=[0-9].[0-9].[0-9])?$/,
 loader: 'file',
 query: {
 name: '[name]-[hash].[ext]',
 // does this do anything?
 prefix: 'font/',
 },
 //include: PATHS.fonts
 },
 {
 test: /.json$/,
 loader: "json-loader"
 },
 ]
 },
 node: {
 fs: 'empty'
 },
 debug: !production,
 devtool: production ? false : 'eval',
 devServer: {
 hot: true,
 port: 8090,
 // tells webpack-dev-server where to serve public files from
 contentBase: '../web/',
 headers: { "Access-Control-Allow-Origin": "*" }
 },
 };
 *** not visible here: the 1000 ways you can shot yourself in the foot
  41. 41. @weaverryan Hello Webpack Encore
  42. 42. @weaverryan oh hai!
  43. 43. Let’s start over > rm -rf package.json node_modules/ > cowsay “Make mooooore room please” @weaverryan
  44. 44. @weaverryan … and let’s start simple // assets/js/app.js 
 console.log('wow');

  45. 45. > yarn add @symfony/webpack-encore --dev @weaverryan Step 1: Install Encore
  46. 46. @weaverryan Step 2: webpack.config.js var Encore = require('@symfony/webpack-encore');
 
 Encore
 .setOutputPath('web/build/')
 .setPublicPath('/build')
 
 // will output as web/build/app.js
 .addEntry('app', './assets/js/app.js')
 
 .enableSourceMaps(!Encore.isProduction())
 ;
 
 module.exports = Encore.getWebpackConfig();

  47. 47. > yarn run encore dev {# base.html.twig #}
 <script src="{{ asset('build/app.js') }}"></script>
  48. 48. @weaverryan Yea… but I need my jQuery’s!
  49. 49. @weaverryan // assets/js/app.js
 var $ = require('jquery');
 
 $(document).ready(function() {
 $('h1').append('I love big text!');
 });

  50. 50. > yarn run encore dev
  51. 51. > yarn add jquery --dev @weaverryan > yarn run encore dev
  52. 52. @weaverryan CSS: An un-handled dependency of your JS app
  53. 53. Could we do this? // assets/js/app.js // ...
 require(‘../css/cool-app.css’);
 
 $(document).ready(function() {
 $('h1').append('I love big text!');
 });

  54. 54. > yarn run encore dev {# base.html.twig #}
 <link ... href="{{ asset('build/app.css') }}">
  55. 55. Want Bootstrap? @weaverryan
  56. 56. > yarn add bootstrap @weaverryan /* assets/css/cool-app.css */
 @import "~bootstrap/dist/css/bootstrap.css";
 /* ... */ > yarn run encore dev
  57. 57. @weaverryan But what happens with images? /* assets/css/cool-app.css */
 .product-price {
 color: green;
 background-image: url('../images/logo.png');
 }

  58. 58. > yarn add bootstrap @weaverryan /* assets/css/cool-app.css */
 @import "~bootstrap/dist/css/bootstrap.css";
 /* ... */ > yarn run encore dev
  59. 59. image is copied to builds/ and the new URL is written into the CSS
  60. 60. Stop @weaverryan thinking of your JavaScript as random code that executes
  61. 61. Start @weaverryan thinking of your JavaScript as a single application with dependencies that are all packaged up together
  62. 62. Sass instead of CSS? @weaverryan
  63. 63. @weaverryan > yarn run encore dev // assets/js/app.js
 // ...
 require('../css/app.scss');
  64. 64. @weaverryan > yarn add sass-loader node-sass --dev // webpack.config.js
 
 Encore
 // ...
 
 .enableSassLoader()
  65. 65. @weaverryan But I want page-specific CSS and JS files!
  66. 66. // assets/js/productApp.js
 import '../css/productApp.css';
 import $ from 'jquery';
 
 $(document).ready(function() {
 console.log('product page loaded!');
 });

  67. 67. // webpack.config.js
 
 Encore
 // ...
 
 .addEntry('app', './assets/js/app.js')
 .addEntry('productApp', './assets/js/productApp.js') {# product.html.twig #}
 {% block stylesheets %}
 {{ parent() }}
 
 <link href="{{ asset('build/productApp.css') }}">
 {% endblock %}
 
 {% block javascripts %}
 {{ parent() }}
 
 <script src="{{ asset('build/productApp.js') }}"></script>
 {% endblock %}

  68. 68. @weaverryan > yarn run encore dev
  69. 69. Wait! @weaverryan You have too many jQuery-ies!
  70. 70. @weaverryan jQuery is in here … and also here
  71. 71. // webpack.config.js
 
 Encore
 // ...
 
 .createSharedEntry('app', './assets/js/app.js')
 .addEntry('productApp', './assets/js/productApp.js')
  72. 72. {# base.html.twig #}
 <script src="{{ asset('build/manifest.js') }}"></script>
 <script src="{{ asset('build/app.js') }}"></script>
  73. 73. @weaverryan Asset Versioning
  74. 74. // webpack.config.js
 
 Encore
 // ...
 
 .enableVersioning()
  75. 75. Amazing! Automatic Cache Busting!!! … oh wait… @weaverryan
  76. 76. {# base.html.twig #}
 <script src="{{ asset('build/manifest.js') }}"></script>
 <script src="{{ asset('build/app.js') }}"></script>
  77. 77. manifest.json to the rescue!
  78. 78. manifest.json to the rescue! {
 "build/app.css": "/build/app.3666e24a0be80f22bd8f31c43a70b14f.css",
 "build/app.js": "/build/app.f18c7a7f2785d99e0c25.js",
 "build/images/logo.png": "/build/images/logo.482c1dc2.png",
 "build/manifest.js": "/build/manifest.d41d8cd98f00b204e980.js",
 "build/productApp.css": "/build/productApp.01f416c68486810b3cb9.css",
 "build/productApp.js": "/build/productApp.1af5d8e89a35e521309b.js"
 } {# base.html.twig #}
 <script src="{{ asset('build/manifest.js') }}"></script>
 <script src="{{ asset('build/app.js') }}"></script>
  79. 79. manifest.json to the rescue! # app/config/config.yml
 framework:
 # ...
 assets:
 json_manifest_path: ‘%kernel.project_dir%/web/build/manifest.json'
  80. 80. … so add long-term caching… @weaverryan
  81. 81. @weaverryan This presentation needs more buzz words React!
  82. 82. {# products.html.twig #}
 <div id="product-app"
 data-products="{{ products|json_encode|e('html_attr') }}">
 </div>
  83. 83. // assets/js/productApp.js
 
 import React from 'react';
 import ReactDOM from 'react-dom';
 import ProductApp from './Components/ProductApp';
 import $ from 'jquery';
 
 $(document).ready(function() {
 const root = document.getElementById('product-app');
 const startingProducts = root.dataset['products'];
 
 ReactDOM.render(
 <ProductApp message="Great Products!” initialProducts={startingProducts} />, 
 root
 );
 });
  84. 84. // webpack.config.js
 
 Encore
 // ...
 .enableReactPreset()
 ; > yarn add --dev react react-dom babel-preset-react
  85. 85. @weaverryan • React • Vue • TypeScript • Code splitting • source maps • versioning • Sass/LESS … and is the life of any party … An Encore of Features
  86. 86. @weaverryan Putting it all together
  87. 87. @weaverryan ES6/ES2015/ECMAScript 2015 The newest version of Javascript, not supported by all browsers
  88. 88. @weaverryan Babel A tool that can transform JavaScript to different JavaScript presets A) ES6 js to “old” JS B) React to raw JS
  89. 89. @weaverryan Webpack A tool that follows imports to bundle JavaScript, CSS, and anything else you dream up into one JavaScript package
  90. 90. @weaverryan Webpack Encore A tool that makes configuring webpack not suck
  91. 91. Ryan Weaver @weaverryan THANK YOU! https://joind.in/talk/bac7c

×