callstack / linaria

Zero-runtime CSS in JS library
https://linaria.dev
MIT License
11.7k stars 417 forks source link

Build time in 1.3.1 #392

Closed SpawnAtis closed 1 year ago

SpawnAtis commented 5 years ago

Environment

"linaria": "^1.3.1", "webpack": "^4.30", "@babel/cli": "^7.4.3", "@babel/core": "^7.4.3", @babel/node": "^7.2.2", "react": "^16.8.6",

Description

After migration to 1.3.1 from 1.3.0 the build time increased in 5 times 😢. After analyzing changes in 1.3.1. suspicion fell on babel/evaluate.js.

In our project we have many "Base" components that use Styling custom components with interpplations, f.e. <LayoutContainer />, <DisplayName />, <Box /> e.c.

const Container = styled(LayoutContainer)<LayoutContainerProps>`  
  background-color: ${componentTheme.stripe.layoutContainerBackgroundColor};  
  border: ${componentTheme.stripe.layoutContainerBorder};  
  border-radius: ${baseTheme.radius};  
  margin: ${componentTheme.stripe.layoutContainerMargin};  
  align-items: stretch;  
`;

The problem is when u create your custom components that based on a "Base" one and use interpolation, Prop based styles the evalute.js executes too many times, and this cause such result for build time ( on my opinion). I tried to output log in TaggedTemplateExpression.js console.log(state.file.opts.filename):

Only small part of the whole log Log.txt

Is this a bug or I am doing something wrong ? Thx

P.S. Currently, I have a bunch of circular decencies in the project. When I fix one the build time decreases on 10-20 sec. 🤔 Can it be a reason of the problem?

Reproducible Demo

babel.config webpack.dev.config.txt

linaria.config.js

module.exports = {
    evaluate: true,
    displayName: process.env.NODE_ENV !== 'production',
};
Hotell commented 5 years ago

Currently, I have a bunch of circular decencies in the project. When I fix one the build time decreases on 10-20 sec.

I would fix this first, then do perf evaluation. circular deps are highway to hell

Hotell commented 5 years ago

So similar issue here (not related to version I guess).

We extracted common styles to styles package within monorepo. now all components use this as a reference when defining styles via css.

import {css} from 'linaria'
import {theme} from '@twisto/styles'

const root = css`
 color: theme.primary;
`

image

This change increases storybook build from 60 seconds to 340 seconds. 🥺 🤒

SpawnAtis commented 5 years ago

I fixed all circular deps, it reduced time on 30 sec. Now, with linaria 1.3.0 - 1.46m, after upgrade to 1.3.1 - 4.75. 😞

Screen Shot 2019-04-26 at 10 27 44
Hotell commented 5 years ago

I feel your pain... we are facing similar issues like mentioned above... had to redo whole building pipeline to make it usable....

steffenmllr commented 5 years ago

Having the same Problem with high build times. The 1.3.0 start time (storybook) is at 2.35min goes up by a half a minute to 3.02 min. We do not have circular dependencies. The github actions build step is at around 5-7min. - this is a medium / small sized project.

Any suggestions @Hotell how to make this more usable? I don't mind long build times in the CI but the hot reload speed is (depending on the widget I'm working) from 1sec to 45sec - which is too slow for a sane developer experience 😄

Where are the bottlenecks within linaria ? And how do other css in js frameworks compare ?

satya164 commented 5 years ago

Sorry there have been a lot of holidays recently. I think we can improve the build time by implementing some sort of caching. I'll look into it soon.

marbiano commented 5 years ago

Having a lot of issues with build times too. Is Linaria not ready yet for real use of styled composition?

kaiserkoenig commented 5 years ago

I removed all these const Container = styled(LayoutContainer)<LayoutContainerProps>'...'; in our project and replaced them with const Container = css'...';. Then i used this as a className for <LayoutContainer className={Container} />

  1. works well without any issues
  2. build time is even faster from 2:17min to 1:37min
  3. sometimes you may need css' && { } ' to overwrite the default css

It was a bunch of work and not easy in every case. Nevertheless you should fix that bug

marbiano commented 5 years ago

@kaiserkoenig curious about 3, can you expand?

kaiserkoenig commented 5 years ago

