Skip to content

Frontend development stack

fabcor edited this page Jan 16, 2024 · 4 revisions

Warning

This page is now deprecated.

The up-to-date documentation can be found at:

https://mxcubecore.readthedocs.io/


Base elements of MXCuBE 3 frontend development stack are:

Babel

Babel compiles Javascript code written with latest coding Javascript standards for instance ES6 to a Javascript version (for instance ES5) that have better support across a wide range of browsers. This allows to use new features like the module system in today's code, without worrying about web browsers compatibility.

Babel ships with built-in support for JSX, which completely replaces the need for the former jsx-transform.

React hot loader

React Hot Loader works with webpack-dev-server with hot reloading enabled. It keeps React components mounted, preserving the state, when JSX file is changed. It is very comfortable while developing, since edits are immediately in effect.

Webpack

One of the best ways to handle the organization of a code base it to divide it into modules. Webpack is a module bundler, it generates static assets from the different modules and dependencies used in the project, these static assets are often referred to as bundles. Utilizing a module bundler enhances not only the development, but also the user experience since static assets cane be optimized to decrease load time.

There are a many popular module bundlers, some of which you might either used or heard about like Browserify or RequireJS. Both of these are extremely helpful and do a great job. Webpack however has, at the time of writing, some extra features:

  • Webpack can easily split an application into multiple files: codebase can be split into multiple chunks and those chunks can be loaded on demand reducing the initial loading time of your application.
  • Webpack can build and bundle CSS, preprocessed CSS, compile-to-JS languages (like CoffeeScript), images and more by utilising webpack loaders. It is similar to grunt or gulp tasks.
  • plugins have the ability to inject themselves into the build process to do all sorts of crazy stuff.
  • webpack-dev-server can be used during the development process, to serve produced bundles - it offers hot reloading and can proxy requests (AJAX or WebSockets) to another server transparently for testing or trying UI effectively while working on it

All those features can be achieved differently using other tools, but webpack bring them all in one tool.

webpack.config.js step by step

Here is the webpack configuration file in use with MXCuBE 3, with detailed information regarding each part of the config. The configuration file format is Javascript:

var webpack = require("webpack");
var path = require("path");

var config = {
    entry: 'main.jsx',
    output: {
        path: path.resolve(__dirname, 'mxcube3','static'),
        filename: 'bundle.js', 
        publicPath: '' 
    },
    resolve: {
        root: path.resolve(__dirname, 'mxcube3/ui'),
        extensions: ['', '.js', '.jsx']
    },

entry is the main file that webpack will read when producing output. In this case, it is set to main.jsx in mxcube3/ui directory. __dirname is a global variable pointing to the directory where webpack.config.js is located. The resolve part of the config tells webpack where to find MXCuBE files in particular. The output section indicates which file should be produced, in this case bundle.js (default name), and where: mxcube3/static.

The following section is loaders configuration:

    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: "style-loader!css-loader"
            },

This is to add .css files support to webpack; doing require("mycssfile.css"); (or ES6: import "mycssfile.css") will work transparently.

            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                loaders: ['react-hot', 'babel']
            },

.jsx and .js files will go through babel, then React hot loader. So ES6 JS code is transformed to ES5, JSX is transformed into JS and, if there are React components, React hot loader will do its magic in order to enable the Hot Reloading feature. node_modules directory is excluded, not to parse all files over there (more on this later).

            {
                test: /\.(jpe?g|png|gif)$/i,
                loaders: [
                    'url?limit=8192',
                    'img'
                ]
            },
            { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "url-loader?limit=10000&minetype=application/font-woff" },

jp(e)g, png and gif files are handled by img loader. If file size is below 8 KB, the image is converted to Base64 and directly embedded into the bundle. This is to optimise loading time. If image is bigger, it will be served via the web server like normal.

            { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: "file-loader" },

