Styling not applied to vue web component during development - css

While developing a Vue web component, the style is not applied to the web component, but added to the head of the document. This means that the style is ignored in the shadow DOM. Here is how I wrap the web component in main.js:
import Vue from 'vue';
import wrap from '#vue/web-component-wrapper';
import MyWebComponent from './components/MyWebComponent';
const WrappedElement = wrap(Vue, MyWebComponent);
window.customElements.define('my-web-component', WrappedElement);
Again, any CSS rules inside the style tags do not take effect.
When I build for production, the styles are added to the web component. I use the following command to do the wrapping:
vue-cli-service build --target wc --name my-web-component ./src/components/MyWebComponent.vue
Is there a way to achieve the same thing with vue-cli-service serve?
edit: example repo here: https://github.com/snirp/vue-web-component
edit2: I have the feeling my problem is closely related to this issue. I cannot make much sense of the workarounds, and I would value a more basic solution.

Based on the GitHub issue you linked, the solution is to set the shadowMode option in vue-loader and vue-style-loader. shadowMode is false by default in a Vue CLI project, but we can tweak that in vue.config.js.
First, we'd inspect the Webpack config to determine which loaders to change:
# run at project root
vue inspect
The command output reveals several loader configs with shadowMode: false:
/* config.module.rule('css') */
{
test: /\.css$/,
oneOf: [
/* config.module.rule('css').oneOf('vue-modules') */
{
resourceQuery: /module/,
use: [
/* config.module.rule('css').oneOf('vue-modules').use('vue-style-loader') */
{
loader: 'vue-style-loader',
options: {
sourceMap: false,
shadowMode: false // <---
}
},
/* ... */
]
},
/* ... */
full list of Webpack loader configs with shadowMode: false:
config.module.rule('vue').use('vue-loader')
config.module.rule('css').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('css').oneOf('vue').use('vue-style-loader')
config.module.rule('css').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('css').oneOf('normal').use('vue-style-loader')
config.module.rule('postcss').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('postcss').oneOf('vue').use('vue-style-loader')
config.module.rule('postcss').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('postcss').oneOf('normal').use('vue-style-loader')
config.module.rule('scss').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('scss').oneOf('vue').use('vue-style-loader')
config.module.rule('scss').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('scss').oneOf('normal').use('vue-style-loader')
config.module.rule('sass').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('sass').oneOf('vue').use('vue-style-loader')
config.module.rule('sass').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('sass').oneOf('normal').use('vue-style-loader')
config.module.rule('less').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('less').oneOf('vue').use('vue-style-loader')
config.module.rule('less').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('less').oneOf('normal').use('vue-style-loader')
config.module.rule('stylus').oneOf('vue-modules').use('vue-style-loader')
config.module.rule('stylus').oneOf('vue').use('vue-style-loader')
config.module.rule('stylus').oneOf('normal-modules').use('vue-style-loader')
config.module.rule('stylus').oneOf('normal').use('vue-style-loader')
So, we can set shadowMode: true for those configs in vue.config.js with this snippet:
function enableShadowCss(config) {
const configs = [
config.module.rule('vue').use('vue-loader'),
config.module.rule('css').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('css').oneOf('vue').use('vue-style-loader'),
config.module.rule('css').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('css').oneOf('normal').use('vue-style-loader'),
config.module.rule('postcss').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('postcss').oneOf('vue').use('vue-style-loader'),
config.module.rule('postcss').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('postcss').oneOf('normal').use('vue-style-loader'),
config.module.rule('scss').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('scss').oneOf('vue').use('vue-style-loader'),
config.module.rule('scss').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('scss').oneOf('normal').use('vue-style-loader'),
config.module.rule('sass').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('sass').oneOf('vue').use('vue-style-loader'),
config.module.rule('sass').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('sass').oneOf('normal').use('vue-style-loader'),
config.module.rule('less').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('less').oneOf('vue').use('vue-style-loader'),
config.module.rule('less').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('less').oneOf('normal').use('vue-style-loader'),
config.module.rule('stylus').oneOf('vue-modules').use('vue-style-loader'),
config.module.rule('stylus').oneOf('vue').use('vue-style-loader'),
config.module.rule('stylus').oneOf('normal-modules').use('vue-style-loader'),
config.module.rule('stylus').oneOf('normal').use('vue-style-loader'),
];
configs.forEach(c => c.tap(options => {
options.shadowMode = true;
return options;
}));
}
module.exports = {
// https://cli.vuejs.org/guide/webpack.html#chaining-advanced
chainWebpack: config => {
enableShadowCss(config);
}
}
Creating <projectroot>/vue.config.js with the snippet above enables Shadow CSS in development mode in your project. See https://github.com/snirp/vue-web-component/pull/1.

Related

Using vite plugins with Storybook and SvelteKit

I have successfully set up #poppanator/sveltekit-svg with SvelteKit using the following configuration (svelte.config.js):
import preprocess from 'svelte-preprocess';
import svg from '#poppanator/sveltekit-svg';
/** #type {import('#sveltejs/kit').Config} */
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess(),
kit: {
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte',
vite: {
plugins: [svg()]
}
}
};
export default config;
This works when running the SvelteKit project using npm run dev. However, I cannot get the svg plugin to work inside Storybook.
I have the following Storybook configuration (.storybook/main.cjs):
module.exports = {
"stories": [
"../src/**/*.stories.mdx",
"../src/**/*.stories.#(js|jsx|ts|tsx|svelte)"
],
"addons": [
"#storybook/addon-links",
"#storybook/addon-essentials",
"#storybook/addon-svelte-csf"
],
"core": {
"builder": "storybook-builder-vite"
},
"svelteOptions": {
preprocess: import("../svelte.config.js").preprocess
}
}
When importing SVG files in Storybook stories (or in components used by the stories), only the file path of the SVG file is returned. When importing SVG files inside a SvelteKit route, a Svelte component is returned as it should be.
I have tried this with Storybook 6.3.10 and 6.4.0-beta.7 with storybook-builder-vite (0.1.0).
How should this go together to make SVG imports work inside Storybook?
While SvelteKit uses Vite, Storybook uses Webpack.
To get svg imports working you'll need to find a loader for webpack that behaves similar to the #poppanator/sveltekit-svg plugin and add that to your storybook configuration.
I'm not sure similar loader exists (yet), depending on your usage, renaming the .svg files to .svelte could be an alternative

Tailwind class doesn't take effect

I created a react setup for a little project and decided to add tailwind. It was successful but when I add the class to the components, I don't see any change.
This is the link to the repository
Everything seems fine. Once delete the node modules and package.lock.json file and install node modules then start the server.
Also, there is no need to import tailwind.css in App.js.
Just main.css is enough as we are already appending all the styles to main.css (check scripts in package.json)
I found the problem. It was from my webpack config for CSS loader. I noticed when I added my own stylesheet, not all the rules were applied.
{
loader: "css-loader",
options: {
modules: true,
importLoaders: 1,
sourceMap: true
}
}
I had to remove all the options. I don't even know why I added it at first. Tailwind CSS now works.
If you know that you've configured Tailwind and added the right settings and presets, maybe you need to add this:
module.exports = {
content: [
'./public/index.html', <-
],
}
or this, if you're using ReactJS:
module.exports = {
content: [
'./pages/**/*.{html,js}',
'./components/**/*.{html,js}'
],
// ...
}
Within your tailwind.config.js file.
You also can learn/read more about it on: https://tailwindcss.com/docs/content-configuration, that worked perfectly for me!

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.

Global variable in SCSS / SASS with angular components

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.

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