sure... if your CSSinJS or Linaria looks like this

const MyDiv = styled.div' background-color: black; ' and const myAdvancedStyles = css' && { background-color: pink; } ' the builded CSS will be .myAdvancedStyles.myAdvancedStyles {background-color: pink;} . if you add this <MyDiv className={myAdvancedStyles} /> Mydiv will be pink.

In most cases this is not necessary, but sometimes, especially when you get className from different, interleaved components, a && is the new !important ;)

OriginalEXE commented 5 years ago

:wave: Thank you for writing and maintaining this library! Anything we could do to help facilitate the solution to this issue? I am faced with very long HMR times (6s) in quite a small project. As I add more styles, it will continue to get worse and it's already pretty much unusable (we are spoiled, I know :slightly_smiling_face: ).

I have noticed that you had a branch for in-memory builds here: https://github.com/callstack/linaria/commit/4c449ac0424c6aae630b238d3450b39b006e3d0d

What was the problem with it, it did not give the satisfying speedups?

Hotell commented 5 years ago

another numbers, when used with storybook (no plugins, no stories loaded):

pbitkowski commented 5 years ago

Thank you for your feedback! 🙌 We're currently working on TS migration which is blocker for us (#398), but it's almost ready. After that we can work on optimizations.

@OriginalEXE if you want to contribute the best way would be creating a proposal issue with info about how do you want to solve this problem. If it will be a good idea, then we can guide you through development process. 😃

It would be great if you could share with us some repro that has extremely slow building process. 🐌

Thanks for using linaria and your patience! We'll make it better for you.

steffenmllr commented 5 years ago

Those are the build speeds from emotion vs linaria for the same project. I just sed the import for now.

╭───────────────────────────────────────────────────╮
│                                                   │
│   Storybook 5.0.11 started                        │
│   2.35 min for manager and 2.38 min for preview   │
│                                                   │
│   Local:            http://localhost:9001/        │
│   On your network:  http://192.168.1.205:9001/    │
│                                                   │
╰───────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────╮
│                                                   │
│   Storybook 5.0.11 started                        │
│   19 s for manager and 20 s for preview           │
│                                                   │
│   Local:            http://localhost:9001/        │
│   On your network:  http://192.168.1.205:9001/    │
│                                                   │
╰───────────────────────────────────────────────────╯
Hotell commented 5 years ago

we have similar results ( as we're adding more components ):

linaria -> 4 minutes emotion -> 16 sec

Quick/Dirty fix:

For development purposes we are using emotion with webpack aliases. for production build we switch back to linaria. 💣🚨

steffenmllr commented 5 years ago

@Hotell that is also what I'm implementing right now - best of both worlds 😄

Edit: Would be nice to have the same dev performance in linaria though

Hotell commented 5 years ago

@steffenmllr just a headsup. You might experience various css bugs, as emotion is injecting classes in unpredictable order within <head/>, which causes specificity issues !

quick fix:

const error = css`
&& {
  color: theme.palette.red
}`
thymikee commented 5 years ago

This is pretty serious issue and seems to be introduced by these 2 commits: 15986c8, 96b8284. We'd appreciate help in investigating which one is exactly to blame and, if both are, how they affect the build times separately. That should make it easier to focus on a quick fix for that.

pbitkowski commented 5 years ago

Could you test your projects with our new alpha release? Let me know if build time improved.

pbitkowski commented 5 years ago

Is there any improvement? :)

steffenmllr commented 5 years ago

@pbitkowski just ran the build command, didn't test the output:

"linaria": "1.3.0", > 146.4s
"linaria": "1.3.1" > 138.3s
"linaria": "1.4.0-alpha.1", > 31.3s
SpawnAtis commented 5 years ago

@pbitkowski "linaria": "1.3.1" > 2.78 "linaria": "1.4.0-alpha.1", > 47.8s 👍

But I got an error :

Screen Shot 2019-06-07 at 13 52 44

import * as theme from '../../theme';

I think it is connected with syntax : import * as content from './somepath'

demo to reproduce: click. Just restart the sandbox and look at terminal.

loader problem 🤔 ?

kryops commented 5 years ago

Small frontend, 43 linaria components across 15 files.

