finally, professional frontend dev with reactjs, webpack & symfony (symfony cat 2016)

Post on 16-Apr-2017

6.901 Views

Category:

Technology

4 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Finally, Professional Frontend dev with:

ReactJS, Webpack & Symfony

♥’s

> Lead of the Symfony documentation team

> KnpLabs US - Symfony consulting, training & kumbaya

> Writer for KnpUniversity.com: PHP & Symfony screencasts packed with puns, unrelated (but entertaining) illustrations and coding challenges!> Husband of the much more talented @leannapelham

knpuniversity.com twitter.com/weaverryan

¡Hola!

♥’s

Finally, Professional Frontend dev with:

ReactJS, Webpack & Symfony

, 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

npm , JSX …

… and of course …

Modern JavaScript is a lot like…

@weaverryan

Game of Thrones

JavaScript

@weaverryan

GoT

Countless libraries and competing standards fighting

for influence

Countless characters and completing factions fighting

for influence

@weaverryan

You spent 6 months building your site in <Cool.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

@weaverryan

Plain, boring old JavaScript

JavaScript is a (weird) language

IT JUST HAPPENS THAT BROWSERS CAN EXECUTE THAT LANGUAGE

@weaverryan

// yay.jsvar message = 'I like Java...Script'; console.log(message);

> node yay.js

I like Java...Script

NodeJS: server-side JavaScript engine

npm: Composer for NodeJS

@weaverryan

Follow along with the real code:

github.com/weaverryan/symfonycat-js

(hint: look at the history, each thing we do is its own commit)

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

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 = function(callback) { for (let i = 0, length = prods.length; i < length; i++) { callback(collection.getProduct(i)); }}; loopThroughProducts(product => console.log('Product: '+product));

what language is this?

JavaScript

@weaverryan

ECMAScriptThe official name of standard JavaScript

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

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 = function(callback) { for (let i = 0, length = prods.length; i < length; i++) { callback(collection.getProduct(i)); }}; loopThroughProducts(product => console.log('Product: '+product));

But will it run in a browser???

Maybe!

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 = function(callback) { for (let i = 0, length = prods.length; i < length; i++) { callback(collection.getProduct(i)); }}; loopThroughProducts(product => console.log(product));

Proper class and inheritance syntax

let: similar to var, but different

function (product) { console.log(product); }

Now we just need to wait 5 years for the crappiest browsers

to support this

@weaverryan

Babel

… or do we?

A JS transpiler!

Babel is a NodeJS binary…

{ "name": "js-tutorial", "version": "1.0.0"}

1) Make a package.json file

2) Download babel

> npm install --save-dev babel-cli

@weaverryan

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

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

Babel can transpile anything

CoffeeScript --> JavaScript

Coffee --> Tea

ES6 JS --> ES5 JS

* Each transformation is called a preset

1) Install the es2015 preset library

2) Add a .babelrc file

> npm install --save-dev babel-preset-es2015

{ "presets": [ "es2015" ]}

@weaverryan

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

But we can use new (or experimental) features now

@weaverryan

Modern JavaScript has a build step

Big Takeaway #1:

@weaverryan

New to ES6:

JavaScript Modules!

The Classic Problem:

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

include all those script tags!

@weaverryan

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

@weaverryan

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

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

Module loading in a browser is hard to do

@weaverryan

@weaverryan

Introducing…

@weaverryan

Webpack!

• bundler • module loader • all-around nice guy

Install webpack

> npm install --save-dev webpack

@weaverryan

Use require instead of import/export *

* I’ll tell you why later

// web/js/ProductCollection.jsclass ProductCollection{ // ...} module.exports = ProductCollection;

// web/js/productApp.jsvar ProductCollection = require('./ProductCollection');// ...

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

Optional config to make it easier to use:

// webpack.config.jsmodule.exports = { entry: { product: './web/js/productApp.js' }, output: { path: './web/builds', filename: '[name].js', publicPath: '/builds/' }};

builds/product.js

{# app/Resources/views/default/products.html.twig' #}

<script src="{{ asset('builds/product.js') }}"></script>

> ./node_modules/.bin/webpack

@weaverryan

Wait!

We lost our ES6->ES5 transformation!!!

@weaverryan

Hey webpack!

Yo! When you load .js files, can you run them through Babel for me?

- kthxbai <3 Ryan

webpack loaders allow you to transform files as they’re loaded

1) Install the babel-loader

2) Activate the loader in webpack.config.js

> npm install --save-dev babel-loader

