Comments (21)
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.
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.
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.
Well, this was a very enlightening discussion. The key takeaways are
- use either
propTypes
ordefaultProps
freely - if you use both, you need to strip
propTypes
withunsafe-wrap
- don't use more than two (i.e. avoid a 3rd
name
, see why terser/terser#293) - don't spread parent
propTypes
(oliviertassinari/babel-plugin-transform-react-remove-prop-types#183) - with
class
es, you can only use one (unless annotated with/*#__PURE__*/
)
With hooks, class
es 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.
@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.
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.
@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.
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.
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.
Yup will do tomorrow. Thanks again for investigating.
from react-css-spinners.
I did a bit more experimenting based on your last post. Here's what I saw:
- Add static
defaultProps
to each of the 3 components - Bundle the library
- Go into the app and rebundle, only importing
Ellipsis
- 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.
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.
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 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.
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:
- Classes with 2 or more static properties cannot be tree shaken: feels like a Webpack issue but maybe it's a
terser
issue sinceterser
is used for minification - When attempting to annotate classes as
/*#__PURE__*/
, they are being stripped out: feels like either arollup
,rollup-plugin-terser
, orterser
issue - Components with
propTypes
set as statics can be consistently tree shaken by usingbabel-plugin-transform-react-remove-prop-types
- 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.
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.
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.
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.
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.
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.
@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.
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from react-css-spinners.