Tech Stack 2019: Frontend Media and Styling

John Tucker
codeburst
Published in
6 min readNov 24, 2018

--

Adding frontend media and styling to the core of a technology stack for 2019.

This is one of a number of articles that add incremental functionality to the frontend and backend applications developed in the article Tech Stack 2019: Core.

The final result of this article is available from the frontend-media-styling branch of the tech-stack-2019-frontend (TODO). The backend is unchanged from the core example in tech-stack-2019-backend.

Overview

In effort to create a minimal core frontend solution (webpack configuration is only 32 lines long), we did not include support for media (images) and styling (CSS). Obviously in most any frontend solution we are going to require these.

The webpack Asset Management documentation provides a solid foundation; we will just add a TypeScript twist along with a couple improvements.

Media

Following the webpack documentation, we install file-loader:

npm install --save-dev file-loader

and update webpack.config.js:

...
module.exports = (env) => ({
...
module: {
rules: [
...
{
test: /\.(png|svg|jpg|gif)$/,
use: [
'file-loader'
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [
'file-loader'
],
},
],
},
...
});

Additionally, because we are using TypeScript, we need to create module declarations, e.g., src / types / import-jpg.d.ts, for each of the media types (to be explicit, we use one file per media type).

With this in place we can update our App component (src / components / App / index.tsx); also putting it and an image into its own folder:

...
import cat from './cat.jpg';
...
class App extends PureComponent {
...
public render() {
...
return (
<div>
<div>hello {hello}</div>
<img src={cat} />
</div>
);
}
}
...

Observations:

  • Notice the strategy of placing the media along-side of the component instead of a global assets folder

CSS

Let begin to style using old-school (will explain later) CSS:

npm install --save-dev style-loader
npm install --save-dev css-loader

We then update webpack.config.js:

...
module.exports = (env) => ({
...
module: {
rules: [
...
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
],
},
...
],
},
...
});

With this, we can create a stylesheet (src / components / App / styles.css):

Observations:

  • Notice, like the image before, we store the component’s CSS alongside the component code

We update the App (src / components / App / index.tsx) component to use it:

...
import './styles.css';
...
class App extends PureComponent {
public render() {
...
return (
<div>
<div className="app__hello">hello {hello}</div>
...
</div>
);
}
}
...

CSS Modules

The way we previously (old-school CSS) implemented our styling has a major problem; CSS’s has a global namespace, i.e., we have to worry about avoiding naming collisions (that is why we prefixed the class with app__. This is contrary to the modular approach espoused by React.

The solution is, you guessed it, CSS Modules. To use, update webpack.config.js:

...
module.exports = (env) => ({
...
module: {
rules: [
...
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
...
],
},
...
});

With CSS Modules, we no longer need to worry about CSS naming collisions (outside the module itself) and thus can drop the app__ in the class name (src / components / App / styles.css):

.hello {
color: green;
}

note: Because we are using these names in JavaScript it is important to use JavaScript (camel-case) naming here

We update the App (src / components / App / index.tsx) component to use it:

...
import styles from './styles.css';
...
class App extends PureComponent {
public render() {
...
return (
<div>
<div className={styles.hello}>hello {hello}</div>
...
</div>
);
}
}
...

One problem, however, is that because we are using TypeScript, we have to also create a matching TypeScript definition file (src / components / App / styles.css.d.ts); without it the code will not compile.

export const hello: string;

Luckily, there is another webpack loader, typings-for-css-modules-loader, that supplements CSS Loader by automatically creating the necessary TypeScript definition files from the CSS files:

npm install --save-dev typings-for-css-modules-loader

And we update webpack.config.js to use it:

...
module.exports = (env) => ({
...
module: {
rules: [
...
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'typings-for-css-modules-loader',
options: {
modules: true,
namedExport: true,
},
},
],
},
...
],
},
...
});

note: When using typings-for-css-modules-loader, you (unless you are careful) will cause the compiler to fail when you create a new CSS file; simply restart the compiler and all is well. This is because you are likely trying to use the imported classes in TypeScript before this loader created the necessary TypeScript definition files (a sort of a chicken-and-egg problem).

CSS Modules — Under the Hood

Under the hood, CSS Modules works by generating an unique hash for imported CSS assets, for example our imported hello class becomes:

The problem, however, with this is that during development it hard to troubleshoot with these hash names. The solution is a subtle change to webpack.config.js:

...
module.exports = (env) => ({
...
module: {
rules: [
...
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'typings-for-css-modules-loader',
options: {
localIdentName: env.NODE_ENV === 'production'
? '[hash:base64]'
: '[name]__[local]__[hash:base64:5]',
...
},
},
],
},
...
],
},
...
});

with a result of:

Flash of Un-styled Content (FOUC)

With the current configuration, running

npm run build

generates the following output:

Observations:

  • The jpg image (with hash name) is the imported cat image
  • There is no CSS file; the style-loader wraps the CSS with JavaScript code in bundle.js; that JavaScript code injects the CSS into the DOM when the component module loads

The problem with this approach is that, depending on timing, React can render DOM elements before the CSS is injected / processed. The result is a Flash of Un-styled Content (FOUC).

To address this, we configure webpack to extract all the CSS from the JavaScript bundle and put it into a traditional stylesheet that we can load in the HTML head; this has a side benefit of shrinking the sized of the JavaScript bundle.

We first install mini-css-extract-plugin:

npm install --save-dev mini-css-extract-plugin

and update webpack.config.js:

...
module.exports = (env) => ({
...
module: {
rules: [
...
{
test: /\.css$/,
use: [
{
loader: env.NODE_ENV === 'production' ?
MiniCssExtractPlugin.loader :
'style-loader',
},
...
],
},
...
],
},
...
plugins: [
...
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
],
});

Finally, we need to update (public / index.html) to use the extracted CSS file.

...
<html lang="en">
<head>
...
<link rel="stylesheet" type="text/css" href="/main.css">
</head>
...
</html>

With this configuration we get the performance benefit of the bundled CSS during development and the extracted CSS in production, e.g., running:

npm run build

We now see the extracted main.css:

SASS / LESS / ETC

Now that we have the basic CSS infrastructure in place, we can layer in additional CSS tools as desired, e.g.:

Wrap Up

This is one of a number of incremental features we will add to the core backend and frontend solutions. If you are interested in getting updates, would recommend that you follow me.

--

--