Webpack

Le packager du futur pour des applications front modulaires

Par :

Slides :
https://antogyn.github.io/xke-webpack-reveal/
Repo :
https://github.com/antogyn/xke-webpack-boilerplates/

Webpack c'est quoi ?

“Webpack takes modules with dependencies and generates static assets representing those modules.”

Un exemple simple

  • Un fichier html racine, notre template de base
  • Un fichier JS racine, celui de mon application
  • Un fichier JS, d'un composant
  • Un fichier css, le style du composant
  • Un fichier html, le template du composant


ES6, compilé avec babel


<my-foo></my-foo>
<script charset="utf-8" src="dist/app.js"></script>
      

// app.js
import './components/my-foo/my-foo';
      


Hello, je suis my-foo !


/* my-foo.css */      
my-foo {
  background-color: blue; /* et je suis bleu ! */
  font-size: 3em;
} 
        

// my-foo.js
import template from './my-foo.html';
import './my-foo.css';

// création du custom element my-foo
const MyFooProto = Object.create(HTMLElement.prototype);
MyFooProto.createdCallback = function() {
  this.innerHTML = template; // utilisation du template importé
};
document.registerElement('my-foo', {prototype: MyFooProto});
      

Le résultat ?

webpack.config.js


module.exports = {
  context: __dirname, // la racine de l'app (fs)
        

  entry: {
    app: './public/app.js' // le point d'entrée de l'app
  },
        

  output: {
    path: __dirname + '/dist', // le path absolu de l'output (fs)
    filename: 'app.js', // le nom de l'output
    publicPath: '/dist/' // l'url de l'output relatif au host
  },
        

  module: {
    loaders: [
      {
        test: /\.html$/, // si je rencontre un import de fichier html...
        loader: 'html' //... alors j'utilise le loader html
      },
        

      {
        test: /\.css/, // si je rencontre un import de fichier css...
        loader: 'style!css' //... alors j'utilise les loaders style et css
      },
        

      {
        test: /\.js$/, // si je rencontre un import de fichier js...
        exclude: /node_modules/,//... qui n'est pas dans node_module/...
        loader: 'babel' //... alors j'utilise le loader babel
      }
        


      // pour tout le reste, webpack utilise le js loader (built-in)
    ]
  }
};
        

Lancer Webpack


webpack
        

# Dev : sourcemaps
webpack -d [--watch]
        

# Prod : minification, optimisations
webpack -p
        

Allons plus loin...

Déplacer les styles dans un fichier à part

Dans notre config webpack :

var ExtractTextPlugin = require('extract-text-webpack-plugin');
        

plugins: [
  new ExtractTextPlugin('app.css')
],
        

{
  test: /\.css/,
  loader: ExtractTextPlugin.extract('style', 'css')
},
        
Dans notre html :


<link rel=stylesheet type="text/css" href="dist/app.css">
       

Linter le code


module: {
  preLoaders: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: "eslint"
    }
  ],
        

Zipper le bundle


var CompressionPlugin = require('compression-webpack-plugin');
        

plugins: [
  new CompressionPlugin()
]
         

Et mes images dans mon html/css ? Mes fonts ?

Les loaders html et style sont capables de reconnaître leurs dépendances


<img src="my-foo.png" />
        

@font-face {
  font-family: myFont;
  src: url(my-font.woff); /* idem */
}
        
Et on les charge grâce au file loader


loaders: [
  {
    test: /\.png$/,
    loader: "file?name=img/[name].[ext]"
  },
  {
    test: /\.woff$/,
    loader: 'file?name=fonts/[name].[ext]'
  },
        

Vous vous souvenez de lui ?


output: {
  path: __dirname + '/dist',
  filename: 'app.js',
  publicPath: '/dist/' // <===== ici
}
        
Webpack préfixe les urls par le public path !


              
devient


        

Ajouter un hash au nom du bundle

Dans notre config webpack :

output: {
  ...
  filename: 'app-[hash].js',
      

var BundleTracker = require('webpack-bundle-tracker');
      

plugins: [
  new BundleTracker({
    path: __dirname,
    filename: 'webpack-manifest.json'})
],
      
Un fichier json est créé, dans lequel on retrouve le bundle

"name": "app-0a277e942b25.js",
        
On utilise une librairie de templating (ici mustache)


<my-foo></my-foo>
{{#chunks.app}}
<script charset="utf-8" src="dist/{{name}}"></script>
{{/chunks.app}}
        
Notre index compilé récupère le nom hashé

mustache webpack-manifest.json index.mustache > index.html
        


<my-foo></my-foo>
<script charset="utf-8" src="dist/app-0a277e942b25.js"></script>
      

Compiler des fichiers sass

Dans notre config webpack :

{
  test: /\.scss$/,  // si je rencontre un import de fichier scss...
  loaders: ["style", "css", "sass"] //... alors j'utilise les loaders sass,
                                    // css et style
}
      
Pour ajouter les sources map (en plus de webpack -d)

{
  test: /\.scss$/,
  loaders: ["style", "css?sourceMap", "sass?sourceMap"]
}
        

Séparer les vendors (libs) de mon app


var webpack = require('webpack');
          

  entry: {
    app: './public/app.js',
    vendors: ['jquery'] // <= va faire un require('jquery');
  },
          

  plugins: [
    new webpack.optimize.CommonsChunkPlugin('vendors', 'vendors.js')
  ],
          

// app.js
import $ from 'jquery';
$('body').css('background-color', 'yellow');
      




            
Le code de jQuery n'est présent que dans vendors.js !

Méthode 2 : créer son propre module important tous les vendors


// vendors.js
import 'jquery';
        

entry: {
  app: './public/app.js',
  vendors: './public/vendors.js' // <=
},
          

Lazy loading

Tout notre bundle est chargé, même les modules qui ne sont pas immédiatement utiles

Pour avoir un chargement initial plus rapide, j'aimerais découper mon bundle et charger les modules à la volée




        

// mybutton.js
import $ from 'jquery';

export default {
  makeItYellow() {
    $('body').css('background-color', 'yellow');
  }
};
        

// app.js
import $ from 'jquery'

$('#mybutton').click(() => {
  require.ensure([], () => {
    const mybutton = require('./mybutton').default;
    mybutton.makeItYellow();
  });
});
          
(démo)

Mettre en place un server de dev qui met à jour le browser à chaque changement

Après avoir installé webpack-dev-server :

webpack-dev-server --inline --hot --content dist/
      
Le server démarre, les assets sont compilés et placés en mémoire.

Les modifications se feront à chaud (hot module replacement) si possible, sinon en livereload.

(démo)

(react-hot-loader)

En fait, Webpack...

...convient à tous les projets !

tada

Questions ?