Can you remove dashes from margin and padding classes in tailwind? - tailwind-css

While using tailwind, is it possible to remove the dashes from margins and padding selectors?
For example:
.mb-1 would be .mb1
.-mx-1 would be .mxn1
I personally think this looks cleaner and wanted to know if it was possible.

It is possible through plugins, but requires a lot of extra work. Pulling from these two (one, two) github threads, here is an example of a plugin that removes the dash from the mb-# class, ie: .mb-1 will become .mb1:
import _ from 'lodash'
export default function(variants) {
return function ({ addUtilities, config }) {
const utilities = _.map(config('margin'), (value, modifier) => {
return defineClass(`mb${modifier}`, {
'margin-bottom': `${value}`,
})
})
addUtilities(utilities, variants)
}
}
Essentially the plugin is creating its own custom classes for the margin bottom class. It would take a lot of work to do this for each module but, again, it is possible. ps: the above plugin requires you to disable the default tailwind margin in your tailwind.config.js file.
module:
modules: {
// ...
- margin: [...],
+ margin: false,
// ...
}

Related

Is there a proper place to put tailwindcss classes that you explicitly need loaded (besides inline comments)?

I have an array of colors gray-200, red-200, blue-200, green-200 that I also need hover: and border- variants (e.g. hover:gray-200).
If I explicitly add a comment (/* ... */) explicating the exhaustive list of permutations (hover:gray-200, border-blue-200, etc.), the styles load properly. Presumably because of (purgeable html?), I can't just dynmically create these on the fly (when I do, the CSS doesn't load).
The inline comment feels like an understandable location, but not the ideal spot to put all that. Is there a "proper" place to do this (like in the config file or something), and if so, how/where?
Forgive the ignorance if this is obvious, I'm new to tailwind.
EDIT: If it matters, I'm using React and NodeJS and yarn.
As you mentioned you cannot build dynamic CSS classes on the fly. In order to work you need to mention somewhere in your app full class name or safelist it. It can be other file, comment or safelist section in Tailwind configuration file
Please note there are no such utility as hover:gray-200 etc as you need to be specific what do you colorize (like text - hover:text-gray-200, background - hover:bg-gray-200 etc)
1. Pass an array of strings
Every part will be compiled as it is
// tailwind.config.js
module.exports = {
safelist: [
'text-gray-200',
'bg-gray-200',
'border-gray-200',
'hover:text-gray-200',
'hover:bg-gray-200',
'hover:border-gray-200',
// ....
],
}
2. In a specific file
Common pattern to create and name file safelist.txt, put in a root of your project and watch its content. The content of the file could be any, extension could be any, name could be any, there are only two rules - it should contain full names of required classes (like in example 1) and this file should be included in content section
// tailwind.config.js
module.exports = {
content: [
// source files,
'./safelist.txt'
]
}
These both methods not ideal as you need to write a lot of classes on your own. However tailwind.config.js is still JS file so you can do something like this
3. Using mapping
Create an array of required color utilities and map them to generate required class names. This way it can be dynamic
// tailwind.config.js
const colors = ['gray-200', 'red-200']
const safelist = colors.map(color => `text-${color} bg-${color} border-${color} hover:text-${color} hover:bg-${color} hover:border-${color}`)
module.exports = {
safelist:,
}
Still not perfect - sometimes thing can be messy and hard to read but it is simple
4. Regular expressions
Finally Tailwind provides you a way to use patterns for safelisting
// tailwind.config.js
module.exports = {
// an array of multiple patterns
safelist: [
{
pattern: /(bg|text|border)-(red|gray)-200/,
variants: ['hover'],
},
],
}
Here we're saying safelist combination of background, color, border-color properties with red-200 and grey-200 utilities plus hover variant for all of them. Variants could be any, if you need you should pass breakpoint variants also and combination of them. Take a look
// tailwind.config.js
module.exports = {
// just as example you can specify multiple patterns for each utility/variant combination
safelist: [
{
pattern: /(bg|text)-(red|gray)-200/,
variants: ['hover'],
},
{
pattern: /border-(red|gray)-500/, // another color
variants: ['hover', 'lg', 'lg:hover']
}
],
}
One last thing about safelisting colors - if you left pattern like this /(bg|text|border)-(red|gray)-200/ it would safelist all color utilities opacity included (bg-gray-200/0, bg-gray-200/5, bg-gray-200/10 and so on).
/** part of compiled CSS file */
.border-gray-200 {
--tw-border-opacity: 1;
border-color: rgb(229 231 235 / var(--tw-border-opacity))
}
.border-red-200 {
--tw-border-opacity: 1;
border-color: rgb(254 202 202 / var(--tw-border-opacity))
}
.border-gray-200\/0 {
border-color: rgb(229 231 235 / 0)
}
.border-gray-200\/5 {
border-color: rgb(229 231 235 / 0.05)
}
/** and so on - many-many classes */
If you are planning not to use opacity variants, finish your pattern with $ dollar sign
// tailwind.config.js
module.exports = {
// an array of multiple patterns
safelist: [
{
pattern: /(bg|text|border)-(red|gray)-200$/, // here is $ - no opacity utilities
variants: ['hover'],
},
],
}
Here - check different types of safelist config and Generated CSS (tab at the bottom - it will show all compiled classes)

