Top Banner
Webpack Encore: Pro JavaScript and CSS for all my Friends! … that means you!
94

Webpack Encore Symfony Live 2017 San Francisco

Jan 21, 2018

Download

Technology

Ryan Weaver
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Webpack Encore Symfony Live 2017 San Francisco

Webpack Encore:

Pro JavaScript and CSS for all my Friends!

… that means you!

Page 2: Webpack Encore Symfony Live 2017 San Francisco

> 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

Page 3: Webpack Encore Symfony Live 2017 San Francisco

… that means you!

Webpack Encore:

Pro JavaScript and CSS for all my Friends!

Page 4: Webpack Encore Symfony Live 2017 San Francisco

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

Page 5: Webpack Encore Symfony Live 2017 San Francisco

Modern JavaScript is a lot like…

@weaverryan

Page 6: Webpack Encore Symfony Live 2017 San Francisco

Game of Thrones

@weaverryan

Page 7: Webpack Encore Symfony Live 2017 San Francisco

JavaScript

@weaverryan

GoT

Countless libraries and competing standards fighting

for influence

Countless characters and completing factions fighting

for influence

Page 8: Webpack Encore Symfony Live 2017 San Francisco

@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

Page 9: Webpack Encore Symfony Live 2017 San Francisco

JavaScript is a (weird) language

IT JUST HAPPENS THAT BROWSERS CAN EXECUTE THAT LANGUAGE

Page 10: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

// yay.jsvar 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

Page 11: Webpack Encore Symfony Live 2017 San Francisco

// web/js/productApp.jsvar 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)

Page 12: Webpack Encore Symfony Live 2017 San Francisco
Page 13: Webpack Encore Symfony Live 2017 San Francisco

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

Page 14: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

ECMAScriptThe official name of standard JavaScript

ES6/ES2015/HarmonyThe 6th accepted (so official) version of ECMAScript

Page 15: Webpack Encore Symfony Live 2017 San Francisco

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

Page 16: Webpack Encore Symfony Live 2017 San Francisco

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!

Page 17: Webpack Encore Symfony Live 2017 San Francisco

Now we just need to wait 5 years for the

worst browsers to support this

Page 18: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Babel

… or do we?

A JS transpiler!

Page 19: Webpack Encore Symfony Live 2017 San Francisco

{ "devDependencies": { "babel-cli": "^6.10.1" }}

Babel is a NodeJS binary…

… package.json

> yarn add --dev babel-cli

@weaverryan

Page 20: Webpack Encore Symfony Live 2017 San Francisco

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

Page 21: Webpack Encore Symfony Live 2017 San Francisco

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

Page 22: Webpack Encore Symfony Live 2017 San Francisco

Babel can transpile anything

CoffeeScript --> JavaScript

Coffee --> Tea

ES6 JS --> ES5 JS

* Each transformation is called a preset

Page 23: Webpack Encore Symfony Live 2017 San Francisco

1) Install the es2015 preset library

2) Add a .babelrc file

> yarn add babel-preset-es2015

{ "presets": [ "es2015" ]}

@weaverryan

Page 24: Webpack Encore Symfony Live 2017 San Francisco

> ./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:

Page 25: Webpack Encore Symfony Live 2017 San Francisco

But we can use new (or experimental) features now

@weaverryan

Modern JavaScript has a build step

Big Takeaway #1:

Page 26: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

New to ES6:

JavaScript Modules!

Page 27: Webpack Encore Symfony Live 2017 San Francisco

The Classic Problem:

If you want to organize your JS into multiple files, you need to manually

include all those script tags!

@weaverryan

Page 28: Webpack Encore Symfony Live 2017 San Francisco

// web/js/ProductCollection.js class ProductCollection{ constructor(products) { this.products = products; } getProducts() { return this.products; } getProduct(i) { return this.products[i]; }} export ProductCollection;

@weaverryan

Page 29: Webpack Encore Symfony Live 2017 San Francisco

// web/js/productApp.jsimport 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

Page 30: Webpack Encore Symfony Live 2017 San Francisco
Page 31: Webpack Encore Symfony Live 2017 San Francisco

