Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Consider using lightningcss as a CSS minifier #38465

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

Closed
devongovett opened this issue Jul 8, 2022 · 10 comments
Closed

Consider using lightningcss as a CSS minifier #38465

devongovett opened this issue Jul 8, 2022 · 10 comments

Comments

@devongovett
Copy link

devongovett commented Jul 8, 2022

Describe the feature you'd like to request

Lightning CSS is a new CSS transformer and minifier in Rust, built by the team behind the Parcel bundler. It is significantly faster than any other CSS minifier I am aware of, while producing smaller output in many cases. It's over 100x faster than cssnano, the CSS minifier used by Next.js today.

I think Next.js could adopt lightningcss as a minifier easily, and users would see reduced production build times, with potential for reduced bundle sizes as well. In the future, additional features such as replacing postcss-preset-env and autoprefixer could also be adopted, but minification is the easiest place to start.

The GitHub readme has information about the features. For more about the internal architecture, see the announcement blog post. As if March, it has been the default CSS minifier in Parcel.

Describe the solution you'd like

css-minimizer-webpack-plugin already supports usinglightningcss with a one line change.

new CssMinimizerPlugin({
  minify: CssMinimizerPlugin.lightningCssMinify,
  // ...
})

This could easily be opt-in at first (similar to the swcMinify option for JavaScript) if you are concerned about bugs or regressions.

Describe alternatives you've considered

I am biased as the main author of lightningcss, but I think changing the default would be a good choice. However, if you don't want to do this, I think it would be nice to be able to customize the minifier more easily. I tried to write a Next.js plugin to do this, but it involves some brittle hackery to the webpack config. There's no way to detect the default minimizer plugin, so you have to assume the index in the list of minimizers in the webpack config doesn't change in order to delete the default and add the replacement. Perhaps a config option similar to the one supported by css-minimizer-webpack-plugin could be supported to allow users choice over the minifier they use.

I am aware that SWC is also working on a CSS minifier. However, lightningcss is ready today. Again, I'm biased, but I think the architecture of Lightning CSS will enable it to continue to be faster and produce smaller output. At least offering users a choice over what minifier they use would be better than locking them in.

@SukkaW
Copy link
Contributor

SukkaW commented Jul 9, 2022

@devongovett @parcel/css is wonderful and awesome! But would you mind providing a more detailed benchmark/comparison between parcel/css and cssnano, with the Next.js' default browserlist config? IIRC, parcel/css achieves a higher minify rate by applying "unsafe" minification (E.g., mangle top: 0; right: 0; bottom: 0; left: 0; into inset: 0;), which would not be available with Next.js' default browserlist config.

