Webpack demistified

https://github.com/lechinoix

Fullstack developer

Use Lean to create better working ways

Love climbing, video games and improving web app performances

Let's roll back in time...

A website kesako?

HTML / CSS / JS

style.css

script.js

And that's it !

Actually that is not, come and see my formation the 27 may at Theodo

How is our website equivalent to this ?

Webpack

A bunch of files

app.js

Entrypoint

// index.js

import Vue from 'vue'
import component from './component'

new Vue({
  el: '#app',
  components: [component]
})

Output

// app.js

var module = {}

Entrypoint

// index.js

import Vue from 'vue'
import component from './component'

new Vue({
  el: '#app',
  components: [component]
})

Output

// app.js

var module = {}

// Vue source code is printed here

// Exports are stored in variables
// to be reused

var Vue = ........

module.Vue.export.default = Vue

Entrypoint

// index.js

import Vue from 'vue'
import component from './component'

new Vue({
  el: '#app',
  components: [component]
})

Output

// app.js

var module = {}

// Vue source code is printed here

// Exports are stored in variables
// to be reused

var Vue = ........

module.Vue.export.default = Vue
// ./component.js

import capitalize from 'capitalize.js'

export default {
  template: '<h1>Hello {{ bigName }}</h1>',
  props: ['name'],
  computed: {
    bigName: function() {
      return capitalize(name)
    }
  }
}

Entrypoint

// index.js

import Vue from 'vue'
import component from './component'

new Vue({
  el: '#app',
  components: [component]
})

Output

// app.js

var module = {}

// Vue source code is printed here

// Exports are stored in variables
// to be reused

var Vue = ........

module.Vue.export.default = Vue

// capitalize source code

var capitalize = .........

module.capitalize.export.default = capitalize
// ./component.js

import capitalize from 'capitalize.js'

export default {
  template: '<h1>Hello {{ bigName }}</h1>',
  props: ['name'],
  computed: {
    bigName: function() {
      return capitalize(name)
    }
  }
}

Entrypoint

// index.js

import Vue from 'vue'
import component from './component'

new Vue({
  el: '#app',
  components: [component]
})

Output

// app.js

var module = {}

module.Vue.export.default = Vue
module.capitalize.export.default = capitalize

// Our code from component.js

var import_1 = module.capitalize.export.default

module.component.export.default = {
  template: '<h1>Hello {{ bigName }}</h1>',
  props: ['name'],
  computed: {
    bigName: function() {
      return import_1(name)
    }
  }
}
// ./component.js

import capitalize from 'capitalize.js'

export default {
  template: '<h1>Hello {{ bigName }}</h1>',
  props: ['name'],
  computed: {
    bigName: function() {
      return capitalize(name)
    }
  }
}

Entrypoint

// index.js

import Vue from 'vue'
import component from './component'

new Vue({
  el: '#app',
  components: [component]
})

Output

// app.js

var module = {}

module.Vue.export.default = Vue
module.capitalize.export.default = capitalize

// Our code from component.js

var import_1 = module.capitalize.export.default

module.component.export.default = {...}

// Our code from index.js

var import_1 = module.Vue.export.default
var import_2 = module.component.export.default

new import_1({
  el: '#app',
  components: [import_2]
})
// ./component.js

import capitalize from 'capitalize.js'

export default {
  template: '<h1>Hello {{ bigName }}</h1>',
  props: ['name'],
  computed: {
    bigName: function() {
      return capitalize(name)
    }
  }
}

There is more about this

  • Only import module once
  • Isolate modules with closure and dependency injection

There is more about this

// app.js

// Our code from index.js

var module1.component.export = function(import_1, import_2) {
  var VueInstance = new import_1({
    el: '#app',
    components: [import_2]
  })

  return {
    default: VueInstance
  }
})(module.Vue.export.default, module.component.export.default)

Config

  • Entry : List entrypoints with path and names
  • Output : Specify the format of the output
  • Module.rules : Specify what rules to apply to your modules
  • Plugins : Add actions to the build on compiler hooks
  • DevServer : Serve assets from a server instead of statically compiling them