Vue 3 with Tailwind using v-bind:class shows classes in html element but not render while style works correctly

I am new to Vue and I'm trying to bind multiple classes in a v-for loop from a const array of object imported from a file.js.
But the trick I'm trying is to import const and than return classes from method that evaluates one property of object looped.
I've tried all ways, methods, computed, setup, onMounted, beforeMount, but even if i can see my classes in html they aren't rendered in styles section of DevTools.
The only way that works is to v-bind:style instead of class. Or just put exact classes in my const array object as a property but I prefer to avoid this.
It seems to save something in cache, but i have tried to delete and to lunch application in hidden mode but it won't works
Is there someone who can help me to understand and maybe to resolve?
Thanks in advance
this is my actual code:
<template>
<div id="cv" class="tp3-flex md:tp3-grid md:tp3-grid-cols-[repeat(27,_minmax(0,_1fr))] md:tp3-grid-rows-[repeat(6,_minmax(0, 5rem))] tp3-justify-center tp3-content-center tp3-justify-items-center tp3-mx-auto tp3-p-2 tp3-bg-cyan-500 tp3-text-blue-50">
<div v-for="(softSkill, index) in softSkills" :key="`softSkill-${index}`"
class="tp3-flex tp3-w-20 tp3-h-20 -tp3-rotate-45 tp3-rounded-full tp3-rounded-tr-none tp3-justify-center tp3-items-center tp3-bg-slate-400 tp3-opacity-70 tp3-mb-4 tp3-mt-4 tp3-shadow-md tp3-overflow-hidden"
v-bind:class="posCols(softSkill)">
<div class="tp3-rotate-45">
<span v-html="softSkill.text"></span>
</div>
</div>
</div>
</template>
<script>
import {softSkills} from "#/assets/skills/softSkills";
export default {
name: "ComponentSoftSkills",
data(){
return{
softSkills: null
}
},
beforeMount() {
this.softSkills = softSkills;
},
methods: {
posCols(softSkill){
console.log(softSkill);
return ' tp3-col-start-['+softSkill.col+'] tp3-col-end-['+(softSkill.col+1)+']';
}
}
}
</script>
<style lang="css" scoped>
</style>
and my file.js is:
export const softSkills = [
{text:`skill 1`, col:1, row:1},
{text:`skill 2`, col:5, row:1},
{text:`skill 3`, col:2, row:2},
{text:`skill 4`, col:15, row:1},
]
I have a suspicion that this might be due to your tailwind setup.
Because the classes are assigned dynamically and tailwind (depending on the configuration) is only making classes available that it can find during compilation. So the classes, even though you see them populated correctly, are not made available through tailwind. simply put, when tailwind scans the code, it doesn't recognize md:tp3-grid-cols-[repeat(27,_minmax(0,_1fr))] or tp3-col-start-[${softSkill.col}] as a valid class name and does not generate the class for it.
Assuming this is the issue and not knowing the exact version on configuration can't give an exact solution, but here are some tips for it.
Instead of using dynamic class names, define all the class names and assign dynamically
so instead of using tp3-col-start-[${softSkill.col}] tp3-col-end-[${(softSkill.col+1)}]
you could make sure all possible classes are clear and accessible by the tailwind parser:
let colClass = `tp3-col-start-[0] tp3-col-end-[1]`;
if(softSkill.col === 1) colClass = "tp3-col-start-[1] tp3-col-end-[2]";
if(softSkill.col === 2) colClass = "tp3-col-start-[2] tp3-col-end-[3]";
if(softSkill.col === 3) colClass = "tp3-col-start-[3] tp3-col-end-[4]";
if(softSkill.col === 4) colClass = "tp3-col-start-[4] tp3-col-end-[5]";
if(softSkill.col === 5) colClass = "tp3-col-start-[5] tp3-col-end-[6]";
// ...etc
this is obviously very verbose, but the classes are clearly defined in the code, so tailwind can find them when scanning your code.
Safelisting classes
using safelisting of classes is another option. Instead of having the code in your js, you would have it in the configuration
// tailwind.config.js
module.exports = {
// ...other stuff
safelist: [
'tp3-col-start-[0]',
'tp3-col-start-[1]',
'tp3-col-start-[2]',
'tp3-col-start-[3]',
'tp3-col-start-[4]',
'tp3-col-start-[5]',
// ...etc
'tp3-col-end-[1]',
'tp3-col-end-[2]',
'tp3-col-end-[3]',
'tp3-col-end-[4]',
'tp3-col-end-[5]',
// ...etc
],
}
there's also a way to use regex, which might look something like this:
// tailwind.config.js
module.exports = {
// ...other stuff
safelist: [
{
pattern: /tp3-col-start-[(0|1|2|3|4|5|6|7|8|9|10|11|12|13|14|15)]/,
variants: ['sm', 'lg'], // you can add variants too
},
{
pattern: /tp3-col-end-[(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15|16)]/,
},
],
}
you can read more about safelisting here safelisting-classes

