Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

Decoupled Drupal Days

New York 2018

Progressive decoupling

The why and the how

Silver sponsors

Gold sponsors

Diamond sponsors

Session & contribution time co-sponsors

  • The most stressed person in the room
  • First time speaker
whoami
Blazej Owczarczyk

@os_blazey
PHP & Javascript Developer

Amazee Labs
On Drupal.org for 9 years 12 months
ps
decopuled projects in

React & Vue.js
contributing to

core, GraphQL, and ...
Progressive decoupling
The why
  • Do whatever needs to be done
Fully decoupled

POWER

  • Best possible performance
Performance
  • Stellar user experinces
UX
  • Do whatever needs to be done
No compromises
with great power comes great
  • Easy tasks become complex

RESPONSIBILITY

  • Extremly potent
  • Secure
  • Pluggable
  • States system
  • Ajax system
  • Well defined validation - submission flow
  • Validation out of the box
  • Huge component libray, eg. text, email, number, file, entity reference
  • There are no js tools that can be compared to FAPI.
  • Validation and submission need to be implemented from scratch.
  • All the forms need to be built from scratch, even those that drupal handles well.
  • The more entity references the harder the implementation is.
✖ Forms API
  • Views are so good.
  • We are so used to them that we may underestimate the underlying complexity.
  • We're used to the fact that a table listing with paging, exposed filters, click sorting and bulk operations takes a few dozen clicks.
  • Implement the table template from scratch.
  • Add markup for the exposed filters form.
  • Then fetch the initial data with the number of pages and allowed values for exposed filters.
  • Implement a pager.
  • Implement the url parsing logic to get the current page and values for exposed filters.
  • Check which colums are sortable, turn then into links and pass the sort order to the query.
✖ Views
  • Ok when the frontend and the backend share top parts of the domain.
  • No untrusted apps should share it.
  • If not - OAuth.
  • Simple OAuth works fine.
  • Hard to implement securely on frontend (SSR, refreshing tokens).
  Authentication
  • Drupal spoils us.
  • Once we learn the abstractions we become extremly efficient.
  • Lost when going fully decoupled.
  • Sometimes there are no alternatives, eg. mobile apps.
  • However, for web projects we can use...
❤ Drupal ❤
Progressive decoupling
  • Front end frameworks are really easy and intuitive when building components.
the
best
of both worlds
  • Login
  • Content editing
  • Existing functionalities
Drupal when you need it
  • Many possibilities.
  • Few examples
decoupled when it makes sense
  • Front end frameworks are really easy and intuitive when building components.
use
cases
  • decreases page load time (drastically)
  • improves perceived performance
  • reduce webserver's network traffic
data from external sources
  • decoupling makes it easy to test
  • don't even use oAuth
  • requires CORS
rich, testable UIs
  • easy to integrate
  • swap one page or block without touching the rest of the site
existing projects
The how
  • the easiest way to decouple for drupalers
  • use graphql to fetch data
  • render in twig
Twig
no js
easy setup
enable graphql_twig
  • declare data dependencies in the template file
  • isolated and self-contained templates
  • power to the themer

greeting.html.twig

{#graphql  query {
    user:currentUserContext {
      name
    }
  }#}

<div>
  Hi { user.name }, good to see you again.
</div>
        
      
progressive by nature
declare data dependencies using GraphQL

in any twig template
  • maintainer of the GraphQL module
  • really cool guy
  • he can sing, at least it looks like so
Created by Philipp Melab

Javascript
Modern JS
  • ECMAScript 2015
  • revolutionary changes
ES6
Deconstructing assignment
    const { width, height, depth } = product.dimensions
Property shorthand
  const person = { firstName, lastName }
Spread syntax
  const order = { ...customer, ...address }
  • js finally joined the family of laguages supporting default parameters
Default parameter values
  const getProperty = function (object, property = 'value') {
  return object[property]
}
  • not only syntax
  • inherits this from the parent scope
Arrow functions
  const getArea = (dims) => dims.width * dims.height
... with lexical this
  this.cartItems.forEach(item => {
  this.total += item.cost
})
Classes
  class Rectangle extends Shape {
  constructor (id, x, y, width, height) {
      super(id, x, y)
      this.width  = width
      this.height = height
  }
}
class Circle extends Shape {
  constructor (id, x, y, radius) {
      super(id, x, y)
      this.radius = radius
  }
};
// Example from http://es6-features.org
  • Multiline
  • inherits this from the parent scope
Template literals
  const greeting = `Hi ${firstName} ${lastName}`
  • ECMAScript 2016
  • not very revolutionary
  • the exponentiation operator **
  • Array.prototype.includes
ES7
  • ECMAScript 2017
  • just one feature, but a powerful one
ES8
  • The evolution of async code
