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

Fix require time performance #2454

Open
chris-morgan opened this issue Mar 30, 2017 · 62 comments
Open

Fix require time performance #2454

chris-morgan opened this issue Mar 30, 2017 · 62 comments
Labels
type: bug a problem with a feature or rule type: enhancement a new feature that isn't related to rules

Comments

@chris-morgan
Copy link

Describe the issue. Is it a bug or a feature request (new rule, new option, etc.)?

Loading stylelint (require('stylelint')) is very slow. It takes over a second for me when there’s nothing else installed, and when there are various other things installed it increases (e.g. two seconds).

I care about this because in a Makefile world where you spin up the tooling for each file, this is a >1 second delay per file which rapidly changes from unpleasant to awful.

Steps to reproduce:

$ cd $(mktemp -d)
$ yarn add stylelint
[…]
$ node -e "s=Date.now();require('stylelint');console.log(Date.now()-s)"
1183
$ node -e "s=Date.now();require('stylelint');console.log(Date.now()-s)"
1128
$ node -e "s=Date.now();require('stylelint');console.log(Date.now()-s)"
1155

Which rule, if any, is this issue related to?

n/a

What CSS is needed to reproduce this issue?

n/a

What stylelint configuration is needed to reproduce this issue?

n/a

Which version of stylelint are you using?

7.9.0

How are you running stylelint: CLI, PostCSS plugin, Node API?

n/a

Does your issue relate to non-standard syntax (e.g. SCSS, nesting, etc.)?

n/a

What did you expect to happen?

require('stylelint') to be fast (like, well under 100ms).

What actually happened (e.g. what warnings or errors you are getting)?

It took a long time to import stylelint, a second or two.

@davidtheclark
Copy link
Contributor

@chris-morgan Can you contrast this with other programs of similar complexity? Is there anything actionable that can be done?

@chris-morgan
Copy link
Author

@davidtheclark Well, here are a bunch of other things I was trying with postcss:

postcss: 109
postcss-cssnext: 67
postcss-custom-properties: 3
postcss-at-rules-variables: 2
postcss-conditionals: 21
postcss-clean: 204
autoprefixer: 267
stylelint: 1887

And I consider postcss-clean and autoprefixer to be far too slow (over 200ms) as well. Seriously, 100ms is a long time.

I don’t know why stylelint is so slow at present, but it is very slow and must be doing something that it shouldn’t be!

@davidtheclark
Copy link
Contributor

davidtheclark commented Mar 30, 2017

@chris-morgan Can you compare it to a module of similar size? I imagine stylelint involves many, many more files than any of those modules you've listed.

@chris-morgan
Copy link
Author

Putting a bit of instrumentation into require I see that the 175 rules, each in their own module/file, consume the largest fraction of the time. I imagine there’s quite a bit of overhead in loading the modules. Rolling all of stylelint up into one file for the npm distribution seems to me to be a reasonable solution, or remedy at least (I don’t know how long it will take to import after that, we can reassess after that). Hundreds of files is simply terrible for performance.

@hudochenkov
Copy link
Member

@davidtheclark I compared with eslint using the method above:

stylelint eslint
959 262
1021 280
853 289
880 276
857 297

eslint is pretty big. Maybe we have slow dependencies, or there are other places to improve the speed.

@chris-morgan
Copy link
Author

Rolling the ~275 files of stylelint into one with rollup.js produced a saving of about one millisecond per file, halving the loading time spent in stylelint. That then leaves stylelint’s dependencies unrolled; I estimate another 150ms could be saved there if they were to roll themselves up. (Fun fact: cosmiconfig adds over 100ms of loading time, just by itself; its dependency js-yaml is around 80ms of that. A few others are almost as bad.)

A 40–50% reduction in load time purely from rolling everything up is not to be sneezed at.

After doing this, stylelint would still be far too slow to start for my liking, but it’s a start, at the least.

@hudochenkov
Copy link
Member

@chris-morgan how did you measure dependencies loading time? Like you did with cosmicconfig. I would like to learn :)

@chris-morgan
Copy link
Author

chris-morgan commented Mar 30, 2017

@hudochenkov A very hacked-together script which produces a kind-of-back-to-front tree. I think I’ll make a nicer version of this which does a bit of analysis, it’ll do nicely as my first package to go on npm.