Build times definitely improved in 1.4.0-alpha.1, though interestingly my production build is a lot faster than the development build (both builds include source maps)

Development build:

Production build:

cometkim commented 5 years ago

My production build time has reached almost 10 minutes and I had some investigation today.

The reason why linaria is going to slow down is "core-js" in my case because linaria loader does:

So it spent a lot of times for a few packages like core-js, polished, which have many sub-packages.

Traced log on a very simple component code:

loader
  file: /home/comet/Workspace/src/github.com/.../src/components/sections/...
  transform.js
    parse: 10.765ms
    transformFromAst: 7013.279ms
      babel/evaluate.js
        resolve: 0.351ms
        evaluate: 1056.369ms
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/...node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/e
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/_
          ... many many many more core-js deps
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/e
          /home/comet/Workspace/src/github.com/.../node_modules/core-js/modules/e
          /home/comet/Workspace/src/github.com/.../node_modules/stylis/stylis.js:
          /home/comet/Workspace/src/github.com/.../node_modules/object-assign/ind
          /home/comet/Workspace/src/github.com/.../node_modules/prop-types/lib/Re
          /home/comet/Workspace/src/github.com/.../node_modules/prop-types/checkP
          /home/comet/Workspace/src/github.com/.../node_modules/react/cjs/react.d
          /home/comet/Workspace/src/github.com/.../node_modules/react/index.js:
          /home/comet/Workspace/src/github.com/.../node_modules/join-react-contex
          /home/comet/Workspace/src/github.com/.../src/utils/screen.ts: 116.451ms
          /home/comet/Workspace/src/github.com/.../src/utils/css.ts: 1020.203ms
          /home/comet/Workspace/src/github.com/.../src/components/sections/Footer
      babel/evaluate.js
        resolve: 0.373ms
        evaluate: 1168.315ms
          ...same pattern here
      babel/evaluate.js
        resolve: 0.422ms
        evaluate: 1300.890ms
          ...same pattern here
      babel/evaluate.js
        resolve: 0.284ms
        evaluate: 1480.975ms
          ...same pattern here
      babel/evaluate.js
        resolve: 0.341ms
        evaluate: 1620.540ms
          ...same pattern here
    preprocessor: 2.812ms
cometkim commented 5 years ago

Quick fix: Install this script in your postinstall to enable module cache

const { readFileSync, writeFileSync } = require('fs');
const filePath = require.resolve('linaria/lib/babel/extract');
const original = readFileSync(filePath, 'utf-8');
writeFileSync(filePath, original.replace(/\.invalidate\(\)/g, ''), 'utf-8')

Before this fix: before-boost-build before-boost-hmr

After fix: after-boost-build after-boost-hmr

stage Before(ms) After(ms)
Initial build ~249000 ~11000
HMR ~41500 ~900

And this is proportional to the product of the number of css tags used and the number of dependencies in the file.

Fortunately, there was no effect in my production without having to invalidate dependencies.

brandonkal commented 5 years ago

Even after disabling the invalidate as suggested by @cometkim, HMR updates are extremely slow at 30seconds with the latest develop branch. This makes development less enjoyable and productive. Hopefully the cause can be found. @cometkim how did you generate that trace log?

cometkim commented 5 years ago

It was manually inserted using console.time() and console.timeEnd().

@brandonkal is there no speed improvements after disabling invalidation? actually I'm enjoying the pretty fast HMR after that, so I'm not sure something there another cause or just bundle size is still matter.

Could you check again the Module.invalidate() is not being called by your node_modules/linaria/lib/babel/extract or share reproduce/trace?

cometkim commented 5 years ago

@brandonkal FYI, It was tested on v1.3.1, probably v1.4.0-alpha or develop has different

brandonkal commented 5 years ago

@cometkim Thank you! I found that I had commented out the invalidate() in the enter() but not in exit(). HMR is now down to ~1-2 seconds. Before it was running the length of the whole build.

It looks like this works because the cache is only kept in memory and so it will be cleared whenever a production build finishes.

brandonkal commented 5 years ago

@satya164 I am curious on your reasons for calling Module.invalidate() on enter and exit. Removing those lines improved performance significantly without issue when using webpack to bundle things.

brandonkal commented 5 years ago

