SlideShare a Scribd company logo
Keeping the frontend
under control with
Symfony and Webpack
Nacho Martín
@nacmartin
nacho@limenius.com
Munich Symfony Meetup
October’16
I write code at Limenius
We build tailor made projects
using mainly Symfony
and React.js
So we have been figuring out how to organize better
the frontend
Nacho Martín
nacho@limenius.com @nacmartin
Why do we need this?
Assetic?
No module loading
No bundle orientation
Not a standard solution for frontenders
Other tools simply have more manpower
behind
Written before the Great Frontend Revolution
Building the Pyramids: 130K man years
Writing JavaScript: 10 man days
JavaScript
Making JavaScript great: NaN man years
JavaScript
Tendencies
Asset managers
Tendency
Task runners
Tendency
Task runners Bundlers
Task runners + understanding of
require(ments)
Tendency
Task runners Bundlers
Task runners + understanding of
require(ments)
Package management in JS
Server Side (node.js)
Bower
Client side (browser)
Used to be
Package management in JS
Server Side (node.js)
Bower
Client side (browser)
Used to be Now
Everywhere
Module loaders
Server Side (node.js)
Client side (browser)
Used to be
Module loaders
Server Side (node.js)
Client side (browser)
Used to be Now
Everywhere
&ES6 Style
Summarizing
Package manager Module loader
Module bundler
Setup
Directory structure
app/
bin/
src/
tests/
var/
vendor/
web/
assets/
Directory structure
app/
bin/
src/
tests/
var/
vendor/
web/
assets/
client/
js/
scss/
images/
Directory structure
app/
bin/
src/
tests/
var/
vendor/
web/
assets/
client/
js/
scss/
images/
NPM setup
$ npm init
$ cat package.json
{
"name": "webpacksf",
"version": "1.0.0",
"description": "Webpack & Symfony example",
"main": "client/js/index.js",
"directories": {
"test": "client/js/tests"
},
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"author": "Nacho Martín",
"license": "MIT"
}
Install Webpack
$ npm install -g webpack
$ npm install --save-dev webpack
Or, to install it globally
First example
var greeter = require('./greeter.js')
greeter('Nacho');
client/js/index.js
First example
var greeter = require('./greeter.js')
greeter('Nacho');
client/js/index.js
var greeter = function(name) {
console.log('Hi '+name+'!');
}
module.exports = greeter;
client/js/greeter.js
First example
var greeter = require('./greeter.js')
greeter('Nacho');
client/js/index.js
var greeter = function(name) {
console.log('Hi '+name+'!');
}
module.exports = greeter;
client/js/greeter.js
Webpack without configuration
$ webpack client/js/index.js web/assets/build/hello.js
Hash: 4f4f05e78036f9dc67f3
Version: webpack 1.13.2
Time: 100ms
Asset Size Chunks Chunk Names
hi.js 1.59 kB 0 [emitted] main
[0] ./client/js/index.js 57 bytes {0} [built]
[1] ./client/js/greeter.js 66 bytes {0} [built]
Webpack without configuration
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>{% block title %}Webpack & Symfony!{% endblock %}</title>
{% block stylesheets %}{% endblock %}
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
<script src="{{ asset('assets/build/hello.js') }}"></script>
{% endblock %}
</body>
</html>
app/Resources/base.html.twig
Webpack config
module.exports = {
entry: {
hello: './client/js/index.js'
},
output: {
publicPath: '/assets/build/',
path: './web/assets/build',
filename: '[name].js'
}
};
webpack.config.js
Webpack config
module.exports = {
entry: {
hello: './client/js/index.js'
},
output: {
publicPath: '/assets/build/',
path: './web/assets/build',
filename: '[name].js'
}
};
webpack.config.js
Loaders
Now that we have modules,
What about using modern JavaScript?
(without caring about IE support)
Now that we have modules,
What about using modern JavaScript?
(without caring about IE support)
JavaScript ES2015
•Default Parameters
•Template Literals
•Arrow Functions
•Promises
•Block-Scoped Constructs Let and Const
•Classes
•Modules
•…
Why Babel matters
import Greeter from './greeter.js';
let greeter = new Greeter('Hi');
greeter.greet('gentlemen');
class Greeter {
constructor(salutation = 'Hello') {
this.salutation = salutation;
}
greet(name = 'Nacho') {
const greeting = `${this.salutation}, ${name}!`;
console.log(greeting);
}
}
export default Greeter;
client/js/index.js
client/js/greeter.js
Install babel
$ npm install --save-dev babel-core 
babel-loader babel-preset-es2015
Install babel
$ npm install --save-dev babel-core 
babel-loader babel-preset-es2015
module.exports = {
entry: {
hello: './client/js/index.js'
},
output: {
publicPath: '/assets/build/',
path: './web/assets/build',
filename: '[name].js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
]
}
};
webpack.config.js
Install babel
$ npm install --save-dev babel-core 
babel-loader babel-preset-es2015
module.exports = {
entry: {
hello: './client/js/index.js'
},
output: {
publicPath: '/assets/build/',
path: './web/assets/build',
filename: '[name].js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
]
}
};
webpack.config.js
Install babel
$ npm install --save-dev babel-core 
babel-loader babel-preset-es2015
module.exports = {
entry: {
hello: './client/js/index.js'
},
output: {
publicPath: '/assets/build/',
path: './web/assets/build',
filename: '[name].js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
]
}
};
webpack.config.js .babelrc
{
"presets": ["es2015"]
}
Install babel
$ npm install --save-dev babel-core 
babel-loader babel-preset-es2015
module.exports = {
entry: {
hello: './client/js/index.js'
},
output: {
publicPath: '/assets/build/',
path: './web/assets/build',
filename: '[name].js'
},
module: {
loaders: [
{
test: /.js$/,
loader: 'babel-loader',
exclude: /node_modules/
},
]
}
};
webpack.config.js .babelrc
{
"presets": ["es2015"]
}
Loaders
SASS
Markdown
Base64
React
Image
Uglify
…
https://webpack.github.io/docs/list-of-loaders.html
Loader gymnastics: (S)CSS
Loading styles
require(‘../css/layout.css');
//…
client/js/index.js
Loading styles: raw*
loaders: [
//…
{ test: /.css$/i, loader: 'raw'},
]
exports.push([module.id, "body {n line-height: 1.5;n padding: 4em 1em;n}nnh2 {n
margin-top: 1em;n padding-top: 1em;n}nnh1,nh2,nstrong {n color: #333;n}nna
{n color: #e81c4f;n}nn", ""]);
Embeds it into JavaScript, but…
*(note: if you are reading the slides, don’t use this loader for css. Use css loader, that will be explained later)
Chaining styles: style
loaders: [
//…
{ test: /.css$/i, loader: ’style!raw'},
]
CSS loader
header {
background-image: url("../img/header.jpg");
}
Problem
CSS loader
header {
background-image: url("../img/header.jpg");
}
url(image.png) => require("./image.png")
url(~module/image.png) => require("module/image.png")
We want
Problem
CSS loader
header {
background-image: url("../img/header.jpg");
}
url(image.png) => require("./image.png")
url(~module/image.png) => require("module/image.png")
We want
Problem
loaders: [
//…
{ test: /.css$/i, loader: ’style!css'},
]
Solution
File loaders
{ test: /.jpg$/, loader: 'file-loader' },
{ test: /.png$/, loader: 'url-loader?limit=10000' },
Copies file as [hash].jpg, and returns the public url
If file < 10Kb: embed it in data URL.
If > 10Kb: use file-loader
Using loaders
When requiring a file
In webpack.config.js, verbose
{
test: /.png$/,
loader: "url-loader",
query: { limit: "10000" }
}
require("url-loader?limit=10000!./file.png");
{ test: /.png$/, loader: 'url-loader?limit=10000' },
In webpack.config.js, compact
SASS
{ test: /.scss$/i, loader: 'style!css!sass'},
In webpack.config.js, compact
$ npm install --save-dev sass-loader node-sass
Also
{
test: /.scss$/i,
loaders: [ 'style', 'css', 'sass' ]
},
Embedding CSS in JS is good in
Single Page Apps
What if I am not writing a Single Page App?
ExtractTextPlugin
var ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCSS = new ExtractTextPlugin('stylesheets/[name].css');
const config = {
//…
module: {
loaders: [
{ test: /.css$/i, loader: extractCSS.extract(['css'])},
//…
]
},
plugins: [
extractCSS,
//…
]
};
{% block stylesheets %}
<link href="{{asset('assets/build/stylesheets/hello.css')}}"
rel="stylesheet">
{% endblock %}
app/Resources/base.html.twig
webpack.config.js
ExtractTextPlugin
var ExtractTextPlugin = require("extract-text-webpack-plugin");
const extractCSS = new ExtractTextPlugin('stylesheets/[name].css');
const config = {
//…
module: {
loaders: [
{ test: /.css$/i, loader: extractCSS.extract(['css'])},
//…
]
},
plugins: [
extractCSS,
//…
]
};
{% block stylesheets %}
<link href="{{asset('assets/build/stylesheets/hello.css')}}"
rel="stylesheet">
{% endblock %}
app/Resources/base.html.twig
webpack.config.js
{ test: /.scss$/i, loader: extractCSS.extract(['css','sass'])},
Also
Dev tools
Webpack-watch
$ webpack --watch
Simply watches for changes and recompiles the bundle
Webpack-dev-server
$ webpack-dev-server —inline
http://localhost:8080/webpack-dev-server/
Starts a server.
The browser opens a WebSocket connection with it
and reloads automatically when something changes.
Webpack-dev-server config Sf
{% block javascripts %}
<script src="{{ asset('assets/build/hello.js', 'webpack') }}"></script>
{% endblock %}
app/Resources/base.html.twig
framework:
assets:
packages:
webpack:
base_urls:
- "%assets_base_url%"
app/config/config_dev.yml
parameters:
#…
assets_base_url: 'http://localhost:8080'
app/config/parameters.yml
Webpack-dev-server config Sf
{% block javascripts %}
<script src="{{ asset('assets/build/hello.js', 'webpack') }}"></script>
{% endblock %}
app/Resources/base.html.twig
framework:
assets:
packages:
webpack:
base_urls:
- "%assets_base_url%"
app/config/config_dev.yml
framework:
assets:
packages:
webpack: ~
app/config/config.yml
parameters:
#…
assets_base_url: 'http://localhost:8080'
app/config/parameters.yml
Optional web-dev-server
Kudos Ryan Weaver
class AppKernel extends Kernel
{
public function registerContainerConfiguration(LoaderInterface $loader)
{
//…
$loader->load(function($container) {
if ($container->getParameter('use_webpack_dev_server')) {
$container->loadFromExtension('framework', [
'assets' => [
'base_url' => 'http://localhost:8080'
]
]);
}
});
}
}
Hot module replacement
output: {
publicPath: 'http://localhost:8080/assets/build/',
path: './web/assets/build',
filename: '[name].js'
},
Will try to replace the code without even page reload
$ webpack-dev-server --hot --inline
Hot module replacement
output: {
publicPath: 'http://localhost:8080/assets/build/',
path: './web/assets/build',
filename: '[name].js'
},
Will try to replace the code without even page reload
Needs full URL (so only in dev), or…
$ webpack-dev-server --hot --inline
Hot module replacement
output: {
publicPath: 'http://localhost:8080/assets/build/',
path: './web/assets/build',
filename: '[name].js'
},
$ webpack-dev-server --hot --inline --output-public-path
http://localhost:8080/assets/build/
Will try to replace the code without even page reload
Needs full URL (so only in dev), or…
$ webpack-dev-server --hot --inline
SourceMaps
const devBuild = process.env.NODE_ENV !== ‘production';
/…
if (devBuild) {
console.log('Webpack dev build');
config.devtool = 'eval-source-map';
} else {
SourceMaps
const devBuild = process.env.NODE_ENV !== ‘production';
/…
if (devBuild) {
console.log('Webpack dev build');
config.devtool = 'eval-source-map';
} else {
eval
source-map
hidden-source-map
inline-source-map
eval-source-map
cheap-source-map
cheap-module-source-map
Several options:
Notifier
$ npm install --save-dev webpack-notifier
module.exports = {
//…
plugins: [
new WebpackNotifierPlugin(),
]
};
webpack.config.js
Notifier
$ npm install --save-dev webpack-notifier
module.exports = {
//…
plugins: [
new WebpackNotifierPlugin(),
]
};
webpack.config.js
Notifier
$ npm install --save-dev webpack-notifier
module.exports = {
//…
plugins: [
new WebpackNotifierPlugin(),
]
};
webpack.config.js
Optimize for production
Optimization options
var WebpackNotifierPlugin = require('webpack-notifier');
var webpack = require(‘webpack');
const devBuild = process.env.NODE_ENV !== 'production';
const config = {
entry: {
hello: './client/js/index.js'
},
//…
};
if (devBuild) {
console.log('Webpack dev build');
config.devtool = 'eval-source-map';
} else {
console.log('Webpack production build');
config.plugins.push(
new webpack.optimize.DedupePlugin()
);
config.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
);
}
module.exports = config;
Optimization options
var WebpackNotifierPlugin = require('webpack-notifier');
var webpack = require(‘webpack');
const devBuild = process.env.NODE_ENV !== 'production';
const config = {
entry: {
hello: './client/js/index.js'
},
//…
};
if (devBuild) {
console.log('Webpack dev build');
config.devtool = 'eval-source-map';
} else {
console.log('Webpack production build');
config.plugins.push(
new webpack.optimize.DedupePlugin()
);
config.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
);
}
module.exports = config;
$ export NODE_ENV=production; webpack
Optimization options
var WebpackNotifierPlugin = require('webpack-notifier');
var webpack = require(‘webpack');
const devBuild = process.env.NODE_ENV !== 'production';
const config = {
entry: {
hello: './client/js/index.js'
},
//…
};
if (devBuild) {
console.log('Webpack dev build');
config.devtool = 'eval-source-map';
} else {
console.log('Webpack production build');
config.plugins.push(
new webpack.optimize.DedupePlugin()
);
config.plugins.push(
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
);
}
module.exports = config;
$ export NODE_ENV=production; webpack
Bundle visualizer
$ webpack --json > stats.json
https://chrisbateman.github.io/webpack-visualizer/
Bundle visualizer
$ webpack --json > stats.json
https://chrisbateman.github.io/webpack-visualizer/
More than one bundle
Separate entry points
var config = {
entry: {
front: './assets/js/front.js',
admin: './assets/js/admin.js',
},
output: {
publicPath: '/assets/build/',
path: './web/assets/build/',
filename: '[name].js'
},
Vendor bundles
var config = {
entry: {
front: './assets/js/front.js',
admin: './assets/js/admin.js',
'vendor-admin': [
'lodash',
'moment',
'classnames',
'react',
'redux',
]
},
plugins: [
extractCSS,
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-admin',
chunks: ['admin'],
filename: 'vendor-admin.js',
minChunks: Infinity
}),
Common Chunks
var CommonsChunkPlugin = require(“webpack/lib/optimize/CommonsChunkPlugin”);
module.exports = {
entry: {
page1: "./page1",
page2: "./page2",
},
output: {
filename: "[name].chunk.js"
},
plugins: [
new CommonsChunkPlugin("commons.chunk.js")
]
}
Produces page1.chunk.js, page2.chunk.js and commons.chunk.js
On demand loading
class Greeter {
constructor(salutation = 'Hello') {
this.salutation = salutation;
}
greet(name = 'Nacho', goodbye = true) {
const greeting = `${this.salutation}, ${name}!`;
console.log(greeting);
if (goodbye) {
require.ensure(['./goodbyer'], function(require) {
var goodbyer = require('./goodbyer');
goodbyer(name);
});
}
}
}
export default Greeter;
module.exports = function(name) {
console.log('Goodbye '+name);
}
greeter.js
goodbyer.js
On demand loading
class Greeter {
constructor(salutation = 'Hello') {
this.salutation = salutation;
}
greet(name = 'Nacho', goodbye = true) {
const greeting = `${this.salutation}, ${name}!`;
console.log(greeting);
if (goodbye) {
require.ensure(['./goodbyer'], function(require) {
var goodbyer = require('./goodbyer');
goodbyer(name);
});
}
}
}
export default Greeter;
module.exports = function(name) {
console.log('Goodbye '+name);
}
greeter.js
goodbyer.js
Hashes
output: {
publicPath: '/assets/build/',
path: './web/assets/build',
filename: '[name].js',
chunkFilename: "[id].[hash].bundle.js"
},
Chunks are very configurable
https://webpack.github.io/docs/optimization.html
Practical cases
Provide plugin
plugins: [
new webpack.ProvidePlugin({
_: 'lodash',
$: 'jquery',
}),
]
$("#item")
_.find(users, { 'age': 1, 'active': true });
These just work without requiring them:
Exposing jQuery
{ test: require.resolve('jquery'), loader: 'expose?$!expose?jQuery' },
Exposes $ and jQuery globally in the browser
Dealing with a mess
require('imports?define=>false&exports=>false!blueimp-file-upload/js/vendor/jquery.ui.widget.js');
require('imports?define=>false&exports=>false!blueimp-load-image/js/load-image-meta.js');
require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.iframe-transport.js');
require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload.js');
require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-process.js');
require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-image.js');
require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload.js');
require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-validate.js');
require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-ui.js');
Broken packages that are famous
have people that have figured out how to work
with them
CopyWebpackPlugin for messes
new CopyWebpackPlugin([
{ from: './client/messyvendors', to: './../mess' }
]),
For vendors that are broken,
can’t make work with Webpack but I still need them
(during the transition)
Summary:
Summary:
• What is Webpack
• Basic setup
• Loaders are the bricks of Webpack
• Have nice tools in Dev environment
• Optimize in Prod environment
• Split your bundle as you need
• Tips and tricks
MADRID · NOV 27-28 · 2015
Thanks!
@nacmartin
nacho@limenius.com
http://limenius.com

More Related Content

PDF
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
PDF
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
PDF
Symfony: Your Next Microframework (SymfonyCon 2015)
PDF
Symfony tips and tricks
PDF
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
PDF
Introducing Assetic: Asset Management for PHP 5.3
KEY
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
PDF
Vuejs testing
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony tips and tricks
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
Introducing Assetic: Asset Management for PHP 5.3
Single Page Web Applications with CoffeeScript, Backbone and Jasmine
Vuejs testing

What's hot (20)

PDF
Symfony & Javascript. Combining the best of two worlds
PDF
Laravel 101
PDF
Guard Authentication: Powerful, Beautiful Security
PDF
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
PDF
[Bristol WordPress] Supercharging WordPress Development
PDF
Laravel 8 export data as excel file with example
PPT
Building Single Page Application (SPA) with Symfony2 and AngularJS
PDF
Sane Async Patterns
PDF
Single Page Web Apps As WordPress Admin Interfaces Using AngularJS & The Word...
PDF
Refresh Austin - Intro to Dexy
PDF
How Kris Writes Symfony Apps
PDF
Introduction to AngularJS For WordPress Developers
PPTX
Ember - introduction
PDF
Bootstrat REST APIs with Laravel 5
PPT
Symfony2 and AngularJS
PDF
HTML5 JavaScript APIs
PDF
Caldera Learn - LoopConf WP API + Angular FTW Workshop
PPT
Creating the interfaces of the future with the APIs of today
PDF
OSCON Google App Engine Codelab - July 2010
PPTX
The road to Ember.js 2.0
Symfony & Javascript. Combining the best of two worlds
Laravel 101
Guard Authentication: Powerful, Beautiful Security
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
[Bristol WordPress] Supercharging WordPress Development
Laravel 8 export data as excel file with example
Building Single Page Application (SPA) with Symfony2 and AngularJS
Sane Async Patterns
Single Page Web Apps As WordPress Admin Interfaces Using AngularJS & The Word...
Refresh Austin - Intro to Dexy
How Kris Writes Symfony Apps
Introduction to AngularJS For WordPress Developers
Ember - introduction
Bootstrat REST APIs with Laravel 5
Symfony2 and AngularJS
HTML5 JavaScript APIs
Caldera Learn - LoopConf WP API + Angular FTW Workshop
Creating the interfaces of the future with the APIs of today
OSCON Google App Engine Codelab - July 2010
The road to Ember.js 2.0
Ad

Viewers also liked (20)

PDF
Migrating to Symfony 3.0
PDF
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
PDF
Integrating React.js Into a PHP Application
PDF
Introduction to Redux
PDF
Get Soaked - An In Depth Look At PHP Streams
PDF
Diving deep into twig
ODP
Elastic Searching With PHP
PDF
Techniques d'accélération des pages web
PDF
Automation using-phing
ODP
PHP5.5 is Here
PPTX
Electrify your code with PHP Generators
PDF
The quest for global design principles (SymfonyLive Berlin 2015)
PDF
WordCamp Cantabria - Código mantenible con WordPress
PDF
Doctrine2 sf2Vigo
PDF
Mocking Demystified
PDF
Top tips my_sql_performance
PDF
Why elasticsearch rocks!
PDF
Understanding Craftsmanship SwanseaCon2015
PDF
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
PDF
Writing infinite scalability web applications with PHP and PostgreSQL
Migrating to Symfony 3.0
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
Integrating React.js Into a PHP Application
Introduction to Redux
Get Soaked - An In Depth Look At PHP Streams
Diving deep into twig
Elastic Searching With PHP
Techniques d'accélération des pages web
Automation using-phing
PHP5.5 is Here
Electrify your code with PHP Generators
The quest for global design principles (SymfonyLive Berlin 2015)
WordCamp Cantabria - Código mantenible con WordPress
Doctrine2 sf2Vigo
Mocking Demystified
Top tips my_sql_performance
Why elasticsearch rocks!
Understanding Craftsmanship SwanseaCon2015
Si le tdd est mort alors pratiquons une autopsie mix-it 2015
Writing infinite scalability web applications with PHP and PostgreSQL
Ad

Similar to Keeping the frontend under control with Symfony and Webpack (20)

PDF
Building Universal Web Apps with React ForwardJS 2017
PPTX
Django + Vue, JavaScript de 3ª generación para modernizar Django
PDF
Vue routing tutorial getting started with vue router
PDF
Building and deploying React applications
PDF
Death of a Themer
PDF
The Future of CSS with Web components
PDF
The Future of CSS with Web Components
PDF
Front End Development for Back End Java Developers - Jfokus 2020
PDF
Django at the Disco
PDF
20130528 solution linux_frousseau_nopain_webdev
PDF
How Bitbucket Pipelines Loads Connect UI Assets Super-fast
PPTX
Single Page JavaScript WebApps... A Gradle Story
PDF
Warsaw Frontend Meetup #1 - Webpack
PPTX
Nodejs.meetup
PDF
A Gentle Introduction to Angular Schematics - Angular SF 2019
PDF
How to Webpack your Django!
PDF
RESS – Responsive Webdesign and Server Side Components
PPT
Build Your Own CMS with Apache Sling
PPTX
Google app engine by example
PPTX
Presentation Tier optimizations
Building Universal Web Apps with React ForwardJS 2017
Django + Vue, JavaScript de 3ª generación para modernizar Django
Vue routing tutorial getting started with vue router
Building and deploying React applications
Death of a Themer
The Future of CSS with Web components
The Future of CSS with Web Components
Front End Development for Back End Java Developers - Jfokus 2020
Django at the Disco
20130528 solution linux_frousseau_nopain_webdev
How Bitbucket Pipelines Loads Connect UI Assets Super-fast
Single Page JavaScript WebApps... A Gradle Story
Warsaw Frontend Meetup #1 - Webpack
Nodejs.meetup
A Gentle Introduction to Angular Schematics - Angular SF 2019
How to Webpack your Django!
RESS – Responsive Webdesign and Server Side Components
Build Your Own CMS with Apache Sling
Google app engine by example
Presentation Tier optimizations

More from Ignacio Martín (15)

PDF
Elixir/OTP for PHP developers
PDF
Introduction to React Native Workshop
PDF
Server side rendering with React and Symfony
PDF
Symfony 4 Workshop - Limenius
PDF
Server Side Rendering of JavaScript in PHP
PDF
Extending Redux in the Server Side
PDF
Redux Sagas - React Alicante
PDF
React Native Workshop - React Alicante
PDF
Asegurando APIs en Symfony con JWT
PDF
Redux saga: managing your side effects. Also: generators in es6
PDF
Integrating React.js with PHP projects
PDF
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
PDF
Adding Realtime to your Projects
PDF
Symfony 2 CMF
PDF
Presentacion git
Elixir/OTP for PHP developers
Introduction to React Native Workshop
Server side rendering with React and Symfony
Symfony 4 Workshop - Limenius
Server Side Rendering of JavaScript in PHP
Extending Redux in the Server Side
Redux Sagas - React Alicante
React Native Workshop - React Alicante
Asegurando APIs en Symfony con JWT
Redux saga: managing your side effects. Also: generators in es6
Integrating React.js with PHP projects
Integrando React.js en aplicaciones Symfony (deSymfony 2016)
Adding Realtime to your Projects
Symfony 2 CMF
Presentacion git

Recently uploaded (20)

PPTX
Introduction about ICD -10 and ICD11 on 5.8.25.pptx
PDF
Unit-1 introduction to cyber security discuss about how to secure a system
PDF
SASE Traffic Flow - ZTNA Connector-1.pdf
PDF
Decoding a Decade: 10 Years of Applied CTI Discipline
PDF
WebRTC in SignalWire - troubleshooting media negotiation
PDF
Vigrab.top – Online Tool for Downloading and Converting Social Media Videos a...
PDF
An introduction to the IFRS (ISSB) Stndards.pdf
PPT
Design_with_Watersergyerge45hrbgre4top (1).ppt
PPTX
presentation_pfe-universite-molay-seltan.pptx
PDF
RPKI Status Update, presented by Makito Lay at IDNOG 10
PPT
tcp ip networks nd ip layering assotred slides
PDF
APNIC Update, presented at PHNOG 2025 by Shane Hermoso
PPTX
Slides PPTX World Game (s) Eco Economic Epochs.pptx
PPTX
CHE NAA, , b,mn,mblblblbljb jb jlb ,j , ,C PPT.pptx
DOCX
Unit-3 cyber security network security of internet system
PPTX
innovation process that make everything different.pptx
PPTX
SAP Ariba Sourcing PPT for learning material
PDF
Paper PDF World Game (s) Great Redesign.pdf
PDF
The New Creative Director: How AI Tools for Social Media Content Creation Are...
PPTX
Introduction to Information and Communication Technology
Introduction about ICD -10 and ICD11 on 5.8.25.pptx
Unit-1 introduction to cyber security discuss about how to secure a system
SASE Traffic Flow - ZTNA Connector-1.pdf
Decoding a Decade: 10 Years of Applied CTI Discipline
WebRTC in SignalWire - troubleshooting media negotiation
Vigrab.top – Online Tool for Downloading and Converting Social Media Videos a...
An introduction to the IFRS (ISSB) Stndards.pdf
Design_with_Watersergyerge45hrbgre4top (1).ppt
presentation_pfe-universite-molay-seltan.pptx
RPKI Status Update, presented by Makito Lay at IDNOG 10
tcp ip networks nd ip layering assotred slides
APNIC Update, presented at PHNOG 2025 by Shane Hermoso
Slides PPTX World Game (s) Eco Economic Epochs.pptx
CHE NAA, , b,mn,mblblblbljb jb jlb ,j , ,C PPT.pptx
Unit-3 cyber security network security of internet system
innovation process that make everything different.pptx
SAP Ariba Sourcing PPT for learning material
Paper PDF World Game (s) Great Redesign.pdf
The New Creative Director: How AI Tools for Social Media Content Creation Are...
Introduction to Information and Communication Technology

Keeping the frontend under control with Symfony and Webpack

  • 1. Keeping the frontend under control with Symfony and Webpack Nacho Martín @nacmartin nacho@limenius.com Munich Symfony Meetup October’16
  • 2. I write code at Limenius We build tailor made projects using mainly Symfony and React.js So we have been figuring out how to organize better the frontend Nacho Martín nacho@limenius.com @nacmartin
  • 3. Why do we need this?
  • 4. Assetic? No module loading No bundle orientation Not a standard solution for frontenders Other tools simply have more manpower behind Written before the Great Frontend Revolution
  • 5. Building the Pyramids: 130K man years
  • 6. Writing JavaScript: 10 man days JavaScript
  • 7. Making JavaScript great: NaN man years JavaScript
  • 11. Tendency Task runners Bundlers Task runners + understanding of require(ments)
  • 12. Tendency Task runners Bundlers Task runners + understanding of require(ments)
  • 13. Package management in JS Server Side (node.js) Bower Client side (browser) Used to be
  • 14. Package management in JS Server Side (node.js) Bower Client side (browser) Used to be Now Everywhere
  • 15. Module loaders Server Side (node.js) Client side (browser) Used to be
  • 16. Module loaders Server Side (node.js) Client side (browser) Used to be Now Everywhere &ES6 Style
  • 17. Summarizing Package manager Module loader Module bundler
  • 18. Setup
  • 22. NPM setup $ npm init $ cat package.json { "name": "webpacksf", "version": "1.0.0", "description": "Webpack & Symfony example", "main": "client/js/index.js", "directories": { "test": "client/js/tests" }, "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "author": "Nacho Martín", "license": "MIT" }
  • 23. Install Webpack $ npm install -g webpack $ npm install --save-dev webpack Or, to install it globally
  • 24. First example var greeter = require('./greeter.js') greeter('Nacho'); client/js/index.js
  • 25. First example var greeter = require('./greeter.js') greeter('Nacho'); client/js/index.js var greeter = function(name) { console.log('Hi '+name+'!'); } module.exports = greeter; client/js/greeter.js
  • 26. First example var greeter = require('./greeter.js') greeter('Nacho'); client/js/index.js var greeter = function(name) { console.log('Hi '+name+'!'); } module.exports = greeter; client/js/greeter.js
  • 27. Webpack without configuration $ webpack client/js/index.js web/assets/build/hello.js Hash: 4f4f05e78036f9dc67f3 Version: webpack 1.13.2 Time: 100ms Asset Size Chunks Chunk Names hi.js 1.59 kB 0 [emitted] main [0] ./client/js/index.js 57 bytes {0} [built] [1] ./client/js/greeter.js 66 bytes {0} [built]
  • 28. Webpack without configuration <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>{% block title %}Webpack & Symfony!{% endblock %}</title> {% block stylesheets %}{% endblock %} <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" /> </head> <body> {% block body %}{% endblock %} {% block javascripts %} <script src="{{ asset('assets/build/hello.js') }}"></script> {% endblock %} </body> </html> app/Resources/base.html.twig
  • 29. Webpack config module.exports = { entry: { hello: './client/js/index.js' }, output: { publicPath: '/assets/build/', path: './web/assets/build', filename: '[name].js' } }; webpack.config.js
  • 30. Webpack config module.exports = { entry: { hello: './client/js/index.js' }, output: { publicPath: '/assets/build/', path: './web/assets/build', filename: '[name].js' } }; webpack.config.js
  • 32. Now that we have modules, What about using modern JavaScript? (without caring about IE support)
  • 33. Now that we have modules, What about using modern JavaScript? (without caring about IE support)
  • 34. JavaScript ES2015 •Default Parameters •Template Literals •Arrow Functions •Promises •Block-Scoped Constructs Let and Const •Classes •Modules •…
  • 35. Why Babel matters import Greeter from './greeter.js'; let greeter = new Greeter('Hi'); greeter.greet('gentlemen'); class Greeter { constructor(salutation = 'Hello') { this.salutation = salutation; } greet(name = 'Nacho') { const greeting = `${this.salutation}, ${name}!`; console.log(greeting); } } export default Greeter; client/js/index.js client/js/greeter.js
  • 36. Install babel $ npm install --save-dev babel-core babel-loader babel-preset-es2015
  • 37. Install babel $ npm install --save-dev babel-core babel-loader babel-preset-es2015 module.exports = { entry: { hello: './client/js/index.js' }, output: { publicPath: '/assets/build/', path: './web/assets/build', filename: '[name].js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ }, ] } }; webpack.config.js
  • 38. Install babel $ npm install --save-dev babel-core babel-loader babel-preset-es2015 module.exports = { entry: { hello: './client/js/index.js' }, output: { publicPath: '/assets/build/', path: './web/assets/build', filename: '[name].js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ }, ] } }; webpack.config.js
  • 39. Install babel $ npm install --save-dev babel-core babel-loader babel-preset-es2015 module.exports = { entry: { hello: './client/js/index.js' }, output: { publicPath: '/assets/build/', path: './web/assets/build', filename: '[name].js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ }, ] } }; webpack.config.js .babelrc { "presets": ["es2015"] }
  • 40. Install babel $ npm install --save-dev babel-core babel-loader babel-preset-es2015 module.exports = { entry: { hello: './client/js/index.js' }, output: { publicPath: '/assets/build/', path: './web/assets/build', filename: '[name].js' }, module: { loaders: [ { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ }, ] } }; webpack.config.js .babelrc { "presets": ["es2015"] }
  • 44. Loading styles: raw* loaders: [ //… { test: /.css$/i, loader: 'raw'}, ] exports.push([module.id, "body {n line-height: 1.5;n padding: 4em 1em;n}nnh2 {n margin-top: 1em;n padding-top: 1em;n}nnh1,nh2,nstrong {n color: #333;n}nna {n color: #e81c4f;n}nn", ""]); Embeds it into JavaScript, but… *(note: if you are reading the slides, don’t use this loader for css. Use css loader, that will be explained later)
  • 45. Chaining styles: style loaders: [ //… { test: /.css$/i, loader: ’style!raw'}, ]
  • 46. CSS loader header { background-image: url("../img/header.jpg"); } Problem
  • 47. CSS loader header { background-image: url("../img/header.jpg"); } url(image.png) => require("./image.png") url(~module/image.png) => require("module/image.png") We want Problem
  • 48. CSS loader header { background-image: url("../img/header.jpg"); } url(image.png) => require("./image.png") url(~module/image.png) => require("module/image.png") We want Problem loaders: [ //… { test: /.css$/i, loader: ’style!css'}, ] Solution
  • 49. File loaders { test: /.jpg$/, loader: 'file-loader' }, { test: /.png$/, loader: 'url-loader?limit=10000' }, Copies file as [hash].jpg, and returns the public url If file < 10Kb: embed it in data URL. If > 10Kb: use file-loader
  • 50. Using loaders When requiring a file In webpack.config.js, verbose { test: /.png$/, loader: "url-loader", query: { limit: "10000" } } require("url-loader?limit=10000!./file.png"); { test: /.png$/, loader: 'url-loader?limit=10000' }, In webpack.config.js, compact
  • 51. SASS { test: /.scss$/i, loader: 'style!css!sass'}, In webpack.config.js, compact $ npm install --save-dev sass-loader node-sass Also { test: /.scss$/i, loaders: [ 'style', 'css', 'sass' ] },
  • 52. Embedding CSS in JS is good in Single Page Apps What if I am not writing a Single Page App?
  • 53. ExtractTextPlugin var ExtractTextPlugin = require("extract-text-webpack-plugin"); const extractCSS = new ExtractTextPlugin('stylesheets/[name].css'); const config = { //… module: { loaders: [ { test: /.css$/i, loader: extractCSS.extract(['css'])}, //… ] }, plugins: [ extractCSS, //… ] }; {% block stylesheets %} <link href="{{asset('assets/build/stylesheets/hello.css')}}" rel="stylesheet"> {% endblock %} app/Resources/base.html.twig webpack.config.js
  • 54. ExtractTextPlugin var ExtractTextPlugin = require("extract-text-webpack-plugin"); const extractCSS = new ExtractTextPlugin('stylesheets/[name].css'); const config = { //… module: { loaders: [ { test: /.css$/i, loader: extractCSS.extract(['css'])}, //… ] }, plugins: [ extractCSS, //… ] }; {% block stylesheets %} <link href="{{asset('assets/build/stylesheets/hello.css')}}" rel="stylesheet"> {% endblock %} app/Resources/base.html.twig webpack.config.js { test: /.scss$/i, loader: extractCSS.extract(['css','sass'])}, Also
  • 56. Webpack-watch $ webpack --watch Simply watches for changes and recompiles the bundle
  • 57. Webpack-dev-server $ webpack-dev-server —inline http://localhost:8080/webpack-dev-server/ Starts a server. The browser opens a WebSocket connection with it and reloads automatically when something changes.
  • 58. Webpack-dev-server config Sf {% block javascripts %} <script src="{{ asset('assets/build/hello.js', 'webpack') }}"></script> {% endblock %} app/Resources/base.html.twig framework: assets: packages: webpack: base_urls: - "%assets_base_url%" app/config/config_dev.yml parameters: #… assets_base_url: 'http://localhost:8080' app/config/parameters.yml
  • 59. Webpack-dev-server config Sf {% block javascripts %} <script src="{{ asset('assets/build/hello.js', 'webpack') }}"></script> {% endblock %} app/Resources/base.html.twig framework: assets: packages: webpack: base_urls: - "%assets_base_url%" app/config/config_dev.yml framework: assets: packages: webpack: ~ app/config/config.yml parameters: #… assets_base_url: 'http://localhost:8080' app/config/parameters.yml
  • 60. Optional web-dev-server Kudos Ryan Weaver class AppKernel extends Kernel { public function registerContainerConfiguration(LoaderInterface $loader) { //… $loader->load(function($container) { if ($container->getParameter('use_webpack_dev_server')) { $container->loadFromExtension('framework', [ 'assets' => [ 'base_url' => 'http://localhost:8080' ] ]); } }); } }
  • 61. Hot module replacement output: { publicPath: 'http://localhost:8080/assets/build/', path: './web/assets/build', filename: '[name].js' }, Will try to replace the code without even page reload $ webpack-dev-server --hot --inline
  • 62. Hot module replacement output: { publicPath: 'http://localhost:8080/assets/build/', path: './web/assets/build', filename: '[name].js' }, Will try to replace the code without even page reload Needs full URL (so only in dev), or… $ webpack-dev-server --hot --inline
  • 63. Hot module replacement output: { publicPath: 'http://localhost:8080/assets/build/', path: './web/assets/build', filename: '[name].js' }, $ webpack-dev-server --hot --inline --output-public-path http://localhost:8080/assets/build/ Will try to replace the code without even page reload Needs full URL (so only in dev), or… $ webpack-dev-server --hot --inline
  • 64. SourceMaps const devBuild = process.env.NODE_ENV !== ‘production'; /… if (devBuild) { console.log('Webpack dev build'); config.devtool = 'eval-source-map'; } else {
  • 65. SourceMaps const devBuild = process.env.NODE_ENV !== ‘production'; /… if (devBuild) { console.log('Webpack dev build'); config.devtool = 'eval-source-map'; } else { eval source-map hidden-source-map inline-source-map eval-source-map cheap-source-map cheap-module-source-map Several options:
  • 66. Notifier $ npm install --save-dev webpack-notifier module.exports = { //… plugins: [ new WebpackNotifierPlugin(), ] }; webpack.config.js
  • 67. Notifier $ npm install --save-dev webpack-notifier module.exports = { //… plugins: [ new WebpackNotifierPlugin(), ] }; webpack.config.js
  • 68. Notifier $ npm install --save-dev webpack-notifier module.exports = { //… plugins: [ new WebpackNotifierPlugin(), ] }; webpack.config.js
  • 70. Optimization options var WebpackNotifierPlugin = require('webpack-notifier'); var webpack = require(‘webpack'); const devBuild = process.env.NODE_ENV !== 'production'; const config = { entry: { hello: './client/js/index.js' }, //… }; if (devBuild) { console.log('Webpack dev build'); config.devtool = 'eval-source-map'; } else { console.log('Webpack production build'); config.plugins.push( new webpack.optimize.DedupePlugin() ); config.plugins.push( new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ); } module.exports = config;
  • 71. Optimization options var WebpackNotifierPlugin = require('webpack-notifier'); var webpack = require(‘webpack'); const devBuild = process.env.NODE_ENV !== 'production'; const config = { entry: { hello: './client/js/index.js' }, //… }; if (devBuild) { console.log('Webpack dev build'); config.devtool = 'eval-source-map'; } else { console.log('Webpack production build'); config.plugins.push( new webpack.optimize.DedupePlugin() ); config.plugins.push( new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ); } module.exports = config; $ export NODE_ENV=production; webpack
  • 72. Optimization options var WebpackNotifierPlugin = require('webpack-notifier'); var webpack = require(‘webpack'); const devBuild = process.env.NODE_ENV !== 'production'; const config = { entry: { hello: './client/js/index.js' }, //… }; if (devBuild) { console.log('Webpack dev build'); config.devtool = 'eval-source-map'; } else { console.log('Webpack production build'); config.plugins.push( new webpack.optimize.DedupePlugin() ); config.plugins.push( new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) ); } module.exports = config; $ export NODE_ENV=production; webpack
  • 73. Bundle visualizer $ webpack --json > stats.json https://chrisbateman.github.io/webpack-visualizer/
  • 74. Bundle visualizer $ webpack --json > stats.json https://chrisbateman.github.io/webpack-visualizer/
  • 75. More than one bundle
  • 76. Separate entry points var config = { entry: { front: './assets/js/front.js', admin: './assets/js/admin.js', }, output: { publicPath: '/assets/build/', path: './web/assets/build/', filename: '[name].js' },
  • 77. Vendor bundles var config = { entry: { front: './assets/js/front.js', admin: './assets/js/admin.js', 'vendor-admin': [ 'lodash', 'moment', 'classnames', 'react', 'redux', ] }, plugins: [ extractCSS, new webpack.optimize.CommonsChunkPlugin({ name: 'vendor-admin', chunks: ['admin'], filename: 'vendor-admin.js', minChunks: Infinity }),
  • 78. Common Chunks var CommonsChunkPlugin = require(“webpack/lib/optimize/CommonsChunkPlugin”); module.exports = { entry: { page1: "./page1", page2: "./page2", }, output: { filename: "[name].chunk.js" }, plugins: [ new CommonsChunkPlugin("commons.chunk.js") ] } Produces page1.chunk.js, page2.chunk.js and commons.chunk.js
  • 79. On demand loading class Greeter { constructor(salutation = 'Hello') { this.salutation = salutation; } greet(name = 'Nacho', goodbye = true) { const greeting = `${this.salutation}, ${name}!`; console.log(greeting); if (goodbye) { require.ensure(['./goodbyer'], function(require) { var goodbyer = require('./goodbyer'); goodbyer(name); }); } } } export default Greeter; module.exports = function(name) { console.log('Goodbye '+name); } greeter.js goodbyer.js
  • 80. On demand loading class Greeter { constructor(salutation = 'Hello') { this.salutation = salutation; } greet(name = 'Nacho', goodbye = true) { const greeting = `${this.salutation}, ${name}!`; console.log(greeting); if (goodbye) { require.ensure(['./goodbyer'], function(require) { var goodbyer = require('./goodbyer'); goodbyer(name); }); } } } export default Greeter; module.exports = function(name) { console.log('Goodbye '+name); } greeter.js goodbyer.js
  • 81. Hashes output: { publicPath: '/assets/build/', path: './web/assets/build', filename: '[name].js', chunkFilename: "[id].[hash].bundle.js" },
  • 82. Chunks are very configurable https://webpack.github.io/docs/optimization.html
  • 84. Provide plugin plugins: [ new webpack.ProvidePlugin({ _: 'lodash', $: 'jquery', }), ] $("#item") _.find(users, { 'age': 1, 'active': true }); These just work without requiring them:
  • 85. Exposing jQuery { test: require.resolve('jquery'), loader: 'expose?$!expose?jQuery' }, Exposes $ and jQuery globally in the browser
  • 86. Dealing with a mess require('imports?define=>false&exports=>false!blueimp-file-upload/js/vendor/jquery.ui.widget.js'); require('imports?define=>false&exports=>false!blueimp-load-image/js/load-image-meta.js'); require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.iframe-transport.js'); require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload.js'); require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-process.js'); require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-image.js'); require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload.js'); require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-validate.js'); require('imports?define=>false&exports=>false!blueimp-file-upload/js/jquery.fileupload-ui.js'); Broken packages that are famous have people that have figured out how to work with them
  • 87. CopyWebpackPlugin for messes new CopyWebpackPlugin([ { from: './client/messyvendors', to: './../mess' } ]), For vendors that are broken, can’t make work with Webpack but I still need them (during the transition)
  • 89. Summary: • What is Webpack • Basic setup • Loaders are the bricks of Webpack • Have nice tools in Dev environment • Optimize in Prod environment • Split your bundle as you need • Tips and tricks
  • 90. MADRID · NOV 27-28 · 2015 Thanks! @nacmartin nacho@limenius.com http://limenius.com