GithubHelp home page GithubHelp logo

Comments (21)

ericmasiello avatar ericmasiello commented on May 27, 2024 1

I saw a few references to statics in other github issues making it difficult to tree shake due to potential side effects caused by the statics. The way I’ve been testing if the statics are affecting things is by opening up the bundle produced by app consuming the library and grepping for the classNames of the various components. Ideally I should only see the className for the one(s) I imported.

TBH I wasn’t testing this using the CRA app you included in your example folder but instead I created a separate example app and using a basic Webpack configuration in production mode.

I’ll experiment some more tomorrow using the CRA app you included to see if there’s any difference in my results. I’ll let u know what I find. Thanks!

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024 1

It makes sense what you're saying. I just tested with a clean Webpack config where I import { Ellipsis } from 'react-css-spinners'. Both if I add

Ring.propTypes = {
  className: PropTypes.string
}

and if I don't, grep lds-ring dist/main.js doesn't return anything. The size goes up by about 800 B but that's it; only lds-ellipsis is there. (I'm using production mode with npm link and [email protected] i.e. latest)

I am curious to see when it fails though. If you're using this library as a demo, perhaps you could post a gist with a reproduction?

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024 1

Never mind, I got it when using both propTypes and defaultProps. Strange how it doesn't happen when using either one alone. As a quick workaround, I'd just put defaults in the function signature, e.g.

const Ring = ({ className = 'ring' }) => (...)

But it's not solving the core problem. Also, I tried building the same code with Rollup, and it tree-shakes correctly (with and without terser plugin), so it seems to be an issue (possibly a bug) with terser itself. I couldn't get it to compress by tweaking pure_getters and passes options either.

Another way around this is if you use babel-plugin-transform-react-remove-prop-types with mode set to unsafe-wrap (which is what @material-ui/core uses). This way you'd get propTypes wrapped with "production"!==process.env.NODE_ENV&&(Ring.propTypes={...}) checks. terser already compresses conditionals by default, so with webpack, (a) in dev build, you'd get the prop-types without tree shaking, and (b) in prod build, prop-types will be stripped away, and your modules tree-shaken. I can confirm that this works with our example above.

That's all I have to say. Sorry, I haven't run into this before. Anyway, thanks for the great find! I'll have some studying to do this evening 😄

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024 1

Well, this was a very enlightening discussion. The key takeaways are

With hooks, classes are effectively rendered obsolete, so you probably shouldn't use them anyway. Also, defaultProps are easily substituted with default parameters. If you're building an app (i.e. not a library), consider using TypeScript (or Flow), so you wouldn't need prop-types at all. (You can of course build a library with TS, but you'd still need prop-types for non-TS users).

I am going to close the issue for now. Feel free to re-open if the above didn't help.

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024 1

@ericmasiello I meant propTypes. Not sure if there's going to be a fix in terser any time soon. IMO it's best to +1 on the thread to keep visibility, but maybe it's not that critical. As far as ESLint, not that I'm aware of, the reason being that it's a tool-specific issue. While it's reproducible in webpack with terser, the same cannot be said about rollup.

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024

Hi @ericmasiello, I don't think static props themselves are the culprit here. Are you seeing the build size go up after importing prop-types? Then that's probably because prop-types are not excluded from the bundle.

If you ship a React library, you'd want to prevent external dependencies like React and PropTypes from being bundled to avoid duplication. Rollup and other bundlers provide a way to mark these peer dependencies as external. In this particular repo, React is already external, so you just need to exclude PropTypes as well. Also, the way your PropTypes are written, they'd end up in both dev and prod bundles. You might want to look into a Babel plugin to wrap them with a conditional.

Let me know if this helps. I might follow up on the series with a video demonstrating this in practice.

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

@alex996 - thanks for the response.

I’ll give what you suggested a shot but I’m quite certain statics are an issue. I’ve experimented with multiple variants of the example above. To simplify things, my problem still exists if you remove all the prop type code and only keep the defaultProps portion.

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

And to further clarify, this is less to do with the bundle size of the library (which I would expect to get larger as I add more code) but more to do with webpacks ability to successfully tree shake the bundled library once any of the components in the library have statics

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024

I tried with create-react-app using PropTypes. and the prod build does seem to change in size depending on the component import. I'd be surprised if the statics are ruining tree shaking.

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

Yup will do tomorrow. Thanks again for investigating.

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

I did a bit more experimenting based on your last post. Here's what I saw:

  1. Add static defaultProps to each of the 3 components
  2. Bundle the library
  3. Go into the app and rebundle, only importing Ellipsis
  4. I only see the ellipsis component in the bundle ✅

Thennnnn...

Change Ring to be class component with defaultProps

class Ring extends React.Component {
  render () {
    return (
      <>
        <style>{styles}</style>
        <div className={`lds-ring ${this.props.className}`}>
          <div />
          <div />
          <div />
          <div />
        </div>
      </>
    )
  }
}

Ring.defaultProps = {
  className: 'foo'
}

Rebundle the library and rebundle the app. Now, even though i'm only importing Ellipsis, Ring is included in the bundle 🤷‍♀️

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

