Styling

With the basic component and tooling in place, we will now expand that to have a bit more content and then also include some styling. First, copy the contents of the <body> of the login.html file from week 1, paste it into the athena.jsx and adjust the classes to bootstrap, so that it looks like this:

import React from "react";
import ReactDom from "react-dom";

function Athena() {
    return (
        <div className="container-fluid gx-0">
        <header className="row gx-0">
            <nav className="navbar navbar-dark bg-dark" aria-label="Main">
            <ul className="navbar-nav horizontal" role="menu">
                <li className="navbar-brand">Athena - Study Portal</li>
                <li className="nav-item">
                <a
                    href=""
                    role="menuitem"
                    tabIndex="0"
                    className="nav-link active"
                >
                    My Modules
                </a>
                </li>
                <li className="nav-item">
                <a href="" role="menuitem" tabIndex="-1" className="nav-link">
                    My Exams
                </a>
                </li>
            </ul>

            <ul className="navbar-nav horizontal" role="menu" aria-label="User">
                <li className="nav-item">
                <a
                    href="profile.html"
                    className="nav-link"
                    role="menuitem"
                    tabIndex="0"
                >
                    Profile
                </a>
                </li>
                <li className="nav-item">
                <a
                    href="login.html"
                    className="nav-link"
                    role="menuitem"
                    tabIndex="-1"
                >
                    Logout
                </a>
                </li>
            </ul>
            </nav>
        </header>
        <main className="row justify-content-lg-center">
            <form className="col-6" action="index.html">
            <h1>Login</h1>
            <div className="form-group">
                <label>
                E-Mail Address
                <input
                    className="form-control"
                    type="email"
                    name="email"
                    placeholder="Your e-mail address"
                    tabIndex="1"
                    required="required"
                />
                </label>
            </div>
            <div className="form-group">
                <label>
                Password
                <input
                    className="form-control"
                    type="password"
                    name="password"
                    placeholder="Your password"
                    tabIndex="2"
                    required="required"
                />
                </label>
            </div>
            <div className="btn-container">
                <button className="button" tabIndex="3">Login</button>
            </div>
            </form>
        </main>
        <footer>
            &copy; 2018 - Mark Hall (
            <a href="mailto:mark.hall@informatik.uni-halle.de" tabIndex="0">
            mark.hall@informatik.uni-halle.de
            </a>
            )
        </footer>
        </div>
    );
}

ReactDom.render(<Athena />, document.getElementById("app-entry-point"));

There are a few rules in JSX that mean that attributes are spelled slightly differently to standard HTML. The first is that instead of class you must always use className. The second is that all attributes need to be camel-cased, so for example tabindex becomes tabIndex. Make those two changes and the code will compile without warnings in the browser and you will see an unstyled login page.

To add styling, we basically take the same approach as for the JSX support. We need to install additional packages for Webpack, so that it knows how to deal with style files:

$ yarn add sass-loader node-sass style-loader css-loader bootstrap

Next, update the webpack.conf.js, adding a module rule to deal with the scss files we use to create our styling:

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
    entry: path.resolve(__dirname, "src/js/athena.jsx"),
    output: {
        filename: "[name]-bundle.js",
        path: path.resolve(__dirname, "dist"),
        libraryTarget: "var",
        library: "Athena",
    },
    mode: "development",
    plugins: [
        new HtmlWebpackPlugin({
        title: "Athena",
        template: path.resolve(__dirname, "src/index.html"),
        inject: false,
        xhtml: true,
        }),
    ],
    module: {
        rules: [
            {
                test: /\.m?jsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: "babel-loader",
                    options: {
                        presets: ["@babel/preset-env", "@babel/preset-react"],
                        plugins: ["babel-plugin-transform-class-properties"],
                    },
                },
            },
            {
                test: /\.scss$/,
                exclude: /node_modules/,
                use: [
                    {
                        loader: "style-loader",
                    },
                    {
                        loader: "css-loader",
                    },
                    {
                        loader: "sass-loader",
                        options: {
                            sassOptions: {
                                includePaths: [path.resolve(__dirname, "node_modules")],
                            },
                        },
                    },
                ],
            },
        ],
    },
    resolve: {
        extensions: ["*", ".js", ".jsx"],
    },
    devServer: {
        static: {
            directory: path.resolve(__dirname, "dist"),
        },
        open: false,
        historyApiFallback: true,
    },
    optimization: {
        splitChunks: {
            chunks: "all",
            cacheGroups: {
                commons: {
                    test: /node_modules/,
                    name: "athena-vendor",
                    chunks: "initial",
                    minSize: 1,
                },
            },
        },
    },
};

Next, create a new directory src/styles and copy the app.scss``from ``week09/app/styles into that directory. The most important part of the addition is the includePaths which tells the SASS/SCSS compiler where to look for files included from our style files.

At this point, if you re-start the build server, you will see that the styling is still not being applied. This is because we haven’t told Webpack where to find it and include it. To include styling we use the standard JavaScript import function in the athena.jsx:

import React from 'react';
import ReactDOM from 'react-dom';

import '../styles/app.scss';

function Athena() {
    ...
}

Because we have configured a module to handle *.scss files, it knows what to do, when a SCSS file is imported and you will see that at this point the application is styled correctly.