module.exports = { // ... module: { loaders: [ { test: /\.js$/, loader: "babel-loader", exclude: /node_modules/ } ] }};

> ./node_modules/.bin/webpack

@weaverryan

Module loading +

ES6 Support

Use import/export now if you prefer

// web/js/ProductCollection.jsclass ProductCollection{ // ...} export default ProductCollection;

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

> ./node_modules/.bin/webpack

@weaverryan

@weaverryan

Dev Tools

… because life is too short to run webpack after every change you make

> npm install webpack-dev-server --save-dev

> ./node_modules/.bin/webpack-dev-server \ --content-base=./web/

@weaverryan

1) Install the webpack-dev-server

2) Run that!

http://localhost:8080 - static assets are served - compiled assets are served dynamically

Wait!

@weaverryan

Don’t I need to update all my script tags?

<script src="{{ asset('builds/product.js') }}">

<script src="http://localost:8080/builds/product.js">

# app/config/config.ymlframework: assets: base_url: http://localhost:8080

Boom!

@weaverryan

… or the real solution

@weaverryan

# app/config/parameters.ymlparameters: # ... use_webpack_dev_server: true

class AppKernel extends Kernel{ // ... public function registerContainerConfiguration(LoaderInterface $loader) { // ... $loader->load(function(ContainerBuilder $container) { if ($container->getParameter('use_webpack_dev_server')) { $container->loadFromExtension('framework', [ 'assets' => [ 'base_url' => 'http://localhost:8080' ] ]); } }); }}

… or the real solution

@weaverryan

@weaverryan

Status Updatewe can:

• use ES6 features • import and export modules

@weaverryan

CSS: An un-handled dependency of your JS app

Could we do this?

// web/js/productApp.jsimport ProductCollection from './ProductCollection'; // could this somehow load that CSS for us?import '../css/productApp.css'; // ...

Loader!

module.exports = { // ... module: { loaders: [ { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" } ] }};

webpack loaders allow you to transform files as they’re loaded

Remember:

1) Install the css-loader

2) Activate the loader just for this file

> npm install css-loader --save-dev

import 'css!../css/productApp.css';

this transforms the CSS into a JS data-structure… but does nothing with it

1) Install the style-loader

> npm install style-loader --save-dev

import 'style!css!../css/productApp.css';

inlines the CSS on the page in a style tag

2) Activate both loaders for this file

Yes,

@weaverryan

the one JS file now holds the contents of two JS files and a CSS file

{# app/Resources/views/default/products.html.twig' #}

<script src="{{ asset('builds/product.js') }}"></script>

Move the loader to config to simplify

import '../css/productApp.css';

// webpack.config.js module.exports = { // ... module: { loaders: [ // ... { test: /\.css$/, loader: "style!css" } ] },};

@weaverryan

Ah, but what should my image paths look like?

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

http://example.com/products/5/photos/../images/logo.png

http://example.com/products/5/photos

This broke webpack!

Yes, webpack parses the CSS and tries to load file imports and url() references

(i.e. to images & fonts)

1) Install the file-loader & url-loader

> npm install file-loader url-loader --save-dev

2) Activate the loader for .png files

// webpack.config.js // ... loaders: [ // ... { test: /\.png/, loader: "url-loader?limit=10000" }]

{ test: /\.png/, loader: "url-loader?limit=10000"}

For .png files < 10kb

image is turned into a “data url” and inlined in the CSS

For .png files > 10kb

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

Stop

@weaverryan

thinking of your JavaScript as random code that executes

Start

@weaverryan

thinking of your JavaScript as a single application with dependencies

that are all packaged up together

@weaverryan

Unleashing the Power of NodeJS and ReactJS

Like Composer, NodeJS has a lot of third-party libraries

@weaverryan

… and we can use them

lodash

@weaverryan

JavaScript utility library

1) Install it

> npm install lodash --save-dev

2) Use it

// web/js/productApp.js// ...import _ from 'lodash'; _.each(collection.getProducts(), function(product) { // ...});

@weaverryan

'./ProductCollection'

vs

'lodash'

@weaverryan

ReactJS

// web/js/productApp.jsimport React from 'react'; import ReactDOM from 'react-dom'; var ProductApp = React.createClass({ render: function() { return ( <h1>Yay!</h1> ) }});

??????

$(document).ready(function() { ReactDOM.render( <ProductApp/>, document.getElementById('product-app') );});

<ProductApp myName="Ryan" />

JSX

React.createElement( ProductApp, { myName: "Ryan" })