The file loader will take .ttf, .eot and .svg files and will put them in the output directory with an unique name. This allows to require/import those files transparently. This specific loader is needed by Bootstrap dependency (could be useful for others too).

            {
                test: /isotope-layout/,
                loader: 'imports?define=>false&this=>window'
            },
        ]
    },

The imports loader is a default webpack loader. It is needed to alter some globals when a JS module is loaded, to make it compatible with webpack (read this). The isotope-layout module used in MXCuBE 3 code for Sample Grid is not compatible with webpack by default, but it can be solved with this magic.

    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            "window.jQuery": "jquery"
        })
    ]

This plugin is a standard webpack plugin; this makes modules with dependency on jQuery that assume jQuery is already loaded and available in global namespace to be made compatible with webpack. Everytime $, jQuery or window.jQuery is seen in a file, webpack adds the corresponding require/import automatically. This was added for x-editable dependency.

Latest addition to webpack.config.js: proxy feature

While working with frontend stuff, at some point AJAX calls or WebSocket connections need to be tested. In this case, webpack-dev-server is not enough because it doesn't know how to deal with requests, it is only there to serve produced bundles.

Then the proxy feature comes handy: webpack-dev-server can forward requests to another server. In the case of MXCuBE it allows to have the Flask development server (back-end) on a beamline, for example, receiving and replying to all requests while continuing to work on the frontend at the same time, without the need to generate a production bundle.

devServer: {
        proxy: {
            '/mxcube/api/*': {
                target: backend_server,
                secure: false,
                ws: true
            },
        },
    },

The backend_server variable is used ; it contains the address of the backend server. The backend_server is initialized at the very beginning of the config script:

var backend_server = require('./backend_server.js');

This allows to keep webpack.config.js clean in git, the only file that is not under version control is backend_server.js as it changes over time and between different institutes:

module.exports = "http://aelita:8081"

Starting webpack-dev-server

package.json file

webpack is built on top of Node. The package.json file is used by npm, the Node Package Manager, to start/stop node services and to handle dependencies. That's why a package.json is also needed with webpack. The file is JSON encoded. Here is the basic configuration to be able to do npm start in order to start webpack-dev-server properly for MXCuBE 3 project:

{
  "scripts": {
    "start": "npm run dev",
    "dev": "./node_modules/.bin/webpack-dev-server --hot --inline --colors --host 0.0.0.0 --port 8090 --display-error-details --content-base mxcube3/static"
  },
  "name": "mxcube3",
  "version": "0.1.0",
  "main": "main.jsx",

The webpack executable is found in ./node_modules/.bin/webpack-dev-server, which means webpack has been installed locally in the ./node_modules directory.

Dependencies

Dependencies are specified in package.json file:

  "dependencies": {
    "react": "0.14.0",
    "react-dom": "0.14.0",
    "isotope-layout": "2.2.2",
    "classnames": "2.2.0",
    "bootstrap": "3.3.5",
    "bootstrap-webpack": "0.0.5",
    "jquery": "2.1.4",
    "x-editable": "1.5.1"
  },
  "devDependencies": {
    "babel-core": "5.8.29",
    "babel-loader": "5.3.2",
    "css-loader": "0.21.0",
    "exports-loader": "0.6.2",
    "extract-text-webpack-plugin": "0.8.2",
    "file-loader": "0.8.4",
    "http-server": "0.8.5",
    "img-loader": "1.2.0",
    "imports-loader": "0.6.5",
    "json5": "0.4.0",
    "less": "2.5.3",
    "less-loader": "2.2.1",
    "react-bootstrap": "^0.27.3",
    "react-hot-loader": "^1.3.0",
    "script-loader": "0.6.1",
    "style-loader": "0.13.0",
    "url-loader": "0.5.6",
    "webpack": "1.12.2",
    "webpack-dev-server": "1.12.1"

This list is built automatically when node modules are installed thanks to the --saved option:

npm -i module_name --saved

or

npm -i -D module_name --saved

(-D means dependency goes to devDependencies)

All dependencies can be installed in one go using npm install.