Module loading in a browser is hard to do

@weaverryan

Page 32: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Introducing…

Page 33: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Webpack!

• bundler • module loader • all-around nice guy

Page 34: Webpack Encore Symfony Live 2017 San Francisco

Install webpack

> yarn add --dev webpack

@weaverryan

Page 35: Webpack Encore Symfony Live 2017 San Francisco

// web/js/ProductCollection.js class ProductCollection{ // ...} export ProductCollection;

// web/js/productApp.jsimport ProductCollection from ‘./ProductCollection';// ...

Page 36: Webpack Encore Symfony Live 2017 San Francisco

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

Page 37: Webpack Encore Symfony Live 2017 San Francisco

Optional config to make it easier to use:

// webpack.config.jsmodule.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>

Page 38: Webpack Encore Symfony Live 2017 San Francisco

> ./node_modules/.bin/webpack

@weaverryan

> yarn run webpack

Page 39: Webpack Encore Symfony Live 2017 San Francisco

Great!

Now let’s add more features to Webpack!

@weaverryan

Page 40: Webpack Encore Symfony Live 2017 San Francisco

@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

Page 41: Webpack Encore Symfony Live 2017 San Francisco

No Problem

@weaverryan

Page 42: Webpack Encore Symfony Live 2017 San Francisco

@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

Page 43: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Hello Webpack Encore

Page 44: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

oh hai!

Page 45: Webpack Encore Symfony Live 2017 San Francisco

Let’s start over

> rm -rf package.json node_modules/

> cowsay “Make mooooore room please”

@weaverryan

Page 46: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

… and let’s start simple

// assets/js/app.js console.log('wow');

Page 47: Webpack Encore Symfony Live 2017 San Francisco

> yarn add @symfony/webpack-encore --dev

@weaverryan

Step 1: Install Encore

Page 48: Webpack Encore Symfony Live 2017 San Francisco

@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();

Page 49: Webpack Encore Symfony Live 2017 San Francisco

> yarn run encore dev