var orig = require.extensions['.js'];
var level = 0;
var cwd = process.cwd();
var toPrint = [];
require.extensions['.js'] = function (module, path) {
    if (path.startsWith(cwd)) {
        path = '.' + path.substr(cwd.length);
    }
    var start = Date.now();
    level++;
    var out = orig.apply(this, arguments);
    level--;
    toPrint.push('│ '.repeat(level) + '┌ ' + path + ' \x1b[32m' + (Date.now() - start) + '\x1b[m');
    if (!level) {
        console.log(toPrint.join('\n'));
        toPrint.splice(0);
    }
    return out;
}

require('stylelint');  // Or ./stylelint-bundle.js after generating it

Then I just worked it over a bit in Vim with regular expressions, :sort n, :%!uniq and other things like that.

@hudochenkov
Copy link
Member

master v8
837 694
838 699
822 694
817 690
869 678

v8 is faster because we eliminated bunch of rules depended on third-party modules.

@alexander-akait
Copy link
Member

@hudochenkov What tool do you use to output the time? 😄

@hudochenkov
Copy link
Member

@evilebottnawi $ node perf.js :)

console.time('stylelint');
const stylelint = require('./stylelint');
console.timeEnd('stylelint');

@alexander-akait
Copy link
Member

@hudochenkov Thanks) I just thought that this could be some kind of other tool, on the hooks require for example 😄

@alexander-akait
Copy link
Member

@hudochenkov also we can try using lazy require for some modules, but it is need investigation

@hudochenkov hudochenkov added the status: needs investigation triage needs further investigation label Mar 30, 2017
@jeddy3
Copy link
Member

jeddy3 commented Apr 8, 2017

Ok, so what's actionable here?

The good news is v8 is only about 2.5x slower than eslint (a similarly sized project), but it sounds like there are two possible areas of improvement: the requiring of our individual source files and our dependencies (in particular, autoprefixer and js-yaml (within cosmiconfig)).

We could try rolling-up the code as part of the publishing process. What I'd like to know is will this complicate using stylelint as a pinned-github-tag dependencies. One of the reasons we target node@4, and no longer transpile our code, is the simplify that process.

autoprefixer includes the caniuse database, which accounts for a significant chunk of stylelint's weight. There's an upcoming PR to migrate autoprefixer's dependency browserlist to caniuse-lite. I suggest waiting to see if that helps.

Regarding js-yaml, perhaps we could open an issue there to see if there's anything that can do about their require time?

Does any performance-orientated person want to pick this up?

@jeddy3 jeddy3 changed the title require('stylelint') is very slow Reduce time taken to require stylelint Apr 8, 2017
@jeddy3 jeddy3 added status: ready to implement is ready to be worked on by someone type: enhancement a new feature that isn't related to rules and removed status: needs investigation triage needs further investigation labels Apr 8, 2017
@ben-eb
Copy link

ben-eb commented Apr 8, 2017

