The purpose of the web components is to encapsulate all HTML/CSS/JS in one place, but I must use general SCSS files, that have a lot of variables such as color, font size and a lot of other stuff. It's a design system and it's always changing. Is there a plugin or a way to solve this problem?
If the purpose of those Sass files is to only expose some variables then probably the smartest thing to do is converting them to CSS custom properties: they're the recommended and most effective way to theme WebComponents.
#use 'sass:meta';
#use 'variables-file-1'; // Import the variable files
#use 'variables-file-2'; // as Sass modules
// ...
#mixin export-vars($module-name) {
// Use module-variables() to extract a name-to-value map of the
// variables declared in the module identified by $module-name
#each $name, $value in meta.module-variables($module-name) {
--#{$name}: #{$value}; // Define an equivalent CSS custom prop
}
}
:root {
#include export-vars(variables-file-1);
#include export-vars(variables-file-2);
// ...
}
You can then import the compiled CSS (a normal <link> in index.html is fine in most cases since CSS custom properties cross shadow DOM boundaries) and use the variables in your components:
import { LitElement, html, css, customElement } from 'lit-element';
#customElement('my-component')
export class MyComponent extends LitElement {
static styles = css`
:host {
color: var(--my-var, <fallback-value>);
}
`;
}
With this said, if you need to share styles between LitElements, that is possible as well.
Related
For some frameworks (eg. Gatsby >= V3) the default for importing CSS modules is as ES modules like so:
import { class1, class2 } from 'styles.modules.css'
// or
import * as styles from 'styles.modules.css'
https://www.gatsbyjs.com/docs/reference/release-notes/migrating-from-v2-to-v3/#css-modules-are-imported-as-es-modules
Other projects such as Create React App still use the default export like this:
import styles from 'styles.modules.css'
How can I publish a react-component (that uses css modules internally) so that it can be imported and used in both scenarios without extracting the css?
One workaround I found is to generate the css module hashed classes and extract the stylesheet. Then import the stylesheet with the hashed classes instead of the css module stylesheet. Every bundler that is able to import css modules should also be able to deal with the extracted css file.
I am using babel-plugin-css-modules-transform for this using the following configuration:
…
"plugins": [
[
"css-modules-transform",
{
"extractCss": {
"dir": "./lib/",
"relativeRoot": "./src/",
"filename": "[path]/[name].compiled.css"
},
"keepImport": true,
"importPathFormatter": "./importPathFormatter"
}
]
]
…
The keepImport option will keep the import but transforms it from eg. import * as styles from 'styles.module.css' to import 'styles.module.css'.
I use this option in combination with the undocumented option importPathFormatter to import the transformed css. CSS preprocessing eg. postCSS is done by the consuming application.
Content of ./importPathFormatter.js:
module.exports = path => path.replace(/\.css$/, '.compiled.css');
In the long term I want to migrate my projects to vanilla extract and use rollup/vite for bundling, but for now this is working fine.
I have the next postcss config
module.exports = {
plugins: [
[
'postcss-preset-env',
{
browsers: 'last 2 versions, IE 11, not dead',
preserve: false,
features: {
'custom-media-queries': true,
'custom-properties': true,
'nesting-rules': true
},
importFrom: ['src/styles/variables.css']
}
]
]
}
And this file with css variables
#custom-media --desktop screen and (min-width: 768px);
#custom-media --mobile screen and (max-width: 767px);
:root {
--montserrat: montserrat, sans-serif;
--sfProDisplay: sf pro display, sans-serif;
--helvetica: helvetica, sans-serif;
--blue: #315efb;
--middleBlue: #2c54e2;
--darkBlue: #274bc8;
--lightBlue: #e0ebff;
--green: #21a038;
--grey: #62687f;
--darkGray: #343b4c;
--blueGray: #8d96b2;
--cloudGray: #f3f4f7;
--cloudGray7: #afb6c9;
--darkCarbone: #1f2431;
--paleYellow: #fffde5;
--red: #ff564e;
}
After i built my project via next build && next export, colors are not displayed correctly. For example, color: var(--blueGray), instead color: #8d96b2. Has anybody idea what's wrong?
I believe you should import your CSS variables directly in your styles section and not in config. but I might have got you wrong
Global CSS needs to be included in the /pages/_app file.
From the Adding a Global Stylesheet
documentation:
To add a (global) stylesheet to your application, import the CSS file within
pages/_app.js.
(...) In development, expressing stylesheets this way allows your
styles to be hot reloaded as you edit them—-meaning you can keep
application state.
In production, all CSS files will be automatically concatenated into a
single minified .css file.
Make sure you import the file where the CSS variables are defined in your custom _app.
import '../styles/globals.css'
import '../styles/variables.css'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
export default MyApp;
This may not be worth the bounty but from NextJS docs.
CSS variables are not compiled because it is not possible to safely do
so. If you must use variables, consider using something like Sass
variables which are compiled away by Sass.
Summary:
When createCustomElement() is called multiple times inside a switch case, all of the components will be included in the build, instead of just the one that will actually be used. This increases the build size with duplicate code.
Details
I have an Angular 11 app with a multi-site architecture. There is a project in the angular.json for each site, so they can be built independently and generate their own dist bundles based on the appropriate environment.ts file that contains a "siteCode".
For one of the big components in my site -- let's call it myWidget -- I also export it as a generic web component (a.k.a "Angular Element", a.k.a. "custom element") for other sites to consume. So I have myWidget in a sub-project of the main app, and it also has its own projects listed in angular.json. Meaning I can run a build that should contain just myWidget for a given site (along with the core Angular framework, obviously).
app.module.ts of myWidget sub-project (simplified):
import { MyWidgetSite1Component } from './my-widget/site-widgets/my-widget-site1.component';
import { MyWidgetSite2Component } from './my-widget/site-widgets/my-widget-site2.component';
import { MyWidgetSite3Component } from './my-widget/site-widgets/my-widget-site3.component';
#NgModule({
declarations: [AppComponent],
imports: [MyWidgetModule]
})
export class AppModule {
constructor(private injector: Injector) {
//Create generic web component version of myWidget. Use correct child version per site.
switch (environment.siteCode) {
case "site1": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite1Component , { injector: this.injector });
customElements.define('site1-myWidget', myWidgetCustomElement);
break;
}
case "site2": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite2Component , { injector: this.injector });
customElements.define('site2-myWidget', myWidgetCustomElement);
break;
}
case "site3": {
const myWidgetCustomElement = createCustomElement(MyWidgetSite3Component , { injector: this.injector });
customElements.define('site3-myWidget', myWidgetCustomElement);
break;
}
}
}
}
Problem: it includes all three of those components in the build, instead of just the one that will be used for that site (verified with webpack bundle analyzer).
Futher background
The three site-specific myWidget components here all inherit from a common base component where all the real logic is, so they are nearly-identical. I'm doing this so I can load the appropriate CSS files for that site and bundle them inside the exported MyWidget web component as component-specific CSS. It uses shadowDom encapsulation and this way the web component is completely sealed off from the parent page that it's inserted into. So the components in question look like this:
my-widget-site1.component.ts
#Component({
selector: 'my-widget-site1',
templateUrl: '../my-widget/my-widget.component.html', //Shares base myWidget template
//First file does nothing but import appropriate site's stylesheets from main project. It would
//have been better to directly include them here, but that resulted in odd build errors.
styleUrls: [
'./my-widget-site1.component.scss',
'../my-widget/my-widget.component.scss'],
encapsulation: ViewEncapsulation.ShadowDom
})
export class MyWidgetSite1Component extends MyWidgetComponent implements OnInit {
//Do not add any code here
}
my-widget-site1.component.scss
#import '../../../../../../src/app/sites/site1/theme.scss';
#import '../../../../../../src/styles.scss';
#import '../../../../../../src/app/sites/site1/styles.scss';
Conclusion
I can think of a few general ideas to solve this:
1) Some trick to load the desired component dynamically instead of in a switch case?
I haven't been able to find anything. It seems as long as I have to import the component, it will be included in the build.
2) Avoid having multiple versions of the component per site entirely?
I would love to do this, but I don't know how to get around the CSS problem. The appropriate CSS files for a given site need to be bundled into this component at build time so they are encapsulated in the shadow-root of the web component and not built as a separate CSS file that gets imported into the global scope of the consuming page. That means I can't just list them in the "styles" section of the project build in angular.json
I tried to do a dynamic #import statement on the scss but that doesn't seem possible.
Can you script something into the build process somehow to choose the right scss files at build time? I would have no idea where to start with something like that.
I figured out an interesting solution to this.
I can get rid of the need for multiple component files and effectively pull off a dynamic #import by using a 'shortcut' to point at the necessary scss files instead of the full path:
#import "theme";
#import "styles";
#import "site-styles";
You can configure the folders it will find the specified files in via angular.json:
"stylePreprocessorOptions": {
"includePaths": [
"src/app/sites/site1/", //theme.scss and site-styles.scss are here
"src/" //styles.scss is here
]
}
So now I can use one component that always has the same imports, but at build time it will actually use the right file for the site being built.
Info about the shortcut syntax: https://www.digitalocean.com/community/tutorials/angular-shortcut-to-importing-styles-files-in-components
Let say I have a typescript class with 10 methods and that the file export a new instance of the class as its default export. Then I have another file, like a React functional component, that import this class and call one method on the class.
How will this be optimized? Can Webpack/Babel extract the code for just the method used, or will it include the whole class and I will have a bunch of unused code?
Is it better to avoid classes and export each function instead?
My goal is to make the exported bundles smaller and also have Lighthouse complain less about unused JavaScript.
Most tree shaking tools (including Webpack) work by analysing the tree of ES6 imports and exports in order to tree shake unused exports.
Take the following example:
export class {
myfunc1() { /* do stuff */ }
myfunc2() { /* do stuff */ }
}
When tree shaking with Webpack, if myFunc2 is used somewhere, myFunc1 cannot be tree shaken even if it is not used.
But here, either function could be tree shaken if not used:
export myFunc1 = () => { /* Do stuff */}
export myFunc2 = () => { /* Do stuff */}
In this case it is better for tree shaking (with Webpack) to use functions grouped together in a file, rather than a class.
I'd like to declare my styles (stylus) inside of my React component classes as such. Preferably while also utilizing CSS modules:
export default class HelloWorld extends Component {
render() {
return (
<div className={styles.hello} />
);
}
static styles = `
.hello
display block
`;
}
or even perhaps
const styles = stylus`
.hello
display block
`;
const HelloWorld = () => <div className={styles.hello} />
The goal here is to compile the styles into a stylesheet via stylus (or another preprocessor), strip the style block from the resulting javascript bundle, and access styles through CSS modules and the className property in JSX. I'd prefer to have these compiled at compile time (babel/webpack) rather than at runtime.
I'm not necessarily looking for hard and fast code, but any direction would be greatly appreciated. I'm not sure if all of this is even possible, although some of it definitely should be. I understand that accessing styles via styles. may not be feasible.
I've never written a babel or webpack plugin so I barely know where to start. Most of the documentation and tutorials that I read didn't seem to get me where I wanted to go.
Thanks
What you're trying to do is not possible, but there is a workaround; unfortunately, the answer might taste like bathtub gin. As you are probably aware, you cannot directly require Stylus. Accordingly, your forced to use a Stylus loader which you have two options, stylus-loader or Walmarts stylus-relative-loader, I would recommend the latter. Here is where things get a bit convoluted. Since you want to use CSS modules and compile the styles into a stylesheet, you will have to use the extract-text-webpack-plugin.
Long story short, if you are using Webpack 1.x here's an example configuration of the loader which you will need to implement into your Webpack config, which uses extract-text-webpack-plugin, css-modules, and stylus-relative-loader.
module: {
loaders: [{
test: /\.styl$/,
loader: ExtractTextPlugin.extract('style-loader', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]!stylus-relative-loader')
}]
},
plugins: [
new ExtractTextPlugin('main.css')
]
From here all you need to do is move your Stylus to a separate file like my-styles.styl and then require it into your React component like so.
const styles = require('./my-styles.styl')
const HelloWorld = () => <div className={styles.hello} />
PS. Sorry, if this is not what you're looking for but my "reputation" does not allow me to ask questions via comments nor can I use more than two links.