Global variable in SCSS / SASS with angular components - css

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.

Related

Angular custom webpack config enable CSS modules

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.

ExtractTextWebpackPlugin always outputs file, even if there are no changes

I have a project that bundles the client-side files using Webpack. We are bundling the CSS using the ExtractTextWebpackPlugin. The problem is when I edit a javascript file the CSS bundle always gets re-built despite there being absolutely no changes to the CSS state.
How can I bundle CSS but only when there are changes to the CSS files?
Extracted from my webpack config:
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: 'css-loader'
})
},
...
plugins: [
new ExtractTextPlugin(isDebug ? '[name].css' : '[name].[chunkhash].css')
],
whenever ExtractTextPlugin sees a import statement with css extension it will automatically extract the text contant of that css file whethere it changes or not
if you are using it to debug then use style-loader and HMR(Hot Module Replacement) for better experence something like this
isDebug
? {
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
: {
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: "css-loader"
})
}
if you want to use current configration and don't want ExtractTextPlugin to build css file and you are importing them in your javascript file using import then somehow you have to remove import statement for css files when there is no change in that css file
and if you are adding css file in your webpack config entry section then it would be easy because webpack allow custom command argument and you can do this by exporting a function in your webpack config file insted of object
//webpack.config.js
module.exports = function(env) {
return {
entry: {
main: env.includeCss
?
["./src/index.js", "./src/main.css"] //build with css
: ["./src/index.js"] //build without css
},
.
.
.
.
}
}
you can pass env.includeCss by command argument like this
webpack --config ./webpack.config.prod.js --env.includeCss
//notice --env.includeCss witch will set env.includeCss as true
run with --env.includeCss normally and without --env.includeCss when you dont want to compile css file
Not directly related to your question but I noticed you use [chunkhash] for extracted CSS. This will make your css name change even if you haven't change content in you CSS files.
Use [contenthash] instead.
https://survivejs.com/webpack/optimizing/adding-hashes-to-filenames/#setting-up-hashing
If you are using ExtractTextPlugin, you should use [contenthash]. This way the generated assets get invalidated only if their content changes.

White label sass/css with Webpack

I'm trying generate multiple css files with webpack for a white labeled application.
I want to run the webpack css/sass loaders one time for each 'brand' and output it in to a separate file. I want to do this as I have a separate variable file for each brand and would want the sass to compile one css file for each brand.
I think I can do this with grunt/gulp, but I wanted to avoid including them in this project.
I've been working on this exact problem lately. I found the i18n example config to be illustrative of how it's possible to do this. Basically the webpack config can be an array. You could do something like this.
import webpack from 'webpack';
export default [
{
entry: {
'brand-1': ['path/to/vars-1.scss', 'path/to/my.scss'],
}
// ...
},
{
entry: {
'brand-2': ['path/to/vars-2.scss', 'path/to/my.scss'],
}
// ...
},
];
Obviously you could generate the list, but I wrote it out for clarity. Our SCSS variables are dynamic, so I wrote a script that creates the webpack config and then injects the variables using the sassLoader.data option.
You may also want to use webpack-merge in order to separate the configs.
const common = {
module: {
loaders: {
// ...
},
},
};
export default BRANDS.map((brand) => (
merge(
common,
{
entry: {
[brand.name]: [brand.variableFile, 'path/to/my.scss'],
},
// If you needed something like this.
plugins: [
webpack.DefinePlugin({
BRAND_NAME: brand.name,
}),
],
},
)
));
I'm using the ExtractTextPlugin, and I instantiate one for each brand. I'm not sure if that's the correct thing to do.
I also have not figured out how this interacts with the CommonChunksPlugin, but I hope I can work something out.

Vue.js + Webpack multiple style tas output

I have several vue.js components, written in single page component format.
For each .vue file, I have less written specific for that page.
After bundling, I have several style tags, which populate the global style space. Thus, some of my classes are overlapping on different pages.
Is this the intended functionality with vue.js and webpack?
This is the default behaviour for vue-loader (which is the main plugin in the vue-webpack template).
However, if you want to you can extract all CSS into one file:
npm install extract-text-webpack-plugin --save-dev
// webpack.config.js
var ExtractTextPlugin = require("extract-text-webpack-plugin")
module.exports = {
// other options...
module: {
loaders: [
{
test: /\.vue$/,
loader: 'vue'
},
]
},
vue: {
loaders: {
css: ExtractTextPlugin.extract("css"),
// you can also include <style lang="less"> or other langauges
less: ExtractTextPlugin.extract("css!less")
}
},
plugins: [
new ExtractTextPlugin("style.css")
]
}
Check out the docs of vue-loader regarding extraction.

With Webpack, is it possible to generate CSS only, excluding the output.js?

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...

Resources