@jeddy3 caniuse-lite is much smaller on disk, though it remains to be seen if it will improve performance. I hope that it does, but of course there is a small overhead to unpacking the data when the module is run. The module is optimised for size not performance (indeed, it's about 7 times smaller).

At the very least I hope we can start a conversation about providing alternative ways to consume caniuse data through subsets like this one.

@davidtheclark
Copy link
Contributor

After reading https://pouchdb.com/2016/01/13/pouchdb-5.2.0-a-better-build-system-with-rollup.html and https://medium.com/@Rich_Harris/how-to-not-break-the-internet-with-this-one-weird-trick-e3e2d57fee28, I'm thinking that the only way to solve this would be to create a bundle with Rollup, and that might be just fine, or at least worth an experiment. Because we wouldn't be transpiling any code (except imports/exports), just concatenating, the built file would still be legible and debuggable (just long).

What I'd like to know is will this complicate using stylelint as a pinned-github-tag dependencies.

I don't know of a way to ease this — but I also don't know if it's worth worrying about too much.

So, when somebody has the time and wants to take a shot at this problem, I suggest looking codemodding to ES2015 module syntax and using Rollup.

@m-allanson
Copy link
Member

I experimented with this the other day, we should be able to use rollup-plugin-commonjs to avoid having to change any existing stylelint code.

This is a rollup config that worked for me on stylelint/master:

"use strict"
const commonjs = require("rollup-plugin-commonjs")
const json = require("rollup-plugin-json")

module.exports = {
  entry: "lib/index.js",
  dest: "dist/index.js",
  moduleName: "stylelint",
  format: "cjs",
  plugins: [
    commonjs(),
    json(),
  ],
}

Here are the before and after results of requiring the bundled and unbundled stylelint 5 times each:

node -e "s=Date.now();require('./lib/index.js');console.log(Date.now()-s)"
800
749
741
762
731
===
mean 756
node -e "s=Date.now();require('./dist/index.js');console.log(Date.now()-s)"
613
673
611
620
665
===
mean 636

However I was unable to succesfully rollup all stylelint's dependencies into one file (using rollup-plugin-node-resolve, the resulting bundle would error when run. I assume that would bring further improvements.

@jeddy3
Copy link
Member

jeddy3 commented Apr 15, 2017

I experimented with this the other day, we should be able to use rollup-plugin-commonjs to avoid having to change any existing stylelint code.

That's awesome.

Here are the before and after results of requiring the bundled and unbundled stylelint 5 times each

100ms improvement isn't too shabby. I think we should roll with it.

However I was unable to succesfully rollup all stylelint's dependencies into one file (using rollup-plugin-node-resolve, the resulting bundle would error when run. I assume that would bring further improvements.

I think we can definitely proceed with rolling up our files for now, and then look to this as an additionally improvement.

@Bigdragon13th
Copy link

I experience this issue as well, I'm using styelint with gulp and it's very slow. You can see my time from time-require here

Start time: (2017-06-23 09:15:03 UTC) [treshold=1%,sorted]
 # [order]  module                                                   time  %
 1 [10299]  /Users/big/Documents/www/c...-boilerplate/gulpfile.js)  44.7s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 73%
 2  [8425]  gulp-stylelint (node_modul...-stylelint/dist/index.js)   9.1s  ▇▇▇▇▇▇▇▇▇ 15%
 3  [8396]  stylelint (node_modules/stylelint/lib/index.js)          8.8s  ▇▇▇▇▇▇▇▇ 14%
 4 [10259]  gulp-pleeease (node_modules/gulp-pleeease/index.js)      6.1s  ▇▇▇▇▇▇ 10%
 5 [10221]  pleeease (node_modules/pleeease/lib/pleeease.js)         5.9s  ▇▇▇▇▇▇ 10%
 6 [10216]  ../lib/processors (node_mo...eeease/lib/processors.js)   5.8s  ▇▇▇▇▇▇ 10%
 7  [7862]  ./utils/checkAgainstRule (...tils/checkAgainstRule.js)   5.8s  ▇▇▇▇▇▇ 9%
 8  [7860]  ../normalizeRuleSettings (...normalizeRuleSettings.js)   5.8s  ▇▇▇▇▇▇ 9%
 9  [7859]  ./rules (node_modules/stylelint/lib/rules/index.js)      5.7s  ▇▇▇▇▇▇ 9%