ES5 - callbacks
  $.ajax({
  url: 'https://swapi.co/api/people/1',
  success: function (person) {
    $.ajax({
      url: person.homeworld,
      success: function (planet) {
        setHomeworld(person, planet)
      },
    );
  },
);
ES6 - promises
  fetch('https://swapi.co/api/people/1')
.then(response => response.json())
.then((person) => {
  fetch(person.homeworld)
  .then(response => response.json())
  .then((planet) => {
    setHomeworld(person, planet)
  })
});
  • No callback hell
  • No nesting
  • Looks like sync code
  • Readable
  • Implemented using generators
ES8 - async/await
  const personResponse = await fetch('https://swapi.co/api/people/1')
const person = await personResponse.json()

const planetResponse = await fetch(person.homeworld)
const planet = await planetResponse.json()

setHomeworld(person, planet)
Embedding
JS
modernization
  • Since 8.4
Drupal core now using ES6 for javascript development
  • It was designed for drupal core
✔️ core
  • It was designed for drupal core
✖ contrib
  • It was designed for drupal core
✖ custom
Issue
  • use package.json from core
  • postponed because of a bug in babel-cli
  • may work (it did at some point)
[#2957390] Use ES6 for contrib and custom JavaScript development
  • Transpilation is not a silver bullet
  • Adding npm packages is tedious
  • It needs to be easy if we want people to start contributing
npm dependencies?
recipe for
Vue.js
  • requires vue-cli
  • vue is just a folder name
Initialize a vue project
  cd my_module
vue create vue
Define a dynamic library
        function hook_library_info_build()
  • add the bundle from webpack dev server to drupal
  • drupal becomes a webpack dev server
  • page reloads when changes to the files are detected
Connect with the dev server
  yarn serve # starts a dev server on port 8080
  $library['js']['http://localhost:8080/app.js'] = [
  'type' => 'external',
];
  • it's best not to commit dist
  • build for production in a deployment step
  • include the files from dist in production environment
  • the flow is similar in react
Build for prod
  RUN yarn install --pure-lockfile && yarn build
  file_scan_directory($dist_path, '/^.*\.js$/')
React?
        cd my_module
yarn create react-app react
  • possible thanks to the api first initiative
Communication
  • read-only data
  • ok for simple widgets
drupalSettings
  $library['dependencies'][] = 'core/drupalSettings';
  • guided product selection
  • redirects to a view with facets pre-set
  • built in react
  • zero-latency navigation
  • this is what the users are expecting
  • alternative to core REST and JSON API
  • Reading and querying content entities out of the box
  • Mutations need to be implemented
  • Quite convenenient in general
GraphQL
  • pre-configured package
Install apollo-boost
  yarn add apollo-boost graphql
  • No configuration needed
  • /graphql is the default endpoint
Import it
    import ApolloClient, { gql } from 'apollo-boost';
const client = new ApolloClient();
  • Fetch everything you need in one simple query
  • Picture is a separate entity
  • Nesting level limited by memory limit
  • Response format defined by the query
Have fun!
  const query = gql`
  query {
    user: currentUserContext {
      ... on UserUser {
        name
        fieldLocation
        picture: userPicture {
          url
          alt
          title
        }
      }
    }
  }
`
const result = await client.query({ query })
const pictureUrl = result.data.user.picture.url
Debugging
Chrome debugger
  • Sources panel is an IDE
Breakpoints
        
  • Exclude framework files from stack traces
Blackboxing
        
  • Component inspector with props and computed props
  • Vuex panel with the current state tree, list of mutations and import / export
  • Event panel with all the emitted vue events
Vue.js
devtools
  • Component inspector
React
developer tools
  • List of queries executed on the current page
  • Run queries in an embedded GraphiQL
Apollo
developer tools
  • How can we make it easier?
  • Define a central package.json of npm packages, like composer.json
  • Integrate webpack into the library system in core?
make it
easier?
Webpack
https://drupal.org/project/webpack
webpack-8.x-1.0-alpha1

released two hours ago :)
alpha?
Setup: download the module
        composer require drupal/webpack
add package.json to repository root
        yarn init -yp
add webpack's npm dependencies
        yarn add webpack webpack-serve
declare your library...

my_module.libraries.yml
my_library:
  webpack: true
  js:
    my_library.js: { }
...add npm dependencies...
        yarn add graphql apollo-boost
...and use them is js your code

my_library.js
import ApolloClient, { gql } from 'apollo-boost';

const client = new ApolloClient();
const query = gql`query { currentUserContext: { name } }`;
const result = await client.query({ query });
const name = result.data.currentUserContext.name;

alert(`Hello ${name}! Good to see you.`);
add babel

composer require drupal/webpack_babel
yarn add babel-core babel-loader
es8_example.js
const recipient = 'Internet Explorer';
alert(`Hello ${recipient} :)`);
local development
          drush webpack:serve
...with live reload

building on a server
        yarn webpack:build
How does it work?
dynamic config
based on drupal libraries

webpack.config.js
module.exports = {
  "entry": {
    "module-library-file": "/path/to/module/js/test.js",
  },
  "output": {
    "filename": "[name].bundle.js",
    "path": "/path/to/project/sites/default/files/webpack",
  },
};
extendable with plugins
(contrib modules)

SplitChunks.php
class SplitChunks extends ConfigProcessorBase {

  public function processConfig(&$config) {
    $config['optimization']['splitChunks']['chunks'] = 'all';
  }

}
alterable with a hook
(projects)

custom.module
function custom_webpack_config_alter(&$config) {
  if (getenv('LAGOON_ENVIRONMENT_TYPE') == 'development') {
    $config['mode'] = 'development';
  }
}
coming soon

webpack_react
webpack_vue

Sunday?
Question time
  • If you have questions or just want to talk
  • The link to slides is on my twitter
blazey

@os_blazey
      
Bonus time?
  • Only if there is a reason for that
  • eg. performance is critical and there is budget
Should this next project be fully decoupled?

Use a spacebar or arrow keys to navigate.
Press 'P' to launch speaker console.