SSR React with Symfonyfor the Strong of Spirit Workshop

Webpack

Theory
Let's talk about Webpack.

Dependencies

One of the first things we will want is to use dependencies in our JavaScript code.
Webpack will allow us to use them and bundle our whole app in a single file.
Let's install it:
npm i --save-dev webpack webpack-cli
Now install our first dependency, the infamous left-pad package.
npm i --save left-pad
Tip
In 2016, Azer Koçulu, author of 250 NPM packages, unpublished all of them in the course of a dispute with NPM admins. One of these packages was left-pad, a package used in thousands of projects.
That caused a lot of despair in lots of people.
Its purpose? padding strings with a fixed number of characters.
The MI6 uses left-pad to generate codes for its secret intelligence service. Let's see that program:
Place this file in src/ as index.js
1
2
3
4
5
6
7
8
9
import leftPad from "left-pad";

var sum = (a, b) => a + b;

var makeSpy = code => leftPad(code, 3, 0);

var sumSpy = (a, b) => makeSpy(sum(a, b));

global.sumSpy = sumSpy;
And now run webpack:
npx webpack
This should have created a file named dist/main.js. Open it and inspect it.
Webpack should complain about the lack of the mode option. If we don't specify anything, it will create a production bundle.
Try creating a development bundle instead:
npx webpack --mode=development
Exercise
Check out the code produced. Can you find our code inside of the bundle produced by Webpack?
Can you find in the documentation of Webpack the diferences between the two modes?
Adapt our PHP code to use this JavaScript source as variant, and use our new global.sumSpy function to generate special agents' codes.
1
2
3
4
5
$phpexecjs->createContextFromFile(__DIR__.DIRECTORY_SEPARATOR.'dist'.DIRECTORY_SEPARATOR.'main.js');

$variant = <<<JS
sumSpy(3, 4);
JS;
Open it in your browser. Does this work?
Now we can handle dependencies :D

Writing a webpack configuration

Since Webpack v4.0, you can run it without configuration.
However, as soon as you want to customize it, you will need to write a config file.
Let's start with the default config file, that does the same as we were doing without it:
Place this code in webpack.config.js
1
2
3
4
5
6
7
8
9
10
const path = require("path");

module.exports = {
  entry: "./src/index.js",

  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist")
  }
};
Run npx webpack. Does it still work?
Exercise
The current configuration would clash with the location of files in a Symfony project.
Think about: Could you make it read the source file instead from client/index.js?
Think about: Could you make it write the output into public/main.js?
Now we are more in control of what is happening.
Restore it to the default values afterwards.

Babelize all the things

Let's explore the modules section of our configuration with an example.
Another thing we will want to do very likely is to use a different flavour of JavaScript, or different features.
This is used for instance to transpile the special flavour of React, JSX.
For instance, we have this code:
1
var sumSpy = (a, b) => makeSpy(sum(a, b));
And we have been reading about a proposal called "Pipeline operator", that would allow us to pipe instructions, like this:
1
2
const sumSpy = (a, b) => sum(a, b)
  |> makeSpy;
Babel allows us to transpile the code to a version of JavaScript that all browsers and older versions of Node.js can understand.
Let's begin by installing some core packages:
npm install --save-dev "babel-loader@^8.0.0-beta" @babel/core @babel/preset-env
Now we can tell Webpack that .js files will be parsed with Babel. Edit webpack.config.js to reflect that:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const path = require("path");

module.exports = {
  entry: "./src/index.js",
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  },
  output: {
    filename: "main.js",
    path: path.resolve(__dirname, "dist")
  }
};
This, per se, does nothing, because we have not configured Babel presets, that tell Babel the transformations to do.
We can configure Babel in several ways. One is to write a .babelrc file. Create this file with the following content:
1
2
3
{
  "presets": ["@babel/preset-env"]
}
This means "use the latest stable version as source". It is currently the same as using all these presets combined: babel-preset-es2015, babel-preset-es2016, and babel-preset-es2017
Exercise
Read about babel-preset in its documentation. Can you identify the section about targeting browsers? And the section about targeting node versions? Which option would we be useful for Server Side Rendering? Which options would be useful for Client Side Rendering?
Now let's play with the pipeline proposal:
Install it with the following command:
npm i --save-dev @babel/plugin-proposal-pipeline-operator
This proposal is in a verly stage (Stage-1), so the community is currently experimenting with it, and identifying the challenges that it poses.
In this case there are several competing implementations. We are going to use the "minimal" implementation.
Let's configure it in .babelrc:
1
2
3
4
5
6
{
  "presets": ["@babel/preset-env"],
  "plugins": [
    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }]
  ]
}
Then change our source file to use this feature:
1
2
3
4
5
6
7
8
9
10
import leftPad from "left-pad";

const sum = (a, b) => a + b;

const makeSpy = code => leftPad(code, 3, 0);

const sumSpy = (a, b) => sum(a, b)
    |> makeSpy;

global.sumSpy = sumSpy;
Did it work?
Run npx webpack once more. Does our program still work?
Got lost?
The code up to this point is in the tag 02-webpack of the repository https://github.com/Limenius/workshop-symfony-react.git
This means:
git reset HEAD --hard
(To discard your changes)
And then:
git checkout 02-webpack