Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature]: Lightning CSS Support #1702

Closed
chenjiahan opened this issue Mar 1, 2024 · 9 comments
Closed

[Feature]: Lightning CSS Support #1702

chenjiahan opened this issue Mar 1, 2024 · 9 comments
Assignees
Milestone

Comments

@chenjiahan
Copy link
Member

What problem does this feature solve?

Lightning CSS is a popular CSS transformer based on Rust. It can solve the performance overhead caused by PostCSS + autoprefixer in the current Rsbuild, and it can also replace SWC CSS for CSS compression.

At present, we cannot determine whether Rspack will integrate Lightning CSS in the future, or replace SWC CSS. However, Rsbuild can first support the integration of Lightning CSS as a plugin, to verify the benefits of Lightning CSS.

What does the proposed API look like?

https://lightningcss.dev/

@chenjiahan chenjiahan added this to the 1.0.0 milestone Mar 1, 2024
@SoonIter
Copy link
Member

SoonIter commented Mar 4, 2024

plugin-lightningcss

Design

Many bundlers and frameworks experimentally support lightningcss. There have been some good cases of lightningcss integration in the community such as lightningcss-loader and css-minimizer-webpack-plugin. In order to avoid future changes in api, our plugin design is as transparent as possible.

Interface

There are two main parts of lightningcss, css-transformer and css-minifier. We already have @rsbuild/plugin-css-minimizer, so plugin-lightningcss would focus on the part of transformer, and how to replace postcss-loader

The configuration of the plugin is as follows:

export type PluginLightningcssOptions = {
  /**
   * @see https://github.com/fz6m/lightningcss-loader
   * @default
   * {
   *   targets: browserslistToTargets(browserslist) 
   * }
   */
  lightningcssLoaderOptions?: ChainedConfig<LightningCssLoaderOptions>;
  /**
   * The 'insert-before'/'insert-after' define the insertion order of the lightningcss-loader based on the postcss-loader.
   * @default false
   */
  disableRemovePostCSS?: false | 'insert-before' | 'insert-after';
};

lightningcssLoaderOptions passed the user options to lightningcss-loader

disableRemovePostCSS retains extensibility for users who want lightningcss and postcss to be used together. By default, we will delete postcss, so tools.postcss will be invalid.

Here are some examples of users

Example 1. only use lightningcss-loader as transformer

import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginLightningcss } from '@rsbuild/plugin-lightningcss';
import {
  pluginCssMinimizer,
  CssMinimizerWebpackPlugin,
} from '@rsbuild/plugin-css-minimizer';

export default defineConfig({
  plugins: [
    pluginReact(),
    pluginLightningcss({
      lightningcssLoaderOptions: {
        // ... lightningcss options
      },
      // disableRemovePostCSS: false | 'insert-before' | 'insert-after'
    }),
  ],
});

Example 2. use lightningcss as both transformer and minifier

import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginLightningcss } from '@rsbuild/plugin-lightningcss';
import {
  pluginCssMinimizer,
  CssMinimizerWebpackPlugin,
} from '@rsbuild/plugin-css-minimizer';
import lightningcss from 'lightningcss';

export default defineConfig({
  plugins: [
    pluginReact(),
    pluginLightningcss({
      lightningcssLoaderOptions: {
        implementation: lightningcss,
        // ... lightningcss options
      },
      // disableRemovePostCSS: false | 'insert-before' | 'insert-after'
    }),
    pluginCssMinimizer({
      pluginOptions: {
        minify: CssMinimizerWebpackPlugin.lightningCssMinify,
        minimizerOptions: {
          // ...
        }
      },
    }),
  ],
});

Example 3. migrate from plugin-px2rem to plugin-lightningcss

@rsbuild/plugin-px2rem is based on postcss, so when migrating to lightningcss, some changes need to be made by user manually.

parcel-bundler/lightningcss#483

Related links and information

feel free to give some feedback if something wrong

@SoonIter
Copy link
Member

SoonIter commented Mar 5, 2024

@chenjiahan I decided to also builtin the feature of minimizer instead of using cssMinimizerWebpackPlugin.So I add a "false" type to turn it off and re-adjusted the plugin interface

export type PluginLightningcssOptions = {
  /**
   * @see https://github.com/parcel-bundler/lightningcss/blob/master/node/index.d.ts
   * @default
   * {
   *   targets: browserslistToTargets(browserslist)
   * }
   */
  loaderOptions?: false | LightningCssLoaderOptions;
  /**
   * The 'insert-before'/'insert-after' define the insertion order of the lightningcss-loader based on the postcss-loader.
   * @default false
   */
  disableRemovePostCSS?: false | 'insert-before' | 'insert-after';
  /**
   * @see https://github.com/parcel-bundler/lightningcss/blob/master/node/index.d.ts
   * @default
   * {
   *   targets: browserslistToTargets(browserslist)
   * }
   */
  minimizerOptions?: false | LightningCssMinifyPluginOptions;
};