10  [9096]  gulp-autoprefixer (node_mo...lp-autoprefixer/index.js)   4.9s  ▇▇▇▇▇ 8%
11  [9094]  autoprefixer (node_modules...ixer/lib/autoprefixer.js)   4.8s  ▇▇▇▇▇ 8%
12  [8879]  browserslist (node_modules/browserslist/index.js)        3.5s  ▇▇▇▇ 6%
13  [8877]  caniuse-lite (node_modules...e/dist/unpacker/index.js)   3.4s  ▇▇▇ 6%
14 [14140]  ./list (node_modules/bower/lib/commands/list.js)         3.4s  ▇▇▇ 6%
15 [14136]  ../core/Project (node_modu...ower/lib/core/Project.js)   3.4s  ▇▇▇ 5%
16  [8876]  ../../data/features (node_...se-lite/data/features.js)   3.3s  ▇▇▇ 5%
17 [14126]  ./Manager (node_modules/bower/lib/core/Manager.js)       3.2s  ▇▇▇ 5%
18  [1750]  gulp-awspublish (node_modu...-awspublish/lib/index.js)   3.2s  ▇▇▇ 5%
19 [14114]  ./PackageRepository (node_...ore/PackageRepository.js)     3s  ▇▇▇ 5%
20  [1693]  aws-sdk (node_modules/aws-sdk/lib/aws.js)                2.8s  ▇▇▇ 5%
21  [4886]  babelify (node_modules/babelify/index.js)                2.8s  ▇▇▇ 5%
22  [4884]  babel-core (node_modules/babel-core/index.js)            2.8s  ▇▇▇ 5%
23  [4883]  ./lib/api/node.js (node_mo...bel-core/lib/api/node.js)   2.7s  ▇▇▇ 4%
24  [4859]  ../transformation/file (no...sformation/file/index.js)   2.7s  ▇▇▇ 4%
25   [358]  gulp (node_modules/gulp/index.js)                        2.3s  ▇▇▇ 4%
26  [3772]  debowerify (node_modules/debowerify/index.js)            2.2s  ▇▇ 4%
27  [3766]  bower (node_modules/bower/lib/index.js)                  2.1s  ▇▇ 3%
28  [2343]  hipchat-notify (node_modules/hipchat-notify/index.js)      2s  ▇▇ 3%
29 [14112]  ./resolverFactory (node_mo.../core/resolverFactory.js)     2s  ▇▇ 3%
30 [14110]  ./resolvers (node_modules/.../core/resolvers/index.js)     2s  ▇▇ 3%
31  [2340]  request (node_modules/request/index.js)                    2s  ▇▇ 3%
32  [1135]  ./node_loader (node_module...s-sdk/lib/node_loader.js)   1.8s  ▇▇ 3%
33  [2339]  ./request (node_modules/request/request.js)              1.7s  ▇▇ 3%
34  [2873]  gulp-eslint (node_modules/gulp-eslint/index.js)          1.7s  ▇▇ 3%
35  [1096]  ./core (node_modules/aws-sdk/lib/core.js)                1.7s  ▇▇ 3%
36 [14075]  ./GitHubResolver (node_mod...olvers/GitHubResolver.js)   1.7s  ▇▇ 3%
37  [2867]  eslint (node_modules/eslint/lib/api.js)                  1.6s  ▇▇ 3%
38  [3999]  watchify (node_modules/watchify/index.js)                1.6s  ▇▇ 3%
39  [3996]  chokidar (node_modules/chokidar/index.js)                1.5s  ▇▇ 3%
40  [3626]  mout (node_modules/mout/index.js)                        1.4s  ▇▇ 2%
41  [3068]  browserify (node_modules/browserify/index.js)            1.4s  ▇▇ 2%
42   [996]  ./xml/builder (node_module...s-sdk/lib/xml/builder.js)   1.3s  ▇▇ 2%
43 [10031]  autoprefixer (node_modules...ixer/lib/autoprefixer.js)   1.3s  ▇▇ 2%
44   [995]  xmlbuilder (node_modules/xmlbuilder/lib/index.js)        1.3s  ▇▇ 2%
45  [4586]  babel-helpers (node_module...bel-helpers/lib/index.js)   1.2s  ▇▇ 2%
46  [6100]  ./at-rule-no-vendor-prefix...o-vendor-prefix/index.js)   1.2s  ▇▇ 2%
47  [6095]  ../../utils/isAutoprefixab...tils/isAutoprefixable.js)   1.2s  ▇▇ 2%
48  [5648]  gulp-less (node_modules/gulp-less/index.js)              1.2s  ▇▇ 2%
49  [4585]  ./helpers (node_modules/babel-helpers/lib/helpers.js)    1.2s  ▇▇ 2%
50  [4584]  babel-template (node_modul...el-template/lib/index.js)   1.1s  ▇▇ 2%
51   [184]  gulp-util (node_modules/gulp-util/index.js)              1.1s  ▇ 2%
52   [357]  vinyl-fs (node_modules/vinyl-fs/index.js)                  1s  ▇ 2%
53  [1692]  ../clients/all (node_modules/aws-sdk/clients/all.js)    996ms  ▇ 2%
54  [9035]  ./prefixes (node_modules/a...prefixer/lib/prefixes.js)  989ms  ▇ 2%
55  [9198]  pixrem (node_modules/pixrem/lib/pixrem.js)              986ms  ▇ 2%
56  [2618]  ./eslint (node_modules/eslint/lib/eslint.js)            973ms  ▇ 2%
57  [8108]  ./testUtils/createRuleTest...tils/createRuleTester.js)  970ms  ▇ 2%
58  [2456]  gulp-modernizr (node_modules/gulp-modernizr/index.js)   943ms  ▇ 2%
59  [8341]  ./formatters (node_modules.../lib/formatters/index.js)  928ms  ▇ 2%
60  [5260]  gulp-uglify (node_modules/gulp-uglify/index.js)         911ms  ▇ 1%
61  [4581]  babel-traverse (node_modul...el-traverse/lib/index.js)  904ms  ▇ 1%
62 [14073]  ../../util/extract (node_m...ower/lib/util/extract.js)  888ms  ▇ 1%
63   [994]  ./XMLBuilder (node_modules...uilder/lib/XMLBuilder.js)  881ms  ▇ 1%
64  [3995]  ./lib/fsevents-handler (no.../lib/fsevents-handler.js)  877ms  ▇ 1%
65  [3994]  fsevents (node_modules/fsevents/fsevents.js)            862ms  ▇ 1%
66  [4557]  ./path (node_modules/babel...averse/lib/path/index.js)  858ms  ▇ 1%
67  [2448]  gulp-util (node_modules/gu...dules/gulp-util/index.js)  833ms  ▇ 1%
68   [311]  ./lib/src (node_modules/vinyl-fs/lib/src/index.js)      813ms  ▇ 1%
69  [3988]  node-pre-gyp (node_modules...-gyp/lib/node-pre-gyp.js)  804ms  ▇ 1%
70  [1846]  gulp-scp2 (node_modules/gulp-scp2/index.js)             801ms  ▇ 1%
71  [5647]  /Users/big/Documents/www/c...de_modules/less/index.js)  780ms  ▇ 1%
72  [8336]  ./stringFormatter (node_mo...tters/stringFormatter.js)  779ms  ▇ 1%
73  [5646]  ./lib/less-node (node_modu...s/lib/less-node/index.js)  771ms  ▇ 1%
74 [13972]  ../../util/download (node_...wer/lib/util/download.js)  764ms  ▇ 1%
75  [9192]  postcss (node_modules/pixr...s/postcss/lib/postcss.js)  763ms  ▇ 1%
76  [7059]  ./no-browser-hacks (node_m...o-browser-hacks/index.js)  755ms  ▇ 1%
77  [7058]  stylehacks (node_modules/stylehacks/dist/index.js)      742ms  ▇ 1%
78 [13966]  request (node_modules/bowe...modules/request/index.js)  739ms  ▇ 1%
79  [5247]  ./composer (node_modules/gulp-uglify/composer.js)       719ms  ▇ 1%
80  [1839]  scp2 (node_modules/scp2/index.js)                       707ms  ▇ 1%
81  [5246]  ./lib/minify (node_modules/gulp-uglify/lib/minify.js)   705ms  ▇ 1%
82 [13965]  ./request (node_modules/bo...dules/request/request.js)  695ms  ▇ 1%
83  [1838]  ./lib/scp (node_modules/scp2/lib/scp.js)                695ms  ▇ 1%
84  [7250]  ./no-indistinguishable-col...uishable-colors/index.js)  687ms  ▇ 1%
85  [8328]  table (node_modules/stylel...ules/table/dist/index.js)  683ms  ▇ 1%
86  [7249]  colorguard (node_modules/colorguard/index.js)           677ms  ▇ 1%
87  [7248]  ./lib/colorguard (node_mod...rguard/lib/colorguard.js)  652ms  ▇ 1%
88  [2855]  ./cli-engine (node_modules/eslint/lib/cli-engine.js)    628ms  ▇ 1%
89  [1837]  ./client (node_modules/scp2/lib/client.js)              626ms  ▇ 1%
90   [993]  ./XMLElement (node_modules...uilder/lib/XMLElement.js)  626ms  ▇ 1%
91 [13640]  bower-registry-client (nod...egistry-client/Client.js)  623ms  ▇ 1%
92  [8255]  ./createStylelint (node_mo...t/lib/createStylelint.js)  621ms  ▇ 1%
Total require(): 27647
Total time: 1m 1.1s