Loader

  • Export a single function
  • Takes the file as text as parameter
  • Applies transformations to it

Loader

  • Babel Loader
  • File Loader
  • Url loader
  • ...

 

https://babeljs.io/en/repl

Rules

Output

// ./compiled/app.js

var module = {}

// Loaded from icon.svg

module.icon.export.default = '/icon.svg'

// Loaded from component.js

var import_1 = module.icon.export.default

module.export.default.component = {
  template: '<img :src="icon" />',
  data() {
    return {
      icon: import_1 
    }
  }
}
// ./index.js

import icon from './icon.svg'

export default {
  template: '<img :src="icon" />',
  data() {
    return {
      icon
    }
  }
}
// webpack.config.js

export default {
  entry: {
    app: './index.js'
  },
  output: {
     path: './compiled',
     filename: '[name].js'
  },
  module: {
    rules: [{
      match: /\.svg$/,
      loader: 'file-loader'
    }]
  }
}
// ./compiled/icon.svg

<svg>
  <fill ... />
</svg>

PublicPath

Output

// ./compiled/app.js

var module = {}

// Loaded from icon.svg

module.icon.export.default =
  config.publicPath + '/icon.svg'

// Loaded from component.js

var import_1 = module.icon.export.default

module.export.default.component = {
  template: '<img :src="icon" />',
  data() {
    return {
      icon: import_1 
    }
  }
}
// ./index.js

import icon from './icon.svg'

export default {
  template: '<img :src="icon" />',
  data() {
    return {
      icon
    }
  }
}
// webpack.config.js

export default {
  entry: {
    app: './index.js'
  },
  output: {
     path: './compiled',
     filename: '[name].js'
  },
  module: {
    rules: [{
      match: /\.svg$/,
      loader: 'file-loader'
      options: {
        publicPath: '/compiled'
      }
    }]
  }
}
// ./compiled/icon.svg

<svg>
  <fill ... />
</svg>

Webpack encore (NPM)

  • Default configuration
  • Customizable with methods
/* global require, __dirname, module */
let Encore = require('@symfony/webpack-encore');
let path = require('path')
const PROJECT_ROOT = path.join(__dirname, '../..')

Encore
  // will create public/build/app.js and public/build/app.css
  .addEntry('app', [
    path.join(PROJECT_ROOT, '/web/js/app.js'),
    path.join(PROJECT_ROOT, '/app/Resources/sass/app.scss')
  ])

  // enable source maps during development
  .enableSourceMaps(!Encore.isProduction())

  // empty the outputPath dir before each build
  .cleanupOutputBeforeBuild()

  // create hashed filenames (e.g. app.abc123.css)
  .enableVersioning(Encore.isProduction())

...

Webpack encore (NPM)

  • Default configuration
  • Some helpers provided
  • Customizable with methods

Webpack encore (Composer)

<!-- base.thml.twig -->

...

{% block javascripts %}

  <script src="{{ asset('js/lib/jquery-2.1.4.min.js') }}"></script>

  {{ encore_entry_script_tags('app') }}

{% endblock %}

...
# config.yml

...

webpack_encore:
    output_path: "%kernel.root_dir%/../web/js/compiled"
    builds:
        leadformance: "%kernel.root_dir%/../web/js/compiled/leadformance"
<!-- base.thml.twig -->

...

{% block javascripts %}

  <script src="/js/lib/jquery-2.1.4.min.js"></script>

  <script src="/js/compiled/app.0ea6c7.js"></script>

{% endblock %}

...
<!-- base.thml.twig -->

...

{% block javascripts %}

  <script src="{{ asset('js/lib/jquery-2.1.4.min.js') }}"></script>

  {{ encore_entry_script_tags('app') }}

{% endblock %}

...
{
  "entrypoints": {
    "app": {
      "css": [
        "/js/compiled/app.8b762e.css"
      ],
      "js": [
        "/js/compiled/app.0ea6c7.js"
      ]
    }
  }
}

Webpack dev server

http://localhost:9001/webpack-dev-server

Questions ?

Webpack demistified

By Nicolas Ngô-Maï

Webpack demistified

  • 293