postcss / autoprefixer

Parse CSS and add vendor prefixes to rules by Can I Use
https://twitter.com/autoprefixer
MIT License
21.57k stars 1.25k forks source link

Loading caniuse-lite takes ~0.5sec, could the cache be persisted on disk? #1492

Closed Knagis closed 1 year ago

Knagis commented 1 year ago

On my machine the require("caniuse-lite") can take around 450ms. This is relatively noticeable slowdown every time some compilation is started. This might be more than usual because i have ts-node etc. but overall my setup is rather straight forward webpack+postcss. Building the prefixes list takes another ~50ms.

I am planning to patch autoprefixer to persist the current in-memory cache to disk to avoid this. Would you be willing to consider this as a PR as well? (just so i know if i can cut corners or if i should make things properly, with configuration options etc.)

ai commented 1 year ago

in-memory cache to disk to avoid this

require() should already have this cache. require() the module next time took less time (if there is no hacks around require()).

How do you plan to add better cache?

Knagis commented 1 year ago

i want the cache to be persisted between compilation runs. so restarting compilation process would be slightly faster. it can also thus be shared between different processes (like production webpack runs and storybook builds etc.)

Knagis commented 1 year ago
const started = Date.now();
let { agents } = require('caniuse-lite')
let dataPrefixes = require('../data/prefixes')
console.log("caniuse require took", (Date.now() - started), "ms");

Running node node_modules\autoprefixer\lib\autoprefixer.js (node 16.19.0):

caniuse require took 423 ms
caniuse require took 327 ms
caniuse require took 324 ms
caniuse require took 320 ms
caniuse require took 331 ms
caniuse require took 334 ms
caniuse require took 338 ms
ai commented 1 year ago

Yes, I understand the problem (honestly, it mostly came from ts-node because it parses all require files, and Can I Use DB has a big JSONs).

What solution do you suggest?

Knagis commented 1 year ago

the times above are plain node, without ts-node.

The let autoprefixerData = { browsers: agents, prefixes: dataPrefixes } object serialized to JSON takes around 285KB in my case. So I was thinking of writing this to disk (probably, just based on caniuse-lite/package.json version) and reading when available.

Knagis commented 1 year ago

First small thing - changing

let { agents } = require('caniuse-lite')

to

let { agents } = require('caniuse-lite/dist/unpacker/agents')

removes the big cost to load whole caniuse-lite for loading just browser stats - this same import is used by browserslist to calculate usage.

So that leaves only the feature require.

ai commented 1 year ago

So I was thinking of writing this to disk (probably, just based on caniuse-lite/package.json version) and reading when available

You need to think about cache invalidation on caniuse-lite update. And about working in the environment without fs (for instance, in the browser).

It could be much more complex, than you think.

let { agents } = require('caniuse-lite/dist/unpacker/agents')

Yes, we can change it. Send PR.

Knagis commented 1 year ago

And changing

let unpack = require('caniuse-lite').feature

to

let unpack = require('caniuse-lite/dist/unpacker/feature');

results in total time of 100ms.

So without any caching, these two imports reduce startup time from ~300ms to ~100ms.

Knagis commented 1 year ago

PR incoming.

ai commented 1 year ago

The fix was released in 10.4.14.