Skip to content

For developers

Here you will find some tips for optimizing the library in production.

CSS extract

The styles in our library are ready to use and don't require any additional actions. They're applied automatically and added to the head of your application by inserting CSS into JavaScript.

To speed up the load time and decrease TTI (time to interactive), separate JS and CSS. This will reduce the size of the JS files and will allow to load CSS and JavaScript in parallel decreasing the application launch time.

WARNING

@semcore/shadow-loader is deprecated. To unify behavior across the bundlers, use @semcore/process-css-unplugin package. As for now it offers implementation for Vite and Webpack.

To do that, use the shadow-loader webpack plugin. It will strip the styles from JavaScript and replace them with require ("./style.css") in the component code. This way you will be able to extract the styles into a separate file using mini-css-extract-plugin or a similar tool.

This is what your webpack.config.js might look like:

js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  // ...
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
      {
        test: /\.m?js$/,
        include: /node_modules\/(@semcore|\.cache\/reshadow)/,
        use: '@semcore/shadow-loader',
      },
      {
        test: /\.css$/,
        include: /node_modules\/(@semcore|\.cache\/reshadow)/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

Webpack/Vite integration with @semcore/process-css-unplugin

vite.config.ts:

js
import { defineConfig } from 'vite';
import { processCssVitePlugin } from '@semcore/process-css-unplugin';

export default defineConfig({
  //...
  plugins: [
    processCssVitePlugin(),
  ],
})

webpack.config.js:

js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { processCssWebpackPlugin } = require('@semcore/process-css-unplugin');

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.css$/,
        exclude: /node_modules/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  plugins: [
    processCssWebpackPlugin(),
  ],
};

Style isolation

Classes in styles include a hash based on content and version to avoid collisions. However, this approach breaks down when two components of the same version, built by different teams, are rendered on the same page. In such cases, styles may overlap or be applied incorrectly due to the CSS cascade order.

To address this, class names must be made unique by adding a suffix. Since we distribute pre-built JavaScript and CSS files, the suffix can't be hardcoded at build time.

The recommended solution is to use the isolationSuffix option provided by process-css-unplugin. This option allows you to change default suffix to a unique one, defined as a value of isolationSuffix.

The suffix is applied consistently to both JavaScript and CSS.

Following is an example of how your webpack.config.js might look:

js
const { processCssWebpackPlugin } = require('@semcore/process-css-unplugin');

module.exports = {
  //...
  plugins: [
    processCssWebpackPlugin({ isolationSuffix: '_unique-suffix_' }),
  ],
};

vite.config.ts:

js
import { defineConfig } from 'vite';
import { processCssVitePlugin } from '@semcore/process-css-unplugin';

export default defineConfig({
  //...
  plugins: [
    processCssVitePlugin({ isolationSuffix: '_unique-suffix_' }),
  ],
})

Server-side rendering

There are two ways to implement SSR.

SSR via sstyled.getStyles function

This method will work for you if you're not using the @semcore/shadow-loader package (see CSS extract).

Use the @semcore/core package and the sstyled.getStyles function which will return style tags with all the necessary styles:

jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { sstyled } from '@semcore/core';
import App from './App';

const body = ReactDOM.renderToString(<App />);

const html = `
    <html>
        <head>
            {sstyled.getStyles().css}
        </head>
        <body>
            <div id="root">{body}</div>
        </body>
    </html>
`;

SSR via style extract

This method will work for you if you're using the @semcore/shadow-loader package (see CSS extract).

If you use CSS-extract, it should already be configured. If you don't use it, go to the CSS extract section and follow the steps described. Once you've done that, all component styles will be collected in a separate chunk that needs to be included in HTML generated on the server.

If you use themes

The webpack-node-externals package is often used to reduce the size of the generated server bundle. This plugin excludes all modules located in the node_modules folder from the bundle, which disables the @semcore library style processing and the application of themes. As a result, you may end up with a server bundle with no theme applied.

This problem is solved by the following configuration for webpack-node-externals:

javascript
{
  externals: [
    webpackNodeExternals({
      modulesFromFile: true,
      whitelist: [/^@semcore/]
    })
  ],
}

Bundle size reduction 🔪

We often reuse some of our components inside others to avoid duplicating styles and logic. Each component is a separate package, and sometimes different versions of the same components may end up included in a bundle.

Package managers like npm or yarn are aimed at solving this problem by optimizing the tree of dependencies, making it more flat. Unfortunately, they don't always succeed.

When the lock-files have already been created, updating or installing new packages may lead to duplication of components not included in package.json and increase the bundle size.

To solve the duplicate problem, you'll need to thoroughly update all components and recalculate the entire dependency tree.

All our packages are collected under the @semcore scope. There are no native tools that would allow to update the entire scope yet, so we recommend that you use the update-by-scope package.

Check your package.json for any critical updates by running the following command:

bash
npx update-by-scope @semcore

Eliminating duplicates helps reduce the bundle size by ~30% on average.

Use these commands regularly and monitor the size of your bundle with webpack-bundle-analyzer.

Released under the MIT License.

Released under the MIT License.