{# base.html.twig #}<script src="{{ asset('build/app.js') }}"></script>

Page 50: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Yea… but I need my jQuery’s!

Page 51: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

// assets/js/app.jsvar $ = require('jquery');$(document).ready(function() { $('h1').append('I love big text!'); });

Page 52: Webpack Encore Symfony Live 2017 San Francisco

> yarn run encore dev

Page 53: Webpack Encore Symfony Live 2017 San Francisco

> yarn add jquery --dev

@weaverryan

> yarn run encore dev

Page 54: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

CSS: An un-handled dependency of your JS app

Page 55: Webpack Encore Symfony Live 2017 San Francisco

Could we do this?

// assets/js/app.js // ...require(‘../css/cool-app.css’); $(document).ready(function() { $('h1').append('I love big text!');});

Page 56: Webpack Encore Symfony Live 2017 San Francisco

> yarn run encore dev

{# base.html.twig #}<link ... href="{{ asset('build/app.css') }}">

Page 57: Webpack Encore Symfony Live 2017 San Francisco

Want Bootstrap?

@weaverryan

Page 58: Webpack Encore Symfony Live 2017 San Francisco

> yarn add bootstrap

@weaverryan

/* assets/css/cool-app.css */@import "~bootstrap/dist/css/bootstrap.css"; /* ... */

> yarn run encore dev

Page 59: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

But what happens with images?

/* assets/css/cool-app.css */.product-price { color: green; background-image: url('../images/logo.png'); }

Page 60: Webpack Encore Symfony Live 2017 San Francisco

> yarn add bootstrap

@weaverryan

/* assets/css/cool-app.css */@import "~bootstrap/dist/css/bootstrap.css"; /* ... */

> yarn run encore dev

Page 61: Webpack Encore Symfony Live 2017 San Francisco

image is copied to builds/ and the new URL is written into the CSS

Page 62: Webpack Encore Symfony Live 2017 San Francisco

Stop

@weaverryan

thinking of your JavaScript as random code that executes

Page 63: Webpack Encore Symfony Live 2017 San Francisco

Start

@weaverryan

thinking of your JavaScript as a single application with dependencies

that are all packaged up together

Page 64: Webpack Encore Symfony Live 2017 San Francisco

Sass instead of CSS?

@weaverryan

Page 65: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

> yarn run encore dev

// assets/js/app.js// ...require('../css/app.scss');

Page 66: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

> yarn add sass-loader node-sass --dev

// webpack.config.jsEncore // ... .enableSassLoader()

Page 67: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

But I want page-specific CSS and JS files!

Page 68: Webpack Encore Symfony Live 2017 San Francisco

// assets/js/productApp.jsimport '../css/productApp.css'; import $ from 'jquery'; $(document).ready(function() { console.log('product page loaded!'); });

Page 69: Webpack Encore Symfony Live 2017 San Francisco

// webpack.config.jsEncore // ... .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 %}

Page 70: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

> yarn run encore dev

Page 71: Webpack Encore Symfony Live 2017 San Francisco

Wait!

@weaverryan

You have too many jQuery-ies!

Page 72: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

jQuery is in here

… and also here

Page 73: Webpack Encore Symfony Live 2017 San Francisco

// webpack.config.jsEncore // ... .createSharedEntry('app', './assets/js/app.js') .addEntry('productApp', './assets/js/productApp.js')

Page 74: Webpack Encore Symfony Live 2017 San Francisco

{# base.html.twig #}<script src="{{ asset('build/manifest.js') }}"></script><script src="{{ asset('build/app.js') }}"></script>

Page 75: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Asset Versioning

Page 76: Webpack Encore Symfony Live 2017 San Francisco

// webpack.config.jsEncore // ... .enableVersioning()

Page 77: Webpack Encore Symfony Live 2017 San Francisco

Amazing! Automatic Cache Busting!!!

… oh wait…

@weaverryan

Page 78: Webpack Encore Symfony Live 2017 San Francisco

{# base.html.twig #}<script src="{{ asset('build/manifest.js') }}"></script><script src="{{ asset('build/app.js') }}"></script>

Page 79: Webpack Encore Symfony Live 2017 San Francisco

manifest.json to the rescue!

Page 80: Webpack Encore Symfony Live 2017 San Francisco

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>

Page 81: Webpack Encore Symfony Live 2017 San Francisco

manifest.json to the rescue!

# app/config/config.ymlframework: # ... assets: json_manifest_path: ‘%kernel.project_dir%/web/build/manifest.json'

Page 82: Webpack Encore Symfony Live 2017 San Francisco

… so add long-term caching…

@weaverryan

Page 83: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

This presentation needs more buzz words

React!

Page 84: Webpack Encore Symfony Live 2017 San Francisco

{# products.html.twig #}<div id="product-app" data-products="{{ products|json_encode|e('html_attr') }}"> </div>

Page 85: Webpack Encore Symfony Live 2017 San Francisco

// assets/js/productApp.jsimport 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 );});

Page 86: Webpack Encore Symfony Live 2017 San Francisco
Page 87: Webpack Encore Symfony Live 2017 San Francisco

// webpack.config.jsEncore // ... .enableReactPreset();

> yarn add --dev react react-dom babel-preset-react

Page 88: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

• React • Vue • TypeScript • Code splitting

• source maps

• versioning

• Sass/LESS

… and is the life of any party …

An Encore of Features

Page 89: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Putting it all together

Page 90: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

ES6/ES2015/ECMAScript 2015

The newest version of Javascript, not supported by all browsers

Page 91: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Babel

A tool that can transform JavaScript to different JavaScript

presets A) ES6 js to “old” JS B) React to raw JS

Page 92: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Webpack

A tool that follows imports to bundle JavaScript, CSS, and anything else you dream up into one JavaScript package

Page 93: Webpack Encore Symfony Live 2017 San Francisco

@weaverryan

Webpack Encore

A tool that makes configuring webpack not suck

Page 94: Webpack Encore Symfony Live 2017 San Francisco

Ryan Weaver @weaverryan

THANK YOU!

https://joind.in/talk/bac7c