As you can see, Stylelint is top of the list here.

@alexander-akait
Copy link
Member

@Bigdragon13th maybe your don't ignore some files (tests, examples, build directory and etc stuff)?

@Bigdragon13th
Copy link

@evilebottnawi It's not runtime that is slow, it's the loading time stylelint use when we do require("stylelint") that is very slow.

@Mottie
Copy link

Mottie commented Aug 29, 2017

Has anyone successfully bundled Stylelint using rollup? I created a stylelint-bundler repo reporting my attempts, work-arounds and failure at making a stand-alone version of Stylelint. Maybe someone with more experience with rollup can help? Paging @bfred-it 😉.

Also, the namedColorData file is 61KB. I think all the func definitions aren't necessary as conversion between the color formats shouldn't be that difficult.

@fregante
Copy link

@Mottie I gave up on rollup for complex tasks. Try webpack or backpack https://github.com/jaredpalmer/backpack/

@jeddy3
Copy link
Member

jeddy3 commented Sep 10, 2017

@Mottie Big thanks for making a start on attempting to bundle stylelint using rollup, and for documenting your progress in your repo. It looks to be a tricky one!

Has anyone successfully bundled Stylelint using rollup?

I don't believe any of the core team have had time to look into this, and so any progress you make would be greatly appreciated. Good performance is one of the goals of stylelint.