As for disableRemovePostCSS, it is an advanced usage for who wants to use postcss and lightningcss together according to parcel-bundler/lightningcss#94 (comment).
Option name can be discussed

@chenjiahan
Copy link
Member Author

I decided to also builtin the feature of minimizer instead of using cssMinimizerWebpackPlugin.

LGTM 😄

I add a "false" type to turn it off and re-adjusted the plugin interface

How about rename the options to:

type PluginLightningcssOptions = {
  /**
   * @see https://github.com/parcel-bundler/lightningcss/blob/master/node/index.d.ts
   * @default
   * {
   *   targets: browserslistToTargets(browserslist)
   * }
   */
  transform?: false | LightningCssLoaderOptions;
  /**
   * @see https://github.com/parcel-bundler/lightningcss/blob/master/node/index.d.ts
   * @default
   * {
   *   targets: browserslistToTargets(browserslist)
   * }
   */
  minify?: false | LightningCssMinifyPluginOptions;
};

I removed the disableRemovePostCSS option as the naming is not intuitive enough and we still need more discussions to decide how to implement this. (This feature can be added in a separate PR later)

@chenjiahan
Copy link
Member Author

For disableRemovePostCSS, I found some comments from the author of lightning css:

image

vitejs/vite#12807 (comment)

@SoonIter
Copy link
Member

SoonIter commented Mar 5, 2024

How about rename the options to:

Ok, I will do this

I removed the disableRemovePostCSS option as the naming is not intuitive enough and we still need more discussions to decide how to implement this. (This feature can be added in a separate PR later)

https://github.com/onigoetz/postcss-lightningcss
https://www.npmjs.com/package/postcss-lightningcss

by the way, postcss-lightningcss which is 3000 downloads weekly maybe a better choice

@chenjiahan
Copy link
Member Author

by the way, postcss-lightningcss which is 3000 downloads weekly maybe a better choice

This is not an ideal choice because:

  • The minimizer should be applied to the bundled CSS files, not during the transform process. This affects compression rate.
  • It is more intuitive to have Lightningcss and Postcss run in two separate loaders, and their execution order will be clearer.

@chenjiahan
Copy link
Member Author

chenjiahan commented Mar 5, 2024

I just considered the design of disableRemovePostCSS. In Rsbuild, we can transform the CSS with the following way:

With PostCSS (default)

The default order without lightning css:

Sass/Less -> PostCSS (includes autoprefixer) -> css-loader / style-loader

With Lightning CSS

When the lightning css plugin is used, the builtin PostCSS / autoprefixer is disabled by default, and the order will become:

Sass/Less -> Lightning CSS -> css-loader / style-loader.

With two transformers

When the lightning css plugin is used, and if user needs to use tailwindcss or some other PostCSS plugins, they can still use postcss.config.js or the tools.postcss config, the order will become:

Sass/Less -> PostCSS (no default autoprefixer) -> Lightning CSS -> css-loader / style-loader.

Other case

Rsbuild will not provide an option for `Sass/Less -> Lightning CSS -> PostCSS', I haven't thought about when to use it yet.

@SoonIter
Copy link
Member

SoonIter commented Mar 5, 2024

With two transformers

When the lightning css plugin is used, and if user needs to use tailwindcss or some other PostCSS plugins, they can still use postcss.config.js or the tools.postcss config, the order will become:

Sass/Less -> PostCSS (no default autoprefixer) -> Lightning CSS -> css-loader / style-loader.

How can we detect the user's intention?

Is this way?

	 if (
        config.tools.postcss !== undefined ||
        config.tools.autoprefixer !== undefined
      ) {
        use.before(CHAIN_ID.USE.POSTCSS);
      }

@chenjiahan
Copy link
Member Author

How can we detect the user's intention?

Yeah, we can check the Rsbuild config or the postcss config file. But this behavior is somewhat implicit, perhaps we still need a plugin option to control whether postcss-loader is enabled.

type PluginLightningcssOptions = {
  /**
   * Whether to enable postcss-loader.
   * @default false
   */
  postcss?: boolean;
  /**
   * We can add this option if needed. It does not need to be implemented in the first release.
   * @default 'postcssFirst'
   */
  executeOrder?: 'postcssFirst' | 'lightningcssFirst'
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants