These post is the first of a trilogy. This is where it all began... ๐ง
๐ Focus on | ๐ CSS Handling | Parts |
---|---|---|
(Index) | #๏ธโฃ | |
development only | inline CSS | ๐ |
both dev & prod | mini-css-extract-plugin | 2๏ธโฃ |
production only | CSS modules | 3๏ธโฃ |
Example Code ๐
Final Product ๐ค
At the end of the first part you will have built a flexible scaffolding for your webpack configuration. Hard work will be rewarded in later parts.
The first-stage final product will run only in development and will be able to inject CSS directly in the bundle.
Flow of Thought ๐ฎ
- Install packages
- Add start script
- Add JavaScript and CSS file
- Instruct webpack
- Read env
- Custom loader
- Inject rules + interface
Implementation ๐ค
- 1 - Install packages
Donwload the webpack triad, the two loaders and the only needed plugin. In the terminal invoke npm i -D webpack webpack-cli webpack-dev-server css-loader style-loader html-webpack-plugin
.
- 2 - Add start script
If not present, quickly create a package.json
by calling npm init -y
. Add a script to activate the server in development mode:
package.json
{
...
"scripts": {
"start": "webpack-dev-server --env development"
},
...
}
- 3 - Add JavaScript and CSS file
Create a src
folder. In there add a style.css
and give it some instruction:
style.css
body {
background-color: indigo;
color: white;
padding: 30px;
}
In the src
folder place a index.js
. By default, webpack is educated to look for an entry at this path (/src/index.js).
For the purpose of this demonstration it is sufficient to add simply the import of the stylesheet:
index.js
import './style.css';
Of course, in a real project it is advisable to use a nested architecture.
- 4 - Instruct webpack
If you try to give the command npm start
you will see that the server actually starts, finds index.js
and reads it. but when itโs time to read the css it starts to complain - webpack still does not know the css language.
We can fix that creating in the root a configuration file named webpack.config.js
.
The hardcoded solution is pretty linear:
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
devServer: {
open: true,
stats: 'errors-only',
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Inline CSS',
}),
],
};
In the terminal call npm start
- the result is there, your favorite color is smeared on the newly opened tab.
However, this type of approach is not flexible and after a few additions we will start to suffer confusion.
I want to build my personal webpack-๐ค with these features:
- able to integrate various parts
- each with a clear interface
- double behavior based on environment variable
- (perhaps also polite and clean)
- 4|a - Instruct webpack | Read env
First export a function that returns a configuration object:
webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const useRules = require('./config/webpack.useRules');
module.exports = (env) => ({
devServer: {
open: true,
stats: 'errors-only',
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Inline CSS',
}),
],
module: useRules(env),
});
useRules it does not exist yet. However we can note that it requires a single argument, env. We will instruct useRules to return different rules (then different webpack behaviors) for different environments.
- 4|b - Instruct webpack | Custom loader
In the root create a config
folder. In it place loaders.js
. Here we can store all loaders that could be needed in the future. The following piece of code exposes the function loadCSS
that may or may not get a config
argument.
loaders.js
function addConfigs(r, c) {
Object.entries(c).forEach(([key, value]) => {
if (!value) return;
r[key] = value;
});
return r
}
exports.loadCSS = (config = {}) => {
// basic rule
const rule = {
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
};
return addConfigs(rule, config);
};
The reference code below rappresent the output. Not passing any configuration object, it would lack of the exclude property (that is indeed the default output).
loadCss({ exclude: ['node_modules'] })
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
exclude: ['node_modules']
};
Thanks to
style-loader
, the previously CSS parsed bycss-loader
is injected directly in the bundle. That's okay when developing but we'll need another solution for production. This file will contain the production-funtion (addressed in the second chapter).
- 4|c - Instruct webpack | Inject rules + interface
Lastly we need an adapter between the newly created function and the core webpack configuration file. Why not take advantage of it and build one that in addition to returning an informative message, allow in the future a simpler implementation of new loaders?
In the config
folder create useRules.js
. At first, the following code could seem overwhelming, but we are going through it togheter.
useRules.js
const { loadCSS } = require('./loaders'); //[1]
module.exports = (env) => { //[2]
const loaders = { //[3]
css: (i) => {
switch (i) {
case 'inline':
return loadCSS()
default:
throw new Error('production is not implemented yet');
}
},
};
// developer interface [4]
const instructions = {
css: {
development: 'inline',
production: 'extract',
},
};
// business logic [5]
let message = '[useRules] ';
const rules = Object.entries(instructions).map(([key, value]) => {
const i = instructions[key][env];
message += key + '|' + i;
return loaders[key](i);
});
console.info(message);
return { rules };
};
- Import the formerly made loader.
- Export a function that gets env
- Create a container of functions. Add one inside that will manage the CSS. It gets an instruction (i) as 'inline', 'extract' or 'modules' and activates the appropriate loader. At this point we only have one loader - it will be used when i is equal to 'inline'.
- Since we don't want to get hands dirty every time a loader need to be added, we are building a developer interface - it's just a set of informations (once again, only for CSS right now) that the developer can easily change and otherwise quickly get a different behavior.
- The last piece is iterate for each instruction (only one right now) and extract the env-associated command.
Checking the Outcome ๐
When using
npm start
, env equals development. So i is 'inline'. This causes the activation of the loader that, not by chance, injects the CSS in the bundle.Let's suppose we already had built another loader and we had associated it with its case in the switch - simply changing the command in the developer interface [4] we could observe the different behavior.
Peek in the console - you should see
[useRules] css|inline
.
Top comments (0)