This is not real EcmaScript, but babel can handle it

… but it doesn’t yet

1) Install the babel preset

> npm install --save-dev babel-preset-react

2) Add the preset in .babelrc

@weaverryan

{ "presets": [ "es2015", "react" ]}

… nope - still not working

// productApp.jsimport React from 'react'; import ReactDOM from 'react-dom'; var ProductApp = React.createClass({ render: function() { return ( <h1>Yay!</h1> ) }});$(document).ready(function() { ReactDOM.render( <ProductApp/>, document.getElementById('product-app') );});

> npm install --save-dev react react-dom

… nope - still not working

It’s alive, but huge!

*file size would be much smaller in reality,due to missing uglify and other production settings

@weaverryan

ReactJS is pretty easy

The setup to get here is tough

React-101: props

@weaverryan

// web/js/Components/ProductList.jsimport React from 'react'; var ProductList = React.createClass({ // ...});module.exports = ProductList;

// web/js/productApp.jsimport ReactDOM from 'react-dom'; import ProductList from './Components/ProductList'; $(document).ready(function() { ReactDOM.render( <ProductList message="Great Products!" />, document.getElementById('product-app') );});

It’s a prop!

// web/js/Components/ProductList.jsimport React from 'react'; var ProductList = React.createClass({ render: function() { return ( <h1>{this.props.message}</h1> ) }});module.exports = ProductList;

hello again prop!

collection props

@weaverryan

// web/js/productApp.jsvar startingProducts = [ 'Sheer Shears', 'Wool Hauling Basket', 'After-Shear (Fresh Cut Grass)', 'After-Shear (Morning Dew)',]; $(document).ready(function() { ReactDOM.render( <ProductList initialProducts={startingProducts} />, document.getElementById('product-app') );});

// web/js/Components/ProductApp.jsimport _ from 'lodash'; var ProductList = React.createClass({ render: function() { var productRows = []; _.each(this.props.initialProducts, function(product) { productRows.push( <tr> <td>{product}</td> <td className="product-price"> {Math.round(Math.random()*50)} </td> </tr> ); }); // ... }});

// web/js/Components/ProductApp.jsimport _ from 'lodash'; var ProductList = React.createClass({ render: function() { var productRows = []; // ... return ( <div> <h1>{this.props.message}</h1> <table className="table"> <tbody>{productRows}</tbody> </table> </div> ) }});

React 101: initial data

@weaverryan

// web/js/productApp.jsvar startingProducts = [ 'Sheer Shears', 'Wool Hauling Basket', 'After-Shear (Fresh Cut Grass)', 'After-Shear (Morning Dew)',]; $(document).ready(function() { ReactDOM.render( <ProductList initialProducts={startingProducts} />, document.getElementById('product-app') );});

@weaverryan

{# app/Resources/views/default/products.html.twig #}<script> window.startingProducts = [ 'Sheer Shears', 'Wool Hauling Basket', 'After-Shear (Fresh Cut Grass)', 'After-Shear (Morning Dew)', ];</script>

// web/js/productApp.jsvar startingProducts = window.startingProducts;$(document).ready(function() { ReactDOM.render( <ProductApp initialProducts={startingProducts} />, document.getElementById('product-app') );});

React 101: state

@weaverryan

var ProductApp = React.createClass({ render: function() { return ( <div> <h1>{this.props.message}</h1> </div> ) }});

var ProductApp = React.createClass({ render: function() { return ( <div> <h1>{this.state.message}</h1>

<button onClick={this.handleClick}> New Message </button> </div> ) }});

var ProductApp = React.createClass({ getInitialState: function() { return { message: 'Product List!'; } } // ...}

var ProductApp = React.createClass({ render: function() { return ( <div> <h1>{this.state.message}</h1>

<button onClick={this.handleClick}> New Message </button> </div> ) }});

var ProductApp = React.createClass({ handleClick: function(e) { e.preventDefault(); this.setState({ message: 'New Message!' }) }, // ...}

@weaverryan

Putting it all together

@weaverryan

ES6/ES2015/ECMAScript 2015

The newest version of Javascript, not supported by all browsers

@weaverryan

Babel

A tool that can transform JavaScript to different JavaScript

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

@weaverryan

Webpack

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

loaders A) JS through Babel B) CSS to inlined styles C) images copied, paths used

@weaverryan

ReactJS

A nifty frontend framework where you pass around and render props (immutable)

and state (mutable)

… but the JavaScript world moves quickly…

Ryan Weaver @weaverryan

THANK YOU!

top related