Discovered another inefficiency that causes things to be slow. Documenting it here in case someone is investigating.

The shaker will shake the current file, but after that, there is no restraint. Imported dependencies are resolved, transformed by babel, and evaluated. This is problematic depending on how you structure your code. Also if babel adds polyfills during the transform, those will be evaluated.

If you access all your components through an index file, your entire component library will be transpiled several times for each input file.

Example: Current file being parsed by webpack loader:

import { Button } from 'components'
const Hero = styled.section`
  ${Button} {
    color: red;
  }
}

index.tsx aliased as components

export * from 'buttons'
export * from 'utils'
export * from 'bigThing'
//...

All files are evaluated. The transforms are not cached. This complete evaluation process repeats for every file that the webpack loader receives.

I am working on solving these problems in my fork which is already 100+ commits ahead. I have some ideas on how to improve caching. Fixing these issues should bring the initial build time down.

adamseckel commented 5 years ago

@brandonkal any progress on your fork solving the issue when using an index.ts for exporting components?

brandonkal commented 5 years ago

@hemlok Yes, thank you for the interest I am seeing much faster compilation times with the changes. Currently working on documenting what I've changed locally and hope to push a release to GitHub soon.

adamseckel commented 5 years ago

For anyone still struggling with this, I was able to dramatically reduce rebuild times on 1.3.1 from 70s to around 1-5s by using cache-loader to cache the results of a babel-loader step that feeds into the linaria/loader step. Unfortunately, initial build times are still slow with 90% of time spent in the linaria/loader step...

jarodpeachey commented 4 years ago

@brandonkal Your fork looks awesome! One question: can I keep the same usage of Linaria in my app? I'm using Linaria in over 141 files, so as you can imagine, updating the syntax would be painful.

For example, can I still use this syntax for passing props, and using a custom theme:

Creating element

<Nav disabled={selectedStepIndex === 0} id="howItWorksLeftArrow" onClick={() => this.handleChangeSlide(false)}>
    <ArrowLeftIcon classes={{ root: classes.navIconRoot }} />
</Nav>

Styling element

const Nav = withTheme(styled.div`
    display: flex;
    align-items: center;
    justify-content: center;
    margin: auto 0;
    width: 100px;
    height: 100px;
    border-radius: 100rem;
    transition: all 150ms ease-in;
    cursor: ${props => (props.disabled ? 'default' : 'pointer')};
    font-size: 72px;
    background: ${props => (props.disabled ? props.theme.colors.grayPale : props.theme.colors.grayChip)};
  color: ${props => (props.disabled ? props.theme.colors.grayChip : props.theme.colors.brandBlue)};
    &:hover {
      filter: ${props => (props.disabled ? '' : 'brightness(102%)')};
    }
    &:active {
      filter: ${props => (props.disabled ? '' : 'brightness(105%)')};
    }
    @media (max-width: ${props => props.theme.breakpoints.lg}) {
      display: none;
    }
`);

I'd really love to use your fork, because my build takes forever.

brandonkal commented 4 years ago

Yes. You should be fine as the additional syntax I've added are just shortcuts. You can use the beta version now as it is stable. The webpack set up requires postcss in the pipeline. I am planning to push a version 3 with documentation on all that but it might be some time before that happens. Please open an issue with build time comparisons and any questions you end up with.

jarodpeachey commented 4 years ago

@brandonkal Cool, thanks! I actually just submitted an issue on your fork; it's not specific to your fork, but it's a feature request I'd like (or would like to know about if it's already there). My repo is large, and when I run webpack-dev-server, it gets bogged down at the linaria/loader stage, and doesn't advance. The css files are all generated as it should be, but it gives me an error that I've run out of memory.

My question is this: is there a way to disable Linaria from using any memory and instead just creating the files? If there's not, do you think this is a feature you could implement?

mikestopcontinues commented 4 years ago

Just updated to 1.4.1-beta.1 and performance is much better. Are there any additional benefits gained by the other recommendations on this thread? Or do they constitute the changes in the beta?

layershifter commented 1 year ago

Linaria is now on v5. There have not been any activity for years and the discussion does not feel relevant to the current state. Let's close this one.

Feel free to open new issues if you are facing problems 🐱