I understand that prettier uses rollup for their build. Perhaps you'll find some inspiration there to overcome the blockers you've encountered.

Also, the namedColorData file is 61KB. I think all the func definitions aren't necessary as conversion between the color formats shouldn't be that difficult.

Agreed. Feel free to contribute that optimisation. You can use the built-in benchmarking tools to gauge the performance impact on the rules that use this data.

@vankop
Copy link
Member

vankop commented May 1, 2020

Without the above exclusions, I'm getting a minified bundle-size of 3.5mb and require-time of 120ms.

I think without bundling time is the same. Last time I checked it was around ~140ms (but some fixes for loading time wasn't merged yet)

@jeddy3
Copy link
Member

jeddy3 commented May 1, 2020

I think without bundling time is the same. Last time I checked it was around ~140ms (but some fixes for loading time wasn't merged yet)

That's correct. The gains of bundling are only apparent when the syntaxes are bundled separately. This makes sense as the bundling replaces lazyloading.

  • No bundling: 120ms
  • Bundled all together: 120ms (6mb)
  • Bundled with separate syntax bundles: 80ms (1mb, with lazyloaded syntax bundles varying from 200kb/20ms for Scss to 2mb/100ms for CSS-in-JS)

I should have made that clearer in my original post.

@vankop
Copy link
Member

vankop commented May 1, 2020

What do you think about marking rules as externals? Why we need to bundle them eagerly?

@jeddy3
Copy link
Member

jeddy3 commented May 1, 2020

Why we need to bundle them eagerly?

For the browser (#3935), e.g. tools like JS Fiddle.

@hudochenkov
Copy link
Member

Lodash might not go away, because other dependencies have it:

├─┬ @stylelint/postcss-css-in-js@0.37.1
│ └─┬ @babel/core@7.9.0
│   ├─┬ @babel/generator@7.9.5
│   │ └── lodash@4.17.15  deduped
│   ├─┬ @babel/helper-module-transforms@7.9.0
│   │ └── lodash@4.17.15  deduped
│   ├─┬ @babel/traverse@7.9.5
│   │ └── lodash@4.17.15  deduped
│   ├─┬ @babel/types@7.9.5
│   │ └── lodash@4.17.15  deduped
│   └── lodash@4.17.15  deduped
├─┬ postcss-reporter@6.0.1
│ └── lodash@4.17.15  deduped
└─┬ table@5.4.6
  └── lodash@4.17.15  deduped

Instead we'll have much more dependencies. One for every lodash function.

@jeddy3
Copy link
Member

jeddy3 commented May 1, 2020

Lodash might not go away, because other dependencies have it:

Interesting. I wonder what ncc's support for tree-shaking is like?

It looks like:

I've been meaning to remove the dependency on postcss-reporter. We only use a tiny util from it in the string reporter.

I think it's worth looking into further as lodash is a significant chunk of the bundle weight at over half a meg and it impacts require time.

I should have made that clearer in my original post.

I should have also been clearer that reducing the require time from 120 to 80 is only half the story. It looks like a bundled stylelint (with the separate syntaxes) is twice as fast to run, which would benefit everyone but especially extension authors and people in the Makefile world, like the original poster.

@jeddy3
Copy link
Member

jeddy3 commented May 2, 2020

I tried it locally and we can cherry-pick the lodash function we use. We'd still have a single lodash dependency, but we'd cherry-pick functions from it (as table and babel already do).

For example:

// package.json
{
  "dependencies": {
    "lodash": "^4.17.15",
  }
}
// stringFormatter.js
const _escapeRegExp = require('lodash/escapeRegExp');
const _flatMap = require('lodash/flatMap');
const _sortBy = require('lodash/sortBy');
const _uniqBy = require('lodash/uniqBy');

It knocks off a chunk of weight from the browser bundle.

@hudochenkov
Copy link
Member

Can we use https://github.com/lodash/babel-plugin-lodash to automate this for bundle?

@jeddy3
Copy link
Member

jeddy3 commented May 2, 2020

Can we use https://github.com/lodash/babel-plugin-lodash to automate this for bundle?

The plugin only supports ES Modules syntax. I used the plugin to rewrite the source files (having first replaced all the lodash requires with imports) and then I converted them back commonjs when I did my local test.

This was referenced May 6, 2020
@m-allanson
Copy link
Member

There is an ESLint rule that we can use to ensure method imports from lodash. See eslint-plugin-lodash/import-scope.

I have written a codemod we can use to make this change across the stylelint repos. See m-allanson/codemod-lodash-requires. I'm planning to work on this once #4796 is finished.

@m-allanson
Copy link
Member

m-allanson commented Jun 6, 2020

This is a useful tool for analysing require times: require-so-slow.

Here's the output from the latest version of stylelint, running on my machine (requires Chrome): devtools timeline viewer (stylelint.trace)

Screenshot 2020-06-06 at 12 42 44

Create your own trace with npx require-so-slow stylelint.

m-allanson pushed a commit that referenced this issue Jul 17, 2020
* Remove `postcss-reporter` package

This change removes the `postcss-reporter` package and reduces the dependencies.
As you see the changed `package-lock.json`, this package depends on:

- `chalk`
- `lodash`
- `log-symbols`
- `postcss`

See also #2454 (comment)

We've used only the tiny utility function `getLocation()` in the package.
This function just returns an object with the `line` and `column` property in our project,
so I think we can replace it with a simple code.

https://github.com/postcss/postcss-reporter/blob/f01a601ea2cd41d626e561969d66a765b3afcb2d/lib/util.js#L4-L13

* Fix test name: `stringFormatter` -> `verboseFormatter`
@jeddy3 jeddy3 mentioned this issue May 19, 2021
@jeddy3
Copy link
Member

jeddy3 commented Oct 6, 2021

ESBuild can bundle the v14 branch without issue. If anyone fancies it, they're welcome to investigate bundling for the 14.0.0 release and open a pull request. It's something we'll need to investigate later down the line anyway for #5291.

I suspect we'll want to bundle:

  • ./lib/index.js
  • ./bin/stylelint.js

Stylelint should remain installable from GitHub. We'll need to investigate how this is done with np, perhaps using npm lifecycle scripts and the --content & --test-script np flags?

We'll want to weigh up the performance gains against any additional maintenance burden. Investigating bundling and opening a pull request will give us the opportunity to assess that.

You can give ESBuild a go with:

./node_modules/.bin/esbuild ./lib/index.js --bundle --outfile=out/index.js --format=cjs --platform=node --minify --metafile=lib.json

Which will result in a 814.3kb bundle with consistently sub-100ms require times (down from the 200ms for 13.x and 130ms from an unbundled 14.x).

If you like, you can upload the generated metafile to https://www.bundle-buddy.com to see a breakdown. Thanks to all the hard refactoring work done (including the removal of lodash) the breakdown looks clean.

@jeddy3 jeddy3 changed the title Reduce time taken to require stylelint Fix time taken to require stylelint Jan 18, 2022
@jeddy3 jeddy3 changed the title Fix time taken to require stylelint Reduce time taken to require stylelint Jan 18, 2022
@jeddy3 jeddy3 changed the title Reduce time taken to require stylelint Bundle to reduce time taken to require Jan 18, 2022
@jeddy3 jeddy3 added status: ready to implement is ready to be worked on by someone and removed status: wip is being worked on by someone labels Jan 18, 2022
@jeddy3 jeddy3 changed the title Bundle to reduce time taken to require Fix require time performance Jun 24, 2023
@jeddy3 jeddy3 added type: bug a problem with a feature or rule and removed status: ready to implement is ready to be worked on by someone labels Jun 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: bug a problem with a feature or rule type: enhancement a new feature that isn't related to rules
Development

No branches or pull requests