This whole thing strikes me as odd. It feels like there should be a solution to this problem given that defaultProps is thing that React is intended to support.

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024

I noticed that it's even worse than that. As soon as you bundle a class (any class, doesn't have to extend React.Component) in the library, it gets included in the app bundle, whether you import it or not ⚠️ However, if that class (strictly speaking, its transpiled function) is annotated with /*#__PURE__*/, then it gets correctly tree shaken by terser.

It works until you assign 2 or more static members, i.e. no static props or a single one e.g. static propTypes = { ... } (or Ring.propTypes = ..., whichever syntax) doesn't affect tree shaking as long as the class is annotated. But as soon as you add a second member, e.g. defaultProps, tree shaking breaks, and /*#__PURE__*/ doesn't help anymore. Seems like the same issue as with functional components.

I tried babel-plugin-annotate-pure-calls to have annotations at build time, but it seems Rollup strips them away as comments.

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

Oi.

So I'm trying to make sense of all this and directionally where it should lead me (or us if you're equally interested). The summary thus far seems to be:

  1. Classes with 2 or more static properties cannot be tree shaken: feels like a Webpack issue but maybe it's a terser issue since terser is used for minification
  2. When attempting to annotate classes as /*#__PURE__*/, they are being stripped out: feels like either a rollup, rollup-plugin-terser, or terser issue
  3. Components with propTypes set as statics can be consistently tree shaken by using babel-plugin-transform-react-remove-prop-types
  4. We can circumvent problems related to defaultProps by using default property ES2015 syntax:
// this...
const Foo = (props) => { /* ... */ }
Foo.defaultProps = {
  className: 'foo',
};

// becomes...
const Foo = ({ className = 'foo', ...rest}) => { /* ... */ }

Does that cover everything?

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024

Pretty much. I'll add that classes don't seem to tree shake at all, whether with or without any static props. (Unless you deliver them as-is, without transpiling through @babel/plugin-transform-classes). If you annotate them as pure, then they tree shake, but there is a cap of one static property. (There must be a may to preserve the annotations in Rollup, but I haven't dug very deep into it)

With functions, you have the same cap. Two or more static props break tree-shaking, while one or none are fine. With prop-types specifically, you should either wrap them in a conditional yourself or use a plugin (so they don't end up in prod bundles). This way, having both propTypes and defaultProps is fine, as long as you don't add a third, e.g. displayName.

That'd be my summary. These are mostly observational points though, so there might be more nuances and workarounds. I've seen a PR in Babel that tries to optimize the way static class props are transpiled, but it hasn't landed in over a year. Otherwise, terser might be patched up to better handle property assignment.

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

Good point about the displayName. I forgot about that one. I wonder if all these tree shaking limitations are why Reactstrap creates their module output by just running babel against the src directory https://github.com/reactstrap/reactstrap/blob/master/package.json#L22.

This means that if you wanna import a component, you have to do:

import Button from 'reactstrap/Button'`

instead of:

import { Button } from 'reactstrap'

But at least this way you can avoid all the tree shaking limitations.

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024

Well, this is confusing now. If you look at reactstrap's Button, it's a /*#__PURE__*/ function with two static members, propTypes and defaultProps, yet it gets tree shaken correctly. Same with Carousel which has three static props. In fact, they endorse named imports in the readme. In other words,

import { Card } from 'reactstrap' // 5440 B

and

import Card from 'reactstrap/es/Card' // 5440 B

yield the same bundle size. In the end, Webpack concatenates all modules together and runs them through terser, which is when the size should blow up, but it doesn't! I wonder why 🤔

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

Interesting. Is my understanding correct that when you do this:

import { Card } from ‘reactstrap’

It is referencing the file found in /es/index.js? And additionally, that file is not built with rollup but instead built with purely Babel?

from react-css-spinners.

alex996 avatar alex996 commented on May 27, 2024

Exactly. Their module fields points to es/index.js. In Webpack, module takes precedence over main, so that's where it's pulling Card from. They build the es directory with build:esm script which only invokes Babel, so the code is transpiled, but not concatenated or minified (this will be done at the integration level by Webpack anyway).

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

@alex996 what exactly do you mean by:

don't spread parent props (oliviertassinari/babel-plugin-transform-react-remove-prop-types#183)

you say “parent props” but the link you provide is referencing propTypes. Can you clarify?

Also, are you aware if Terser plans to address this “no more than 2 static properties breaks tree shaking” issue?

I’m trying to build out a UI library and I’m trying to figure out the best way to enforce the rule of no more than 2 static properties. Are you aware of any eslint rules that could help enforce this? Or do you have any other suggestions?

from react-css-spinners.

ericmasiello avatar ericmasiello commented on May 27, 2024

I ended up making an eslint rule for my project that at least solves my immediate problem

rules: {
    'no-restricted-properties': [2, {
      property: 'defaultProps',
      message: 'Specifying static properties such as defaultProps can break tree shaking. Use default parameter values instead. See http://es6-features.org/#DefaultParameterValues.'
    }],
}

Technically, this prevents you from using defaultProps on anything, even non-React objects but i don't expect anyone on my team to be doing that so it seems like a reasonable hack.

from react-css-spinners.

Related Issues (5)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.