Material UI 5 class name styles

I migrated from Mui 4 to 5 and wonder how to use class names. If I want to apply certain styles to just one component there is the SX property. However, I'm struggling with using the same class for multiple components. In v4 my code looked like this:
export const useStyles = makeStyles((theme: Theme) =>
createStyles({
root: {
padding: theme.spacing(1),
margin: 'auto',
},
})
)
I could import this useStyles hook in any component and use it like this:
const classes = useStyles()
...
<div className={classes.root}>...</div>
This docs say, that I can 'override styles with class names', but they don't tell how to do it:
https://mui.com/customization/how-to-customize/#overriding-styles-with-class-names
Do I have to put these styles in an external CSS file?
.Button {
color: black;
}
I would rather define the styles in my ts file.
I also found this migration guide:
https://next.material-ui.com/guides/migration-v4/#migrate-makestyles-to-emotion
I don't like approach one, because using this Root wrapper, it is inconvenient to apply a class conditionally. (Especially for typescript there is some overhead) Approach two comes with an external dependency and some boilerplate code.
Ideally I would use styles like this, perhaps with one rapper function around the styles object:
export const root = {
padding: theme.spacing(1),
margin: 'auto',
}
<div className={root}>...</div>
Of course, the last approach doesn't work, because className wants a string as input. Does anybody know an alternative with little boilerplate code?
I suggest you take a look at emotion's documentations for details. The sx prop is actually passed to emotion.
You can do something like this:
const sx = {
"& .MuiDrawer-paper": {
width: drawerWidth
}
};
<Drawer sx={sx}/>
Equivalent to MUI v4
const useStyles = makeStyles({
drawerPaper: {
width: drawerWidth,
}
});
const classes = useStyles();
<Drawer
classes={{
paper: classes.drawerPaper,
}}
/>
Answering your exact question, there are use cases (I think yours is not one of them and you should use styled components) however for those like me who stumble upon it and want a "exact answer to this question" and not a "do this instead", this is how you achieve to retrieve the class names.
This is so far undocumented.
For functional components, using emotion, here an use case where you have a 3rd party component that expects, not one, but many class names, or where the className property is not where you are meant to pass the property.
import { css, Theme, useTheme } from "#mui/material/styles";
import { css as emotionCss } from "#emotion/css";
const myStyles = {
basicClass: {
marginLeft: "1rem",
marginRight: "1rem",
paddingLeft: "1rem",
paddingRight: "1rem",
},
optionClass: (theme: Theme) => ({
[theme.breakpoints.down(theme.breakpoints.values.md)]: {
display: "none",
}
})
}
function MyComponent() {
cons theme = useTheme();
// first we need to convert to something emotion can understand
const basicClass = css(myStyles.basicClass);
const optionClass = css(myStyles.optionClass(theme));
// now we can pass to emotion
const basicClassName = emotionCss(basicClass.styles);
const optionClassName = emotionCss(optionClass.styles);
return (
<ThirdPartyComponent basicClassName={basicClassName} optionClassName={optionClassName} />
)
}
When you have a Class Component, you need to use the also undocumented withTheme from #mui/material/styles and wrap your class, if you use the theme.
WHEN IT IS NOT AN USE CASE
When your component uses a single className property just use styled components.
import { styled } from "#mui/material/styles";
const ThrirdPartyStyled = styled(ThirdPartyComponent)(({theme}) => ({
color: theme.palette.success.contrastText
}))
Even if you have dynamic styles
import { styled } from "#mui/material/styles";
interface IThrirdPartyStyledExtraProps {
fullWidth?: boolean;
}
const ThrirdPartyStyled = styled(ThirdPartyComponent, {
shouldForwardProp: (prop) => prop !== "fullWidth"
})<IThrirdPartyStyledExtraProps>(({theme, fullWidth}) => ({
color: theme.palette.success.contrastText,
width: fullWidth ? "100%" : "auto",
}))
Even if each one has some form of custom color, you just would use "sx" on your new ThrirdPartyStyled.
When you are just trying to reuse a style around (your use case)
const myReusableStyle = {
color: "red",
}
// better
const MyStyledDiv = styled("div")(myReusableStyle);
// questionable
const MySpanWithoutStyles = styled("span")();
// better
const MyDrawerStyled = styled(Drawer)(myReusableStyle);
function MyComponent() {
return (
<MyStyledDiv>
questionable usage because it is less clean:
<MySpanWithoutStyles sx={myReusableStyle}>hello</MySpanWithoutStyles>
<MySpanWithoutStyles sx={myReusableStyle}>world</MySpanWithoutStyles>
these two are equivalent:
<MyDrawerStyled />
<Drawer sx={myReusableStyle} />
</MyStyledDiv>
)
}
Now what is "presumably" cool about this is that your style, is just an object now, and you can just import it and use it everywhere without makeStyles or withStyles, supposedly an advantage, even when to be honest, I have never used that of exporting/importing around; the code seems a bit cleaner nevertheless.
You seem to want to use it so all you do is.
export const myStyles {
// your styles here
}
because this object is equivalent in memory, and it is always the same object, something that is easier to mess up with styles, it should be as effective or even more than your hook, theoretically (if it re-renders often even when setup may be longer), which stores the same function in memory but returns a new object every time.
Now you can use those myStyles everywhere you deem reasonable, either with styled components or by assigning to sx.
You can further optimize, say if it's always a div that you use that is styled the same way, then the styled component MyStyledDiv should be faster, because it is the same and done each time. How much faster is this? According to some sources 55% faster, to me, it is taking 4 weeks of refactor and the JSS compatibility with emotion is bad, all mixed with SSR is making everything unusable and slow and broken, so let's see until then when the whole is refactored.
Here is a pattern that I've found useful in MUI 5. It allows you to keep style definitions in the same file but isolated, & avoids repeated function calls for every CSS property where you need to access your theme (e.g. width: ({ spacing }) => spacing(12))). It also feels similar to MUI's native CSS API.
Create a function that takes your theme as an argument & returns an object of named style groups. Then reference those groups directly in your sx props. This also allows for the use of classNames in a way similar to Material-UI 4.
import { useTheme } from '#mui/material';
import clsx from 'clsx';
export const NavItem = (props) => {
// Bring in style groups
const sx = styles(useTheme());
// Define classNames
const classNames = clsx({
isActive: props.isActive
});
return (
{/* Use classNames and style groups */}
<ListItemButton className={classNames} sx={sx.button}>
<ListItemAvatar sx={sx.avatar}>{props.icon}</ListItemAvatar>
<ListItemText>{props.label}</ListItemText>
</ListItemButton>
);
}
// Define style groups
function styles(theme) => {
return {
button: {
paddingX: 6,
'&.isActive': {
backgroundColor: theme.palette.secondary.light
}
},
avatar: {
'.isActive &': {
border: '2px solid green'
}
}
};
}
I'm in the same boat, about six months behind, i.e., starting to make the transition to v5 from v4 now... Just when I thought I had a handle on it all!
Having read this post and trying a few things out, I was able to replicate the ability to re-use a chunk of css. I'm a big fan of what used to be the overrides prop; that feature hasn't gone away, it's just under a different prop (loosely speaking). Regardless, I mention it because it provides access to what I like a lot about css: selectors.
To hit all MUI-Drawers my pref is for whatever the new overrides is. For targeted reuse of css I like the following:
import { reuseThisCss } from 'sharedCss';
export default styled(Drawer)(({ theme, ownerState }) => {
...
return {
'& .MuiDrawer-paper': {
boxShadow: xxl,
border: 'none',
'& .MuiListItemText-root': reuseThisCss,
},
};
export default ThisSpecificDrawerVariant;
Note: The focus is not on using styled (It's not my goto approach).
The css in the return value is the equivalent to the following css: .MuiDrawer-paper .MuiListItemText-root {...}.
This says, "select all .MuiListItemText-root under the .MuiDrawer-paper parent. If I want to optimize the render, while increasing the dependency on a specific hierarchy, I'll specify/expand on the selector that much more with whatever lies between the .MuiDrawer-paper and MuiListItemText-root. For instance, in my case:
...
return {
'& .MuiDrawer-paper': {
boxShadow: xxl,
border: 'none',
'& > a > li > div > .MuiListItemText-root': reuseThisCss,
},
};
Finally, per a question in the comments, generally this will not prevent a nested application of the style. In my experience, marking each level with a className is useful. I only "mark" the element that signals the start of a new level. So, if it were Drawer in the above example, I would start the css selector with .MUI-Drawer.level-3. The rest of css remains the same.
I still have not figured out if whether setting the className dynamically remains a performant and sufficiently flexible goto... TBD.
If you are using makeStyles or withStyles to provide CSS class, you can follow the instruction below.
CSS overrides created by makeStyles

How to prefix Bootstrap 4 classes to avoid css classes conflict

I am working on building small applications that will live in a website. The website that will host these applications may or may not be using a css framework. I Want to prefix all Bootstrap classes with my own unique prefix.
To avoid "ANY INSTANCE or CHANCE" of conflict I want to prefix all Bootstrap CSS classes with - let's say - "year19-" prefix. So, for example, all the col- classes would now be year19-col- and all the .btn classes would now become .year19-btn, .year19-btn-primary, etc...
I know if I use the sass theme, new classes, then we would get around some of that as we can create our own prefixes using the theming approach, but JS would still remain a source of conflict if two versions of the same framework live on the same page. There was a Github project for Bootstrap 3 with the namespacing feature where you could just add your prefix in the namespace variable then compile the entire code to a CSS and JS package. Bootstrap 4 doesn't seem to have that package yet.
Also, I don't want to wrap the project with a css class. That approach is fine for some things, but not the right approach. I wouldn't even call that namespace. That is just wrapping the classes.
year19-btn-primary {
then this would be whatever the code that already existed there before, not touched.}
I managed to get classes prefixed for Bootstrap 5.1.3. You'll need to make the following changes before compiling Bootstrap yourself. My full implementation is available here: https://github.com/Robpol86/sphinx-carousel/tree/85422a6d955024f5a39049c7c3a0271e1ee43ae4/bootstrap
package.json
"dependencies": {
"bootstrap": "5.1.3",
"postcss-prefix-selector": "1.15.0"
},
Here you'll want to add postcss-prefix-selector to make use of it in postcss.
postcss.config.js
'use strict'
const prefixer = require('postcss-prefix-selector')
const autoprefixer = require('autoprefixer')
const rtlcss = require('rtlcss')
module.exports = ctx => {
return {
map: ctx.file.dirname.includes('examples') ?
false :
{
inline: false,
annotation: true,
sourcesContent: true
},
plugins: [
prefixer({
prefix: 'scbs-', // ***REPLACE scbs- WITH YOUR PREFIX***
transform: function (prefix, selector) {
let newSelector = ''
for (let part of selector.split(/(?=[.])/g)) {
if (part.startsWith('.')) part = '.' + prefix + part.substring(1)
newSelector += part
}
return newSelector
},
}),
autoprefixer({
cascade: false
}),
ctx.env === 'RTL' ? rtlcss() : false,
]
}
}
This is where the CSS will be prefixed. I'm using postcss instead of just wrapping bootstrap.scss with a class/id selector so I can use the Bootstrap 5 carousel component on Bootstrap 4 webpages (which is my use case). This will replace https://github.com/twbs/bootstrap/blob/v5.1.3/build/postcss.config.js
rollup.config.js
// ...
const plugins = [
replace({ // ***COPY/PASTE FOR OTHER BOOTSTRAP COMPONENTS***
include: ['js/src/carousel.js'], // ***YOU MAY NEED TO REPLACE THIS PATH***
preventAssignment: true,
values: {
'CLASS_NAME_CAROUSEL': '"scbs-carousel"', // ***USE YOUR PREFIXES HERE***
'CLASS_NAME_ACTIVE': '"scbs-active"',
'CLASS_NAME_SLIDE': '"scbs-slide"',
'CLASS_NAME_END': '"scbs-carousel-item-end"',
'CLASS_NAME_START': '"scbs-carousel-item-start"',
'CLASS_NAME_NEXT': '"scbs-carousel-item-next"',
'CLASS_NAME_PREV': '"scbs-carousel-item-prev"',
'CLASS_NAME_POINTER_EVENT': '"scbs-pointer-event"',
'SELECTOR_ACTIVE': '".scbs-active"',
'SELECTOR_ACTIVE_ITEM': '".scbs-active.scbs-carousel-item"',
'SELECTOR_ITEM': '".scbs-carousel-item"',
'SELECTOR_ITEM_IMG': '".scbs-carousel-item img"',
'SELECTOR_NEXT_PREV': '".scbs-carousel-item-next, .scbs-carousel-item-prev"',
'SELECTOR_INDICATORS': '".scbs-carousel-indicators"',
}
}),
babel({
// Only transpile our source code
// ...
Lastly rollup replace plugin is used to add the prefixes in the compiled javascript file. I wasn't able to find a way to just prefix the consts so I had to resort to having the entire const replaced and hard-coded with the full class names. This means you'll need to do this for every Bootstrap component that you're including in your final build (for me I just need the carousel so it's not a big deal).

Make webpack conditionally load additional or alternative css file

I'm trying to implement a kind of css theming in an angular 4 project. We use webpack 3 for bundling. The product is intended to be used by several companies and has to look according to their brandbooks. So we need themes.
We gonna have several builds, but we don't want to have several versions of code. All themes should remain in the same codebase. The differences are minimal: colors, icons, fonts — everything may be changed in css.
I have thought of several ways to do it, the most obvious would be to implement theming via :host-context for components and change the class of body by changing environment variable for webpack. With such method we will heve every theme inside our bundle, which is not good. Maybe there's another way?
I wonder if it is possible to have webpack load not the css file it is asked for. Instead it could look for another file by pattern, and if it exists, use that file instead of original one. Or load both files.
For example, we have a button.component.ts which imports button.component.css. If we don't tell webpack to use any theme, it works as usual. But if we do, it tries to read button.component.theme-name.css in the same directory. If that file exists, webpack imports it instead (or altogether with) the default file.
That's basically what I'm trying to do. I guess, the same mechanism would be useful for html templates in angular.
Is there a plugin to do such magic? Or maybe some sophisticated loader option? If you have another way to solve my task — feel free to drop a comment!
I created a loader which can append or replace the content of a loaded file with the content of its sibling which has a chosen theme's title in its name.
TL;DR
Create a file with loader.
Use it in webpack config.
Run webpack in THEME=<themeName> evironment.
theme-loader.js
const fs = require('fs');
const loaderUtils = require('loader-utils');
module.exports = function (mainData) {
const options = loaderUtils.getOptions(this);
let themeName = options.theme;
let mode = options.mode;
if (themeName) {
// default mode
if (!Object.keys(transform).includes(mode)) {
mode = 'replace';
}
// fileName.suffix.ext -> fileName.suffix.themeName.ext
const themeAssetPath = this.resourcePath.replace(/\.([^\.]*)$/, `.${themeName}.$1`);
const callback = this.async();
// for HMR to work
this.addDependency(themeAssetPath);
fs.readFile(themeAssetPath, 'utf8', (err, themeData) => {
if (!err) {
callback(null, transform[mode](mainData, themeData));
} else if (err.code === 'ENOENT') {
// don't worry! if it's not here then it's not needed
callback(null, mainData);
} else {
callback(err);
}
});
} else {
return mainData;
}
};
const transform = {
// concat theme file with main file
concat: (mainData, themeData) => mainData + '\n' + themeData,
// replace main file with theme file
replace: (mainData, themeData) => themeData
};
A piece of sample webpack.config.js to use this handmade loader:
resolveLoader: {
modules: [
paths.libs, // ./node_modules
paths.config // this is where our custom loader sits
]
},
module: {
rules: [
// component styles
{
test: /\.css$/,
include: path.join(paths.src, 'app'),
use: [
'raw-loader',
// search for a themed one and append it to main file if found
{
loader: 'theme-loader',
options: {
theme: process.env.THEME,
mode: 'concat'
}
}
]
},
// angular templates — search for a themed one and use it if found
{
test: /\.html$/,
use: ['raw-loader',
{
loader: 'theme-loader',
options: {
theme: process.env.THEME,
mode: 'replace'
}
}
]
}
]
}
For example, an app.component.css:
:host {
background: #f0f0f0;
color: #333333;
padding: 1rem 2rem;
display: flex;
flex-direction: column;
flex: 1;
justify-content: center;
}
nav {
/* ... */
/* something about nav element */
/* ... */
}
header {
/* ... */
/* pile of styles for header */
/* ... */
}
To implement dark theme we don't need to change all that flex and padding staff and maybe nav and header don't have their own background and font color settings. So we'll just have to override host element style. We create app.component.dark.css:
:host {
background: #222222;
color: #e0e0e0;
}
The we run webpack with environment variable THEME set to dark. The loader takes a request to process app.component.css, tries to load app.component.dark.css and voila! Themed css is appended to the end of resulting file. Because of cascade,
if multiple competing selectors have the same importance and specificity, … later rules will win over earlier rules (MDN).
For HTML we don't have such method. So we'll have to rewrite our template completely. Hopefully, you won't need to do it too often. I my case, I wanted to change like header and footer to fit the cutomer's branding demand.
This was my first attempt to create a webpack loader, please leave a comment if you see a problem with it.

Resources