FYI, React Beta Docs (https://beta.reactjs.org/) has only 1 CSS file and the after-minified size is at 75.2 KiB. Is it possible for @parcel/css to beat cssnano on this?

Besides, cssnano does support more advanced minification features than @parcel/css. For instance, cssnano does support merging adjacent atrules (@media, @supports, etc.), but @parcel/css doesn't (see my feature request at parcel-bundler/lightningcss#104).

Also, IMHO a normal Next.js project would usually output less than 10 CSS files, thus the speed of parcel/css might not be a huge advantage here as well.

And the npm install size is also an important thing to be considered. Currently, Next.js' built-in cssnano preset has an install size of 1.71 MiB (see cssnano-simple @ packagephobia), however the @parcel/css has an install size of 3.05 MiB (@parcel/css @ packagephobia).

@devongovett
Copy link
Author

IIRC, parcel/css achieves a higher minify rate by applying "unsafe" minification (E.g., mangle top: 0; right: 0; bottom: 0; left: 0; into inset: 0;), which would not be available with Next.js' default browserlist config.

No,@parcel/css does not do any unsafe transformations. However, you must specify the correct browser targets. inset is only output when the browser targets allow it. If you find a case where the output CSS behaves differently than the input in one of the target browsers, please report it as a bug.

FYI, React Beta Docs (https://beta.reactjs.org/) has only 1 CSS file and the after-minified size is at 75.2 KiB. Is it possible for @parcel/css to beat cssnano on this?

Browser targets have a big effect on this, since they allow @parcel/css to remove unnecessary prefixes, and use more compact syntax when supported.

  • cssnano – 75182 bytes
  • @parcel/css with browserslist: "chrome 61,edge 16,firefox 60,opera 48,safari 11" – 74363 bytes
  • @parcel/css with a more modern browserslist: "chrome 95" – 71564 bytes

Again, I wouldn't expect the difference in size to be massive – cssnano is a very good minifier, and there's only so much that can be done safely, especially in this case where the input CSS is generated by Tailwind rather than authored by hand. The ability to take advantage of browser improvements over time is a powerful feature though.

For instance, cssnano does support merging adjacent atrules (@media, @supports, etc.), but @parcel/css doesn't

Merging adjacent @media/@supports rules with identical queries is a good feature request, though I'm not sure how often it would really occur in real-world code. Non-adjacent rules cannot be merged safely, as doing so could break specificity. Some minifiers will do this, and might look better on size benchmarks, but it's not safe to do generally.

Also, IMHO a normal Next.js project would usually output less than 10 CSS files, thus the speed of parcel/css might not be a huge advantage here as well.

Depends how big those files are. The time can add up quickly, and can easily represent a significant percentage of the overall build time. Making this 100x faster could be a nice speedup.

however the @parcel/css has an install size of 3.05 MiB

This depends on the platform since @parcel/css is a binary, and binary sizes vary from platform to platform. But yes, binaries tend to be larger than source code. We've done some work to reduce the binary size as much as possible, but there are probably additional things we could look into if this is a big concern.

@SukkaW
Copy link
Contributor

SukkaW commented Jul 9, 2022

No,@parcel/css does not do any unsafe transformations. However, you must specify the correct browser targets. inset is only output when the browser targets allow it.

Sorry I didn't make my idea clear. What I mean is that @parcel/css does be able to transform outdated syntax/usage into more modern syntax, in order to save bytes.

While as I said. Next.js includes some legacy browsers in its default browserlists. Thus some optimization provided by @parcel/css might not be available (which might make the @parcel/css's minification less effective).

Some minifiers will do this, and might look better on size benchmarks, but it's not safe to do generally.

In my personal case, I use @parcel/css on extracted CSS files generated by a CSS-in-JS library, which does sorting beforehand (and it leaves the job of merging to the minifier). So the adjacent atrules might not be common for others' normal hand-written CSS, but the case does exist.

Depends on how big those files are. The time can add up quickly, and can easily represent a significant percentage of the overall build time. Making this 100x faster could be a nice speedup.

@parcel/css is indeed fast and performant. I have used it in many of my personal projects already and it is awesome. However, the time saved by the @parcel/css might be counteracted by the elapsed time of the node native addon boot-up (which is really my concern), especially when there are not many CSS files or when CSS files are small.

@devongovett
Copy link
Author

Thus some optimization provided by @parcel/css might not be available (which might make the @parcel/css's minification less effective).

True, but (a) it should be at least as good as cssnano, and (b) users can customize their browserslist so may be able to get additional benefit over the defaults.

So the adjacent atrules might not be common for others' normal hand-written CSS, but the case does exist.

I just implemented it in parcel-bundler/lightningcss@ae87b83. Should go out with the next release.

However, the time saved by the @parcel/css might be counteracted by the elapsed time of the node native addon boot-up

Where does this concern come from? I just tested and on my machine:

require parcel css: 2.346ms
require cssnano: 15.728ms

@devongovett devongovett changed the title Consider using @parcel/css as a CSS minifier Consider using lightningcss as a CSS minifier Sep 21, 2022
@padmaia
Copy link
Member

padmaia commented Sep 22, 2022

Hey @devongovett, it's definitely something we are considering. SWC also has a CSS parser, and although it's less far along than Lightning, the team sees some advantages to using the same tool for JS/CSS compilation. We'll be evaluating whether we want to invest more in the SWC CSS compiler or use something else soon, but we've got some higher priority items, like polishing up the minifier, that we need to focus on first.

@jacobrask
Copy link

Apart from minification there are some features that Lightning CSS does better than any PostCSS plugins or other preprocessors I've seen, like downleveling of the :dir() selector and downleveling of CSS logical properties with correct semantics.

@baraeb92
Copy link

baraeb92 commented Jan 5, 2023

With the latest release, can lightningcss now replace the postCSS as well? @devongovett ?

With the plan for Nextjs to allow usage of bun with Next, is it possible to give first-party support for lightingcss as well? As to create documentation on how to use NextJS with lightningCSS

@padmaia @leerob

@leerob
Copy link
Member

leerob commented Jan 5, 2023

Nothing has changed since the last comment above – the "usage of bun" here I'm assuming is referring to my draft PR for using bun as a packager manager with create-next-app, which is unrelated 👍

@devongovett
Copy link
Author

devongovett commented Jan 5, 2023

I'd love it if I could at least write a Next.js plugin for Lightning CSS. I tried at one point and it was possible to override the webpack CSS plugins, but it seemed kinda brittle to be splicing plugins in the array at hard coded indices.

So many developers use Next.js, and it's unfortunate that they are locked into the tools that it ships with. This makes it really hard for new tools in a specific area to gain adoption. It would be huge for the ecosystem if the defaults could be swapped out for alternatives, allowing devs to continue using next.js while trying out new tools.

@SukkaW
Copy link
Contributor

SukkaW commented Jan 7, 2023

but it seemed kinda brittle to be splicing plugins in the array at hard coded indices.

It is always difficult to modify the Next.js' built-in webpack config without breaking anything, but this should work:

import NextBuiltinMiniCssExtractPlugin from 'next/dist/compiled/build/webpack/...';

// ...
webpackConfig.plugins = webpackConfig.plugins.map(plugin => {
  if (plugin instanceof NextBuiltinMiniCssExtractPlugin) {
    return new MiniCssExtractPlugin({ /* now with lightningcss enabled */ });
  }
  return plugin;
});

@vercel vercel locked and limited conversation to collaborators Feb 28, 2023
@timneutkens timneutkens converted this issue into discussion #46536 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants