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).
Related
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)
I'm trying to apply CSS modules concept agains my angular app, to order embed it into existing frontend with CSS which unfortunately overlaps. My project uses scss, I want webpack "modulize" my CSS after CSS if formed from scss on last build step I think.
I want to use CSS loader of webpack to achieve this.
But I couldn't make it work.
https://www.npmjs.com/package/#angular-builders/custom-webpack
to order customize my webpack config.
I've tried to apply the next configuration
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
loader: 'css-loader',
options: {
modules: true,
},
},
],
},
};
and got this error
ERROR in Module build failed (from ./node_modules/postcss-loader/src/index.js):
SyntaxError
(1:1) Unknown word
> 1 | exports = module.exports = require("../../../../css-loader/dist/runtime/api.js")(false);
I've tried to find and add css loader into existing
module.exports = (config, options) => {
const rules = config.module.rules || [];
rules.forEach(rule => {
if (String(rule.test) === String(/\.css$/)) {
rule.use.push({ loader: 'css-loader', options: { modules: true }})
}
});
return config;
};
Getting the same error. How to make it work?
Update 1:
I found that angular uses postcss which also provides this functionality as a plugin, postcss-modules. Also still can't make it work.
I was able to implement CSS Modules in Angular with the help of #angular-builders/custom-webpack as you suggested.
However, my solution use postcss-modules and posthtml-css-modules instead of the css-loader to hash the styles.
postcss-modules hash all the styles, then posthtml-css-modules replaces the class names on the html files by the hashed class names.
I documented my solution here:
Live Demo: https://angular-css-modules.herokuapp.com/
Github Repo: https://github.com/gquinteros93/angular-css-modules
Article with the step by step: https://indepth.dev/angular-css-modules/
I hope it will be useful for you.
We are developing a cloud application in angular 5 that will be deployed in a single instance but will be addressed to several customer.
The question concerns the management of themes, each customer wants to have his own theme (mainly colors).
The architecture (simplified) of the application looks like this:
--src
--app
app.component.html
app.component.scss
app.component.ts
-- component1
comp1.component.html
comp1.component.scss
comp1.component.ts
...
--scss
-- current
style.scss
_variables.scsc
-- customer1
style.scss
_variables.scss
-- customer2
style.scss
_variables.scss
...
Currently we are deploying by client, so there is no problem, we copy / paste the style in the current before the build.
Each component.scss imports _variables.scss to exploit the variables.
We would like that when a user logs on to the app, we detect the customer and choose the right style, but that the css is generated at compile time.
Is there a way to define global variables that can be modified in runtime and that impacts sub-components?
The solutions I tested:
Set in angular-cli a scss per customer, build and execute script js to modify the html link to css "href = 'assets / {customer} .bundle.css'". It works for global styles, but variables in subcomponents are not updated.
Use pure css to declare scope variables: root {--color-primary: gray; } and exploit them in the sub-components .test {color: var (- color-primary)}.
Make a JS script that will update all the global variables according to the client.
It works, but no equivalent in SCSS? strange
I do not know if I gave enough detail, thanks for the help.
As there is no proper way to do this; And solution used by Angular theming was not satisfactory to us; we've decided to use custom webpack builder that will compile our style based on the entries we provide. Please note, that we are not using SCSS in angular components explicitly.
"use strict";
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const AutoPrefixer = require("autoprefixer");
module.exports = {
entry: {
"modern_style.application": "./src/styles/modern_style.scss",
"default.application": "./src/styles/default.scss"
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].bundle.css"
})
],
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
ident: "postcss",
plugins: [AutoPrefixer()],
sourceMap: true
}
},
{
loader: "resolve-url-loader"
},
{
loader: "sass-loader",
options: {
precision: 8,
includePaths: [],
sourceMap: true
}
}
]
}
]
}
};
These entry files will have their variables set & customizations applied in each respective entry file, which looks like this:
// Entry file: modern_style.scss
$someVariableToBeUsedOrOverwritten: red;
#import "common/allModulesAreImportedHere"
.customRule{
//...
}
This generated style, e.g default.bundle.css is then loaded via <link rel="stylesheet" type="text/css" [href]="linkToTheme">
There is no way to pass any variables from ts to scss.
All you need is theming.
So for each customer you need a global body class whith its own set of variables / classes.
Check out angular material theming docs for example https://material.angular.io/guide/theming#defining-a-custom-theme
Mixins will solve your issue.
In the specific customer scss files, you would be holding the specific definition.
In the component.scss, you would be using the mixin, which is specific to the customer and it will resolve your issue on both compile and run time.
I use gulp to compile my sass file to css files, and reference the css file in my html. The project support theme switch. For example, I have 3 css theme files:
red.css
yellow.css
blue.css
I can currently switch the theme css like this:
var styleDom = $('#theme-style');
var newHref = 'styles/themes/' + themeName + '.css';
if (styleDom.attr('href') !== newHref) {
styleDom.attr('href', newHref);
}
Now I want to use webpack to load the css file.
require('styles/themes/red.css');
It seems work well, but I cannot find a way to switch the theme css file now, does anyone have a solution?
Your approach doesn’t need to change. Just use Extract Text plugin to save out the CSS files. You’ll need to make multiple entry points to create multiple CSS files.
OR
More ideally, (the approach I would take) make your CSS switch based on a different html or body class and just change the class. It won’t add much overhead, and it will be a more ideal UX when changing themes.
You'll need to use a combination of webpacks style-loader and file-loader (second example ) and use require.ensure (second example "dynamic imports") to accomplish this:
function switchTheme(name) {
// Remove the current theme stylesheet
// Note: it is important that your theme css always is the last
// <link/> tag within the <head/>
$('head link[rel="stylesheet"]').last().remove();
// Since webpack needs all filePaths at build-time
// you can't dynamically build the filePath
switch(name) {
case 'red':
// With require.ensure, it is possible to tell webpack
// to only load the module (css) when require is actually called
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/red.css');
});
case 'yellow':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/yellow.css');
});
case 'blue':
return require.ensure([], function () {
require('style-loader/url!file-loader!styles/themes/blue.css');
});
default:
throw new Error('Unknown theme "' + name + '"');
}
}
Then a call like switchTheme('blue') should do the trick.
And you might have to check your current webpack.config.js, in case you already have configured a loader for .css files.
I'm using Webpack with the extract-text-webpack-plugin.
In my project, I have some build scripts. One of the build scripts is supposed to bundle and minify CSS only. As I'm using Webpack for the other scripts, I found it a good idea to use Webpack even when I only want to bundle and minify CSS.
It's working fine, except that I can't get rid of the output.js file. I don't want the resulting webpack output file. I just want the CSS for this particular script.
Is there a way to get rid of the resulting JS? If not, do you suggest any other tool specific for handling CSS? Thanks.
There is an easy way, no extra tool is required.
There is an easy way and you don't need extra libraries except which you are already using: webpack with the extract-text-webpack-plugin.
In short:
Make the output js and css file have identical name, then the css file will override js file.
A real example (webpack 2.x):
import path from 'path'
import ExtractTextPlugin from 'extract-text-webpack-plugin'
const config = {
target: 'web',
entry: {
'one': './src/one.css',
'two': './src/two.css'
},
output: {
path: path.join(__dirname, './dist/'),
filename: '[name].css' // output js file name is identical to css file name
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: 'css-loader'
})
}
]
},
plugins: [
new ExtractTextPlugin('[name].css') // css file will override generated js file
]
}
Unfortunately, that is currently not possible by design. webpack started as a JavaScript bundler which is capable of handling other "web modules", such as CSS and HTML. JavaScript is chosen as base language, because it can host all the other languages simply as strings. The extract-text-webpack-plugin is just extracting these strings as standalone file (thus the name).
You're probably better off with PostCSS which provides various plugins to post-process CSS effectively.
One solution is to execute webpack with the Node API and control the output with the memory-fs option. Just tell it to ignore the resulting js-file. Set the output.path to "/" in webpackConfig.
var compiler = webpack(webpackConfig);
var mfs = new MemoryFS();
compiler.outputFileSystem = mfs;
compiler.run(function(err, stats) {
if(stats.hasErrors()) { throw(stats.toString()); }
mfs.readdirSync("/").forEach(function (f) {
if(f === ("app.js")) { return; } // ignore js-file
fs.writeFileSync(destination + f, mfs.readFileSync("/" + f));
})
});
You can clean up your dist folder for any unwanted assets after the done is triggered. This can be easily achieved with the event-hooks-webpack-plugin
//
plugins: [
new EventHooksPlugin({
'done': () => {
// delete unwanted assets
}
})
]
Good Luck...