Use single StylesContext when importing component from custom MUI-based library - reactjs

I have an application (let's call it frontend) importing a custom library of React components (design-system) that's Material UI based.
In the frontend app, I have a ShadowDOM-contained component (using react-shadow) that I want to import a Button component from design-system to. I wrapped the whole tree inside ShadowDOM with StylesProvider that I passed a custom instance of jss to (with different injectionPoint, so the styles from inside that tree are attached not at the head of the document, but inside the ShadowDOM.
The issue I have is, that frontend uses its own copy of StylesProvider from #material-ui/styles package (I have no need to install the whole #material-ui/core as all components should be imported from design-system instead) and design-system uses its own copy of StylesProvider from #material-ui/core, resulting in a situation, where the Button is being wrapped with another StylesProvider by MUI because it doesn't detect any styles context available (and it doesn't, because MUI creates the context in global scope of StylesProvider.js file - so there are two different contexts that do the same), resulting in Button having access to default JSS instance where the insertionPoint is pointing to document head.
What I'd like to achieve is, have MUI not create another styles context for every single component imported from design-system but use the one I defined in frontend - without passing it as a prop manually to every component imported. I considered adding #material-ui/styles on top of #material-ui/core to design-system and adding it to webpack's "externals" (so, theoretically, only one StylesProvider.js would be used and in turn - one context), but I was hoping there's a better solution.

TLDR:
Just adding #material-ui/styles to externals in design-system's webpack config was enough (even if you don't add it explicitly as dependency, #material-ui/core depends on it and will use it anyway, so it works).
Long answer:
My setup is such that the "main" entrypoint in package.json of design-system points to dist/index.js (so, already bundled/minified version of the library). Without adding #material-ui/styles to "externals" in webpack config, it was bundled along the whole library, so at this point, whatever I did there would always be two copies of StylesProvider.js:
One solution is to mark #material-ui/styles as external - this way, it won't be bundled in dist/index.js and instead, expected to be available during runtime (and it is):
Besides the aforementioned solution, there's also another one that worked - pointing "main" entry in package.json to "src/index.ts" instead. It made the frontend webpack treat it as a part of frontend code, instead of separate library:
As a bonus, it ended up with the final bundle being smaller (as common dependencies weren't duplicated). One minus of this approach was, however, that each frontend build had to also rebuild design-system, which increased the total build time.

Related

How can I compile my React component's styles (JSX and CSS Modules) to be used statically — as a simple imported .css file?

Background
I'm creating a public Node package which consists of some React UI. I'm currently using CSS Modules to scope the styles to the component, and it's all being successfully bundled with Webpack. The bundle outputs a main.css file.
The ask
Since I intend to use this packaged component across many projects with different frameworks, I cannot guarantee that CSS Modules will be available. Thus, I would like to "flatten" the compiled JSX, such that the generated CSS Module classNames are always added at build time, rather than being conditionally added based on whether or not the CSS modules are being imported. From there I should be able to just import the compiled CSS file and call it a day.
What I've found
This tool seems to solve my problem, specifically using CSS Modules. This is not actively maintained though, and I wonder if there's a better solution out there.
https://cef62.github.io/css-modules-compiler/
https://cef62.github.io/css-modules-compiler/quick-start.html
I do wonder if this is doable with some sort of PostCSS routine or a preexisting PostCSS plugin.

TailwindCSS Duplicate CSS classes when using library in app

I have a component library using storybook & TailwindCSS and a host app that's also using TaildwindCSS itself that imports the component library. When the classes are generated, I'm seeing that they're duplicated:
Both projects import TailwindCSS standardly in their index.css files which is then imported into index.tsx using import "./index.css";:
The host app does generate all the classes from the component library when imported but due to there being duplicate classes, some are being overridden due to the order (pay attention to the source and line numbers in the above image)
The component looks correct on storybook:
Host app:
Looking for advice on how to correctly import the component library within the host app?
UPDATE:
I've figured that the component library generates it's own TailwindCSS classes as expected and that's where the "duplicate" classes (inline) come from and it's being included in a single output in index.js in the dist folder. Still need a way to avoid these duplicates when imported in the host app. May need to look at changing the component library to build a separate .css file with the styles and tell the host app to generate the component library's styles to prevent these duplicates.
After reading more on the TailwindCSS documentation, I've found a resolution. Using the following information from https://tailwindcss.com/docs/content-configuration#working-with-third-party-libraries, I was able to fix my issues.
Essentially what I've now done is, on my component library, I ensured that the.css styles are extracted into it's own file and not built into a single index.js. After that, on the host app, I set the content of tailwind config to reference my component library so that it scans the src and generates those classes itself.

What is the correct way to include React in both an application and a private library? (React Invalid Hook Call warning from duplicate React)

I have a sort of a "monorepo", one big project consisting of a few smaller projects that use React.
I'm trying to break these up into three separate repositories, lets call them Core, Application1, and Application2
The Core is a dependency of both applications, and the Core depends on React, because it defines some React component classes. The applications both also use React.
When I tried to build this all together (using Parcel bundler), I am getting a final bundle which at runtime gives the Invalid Hook Call warning in one (but not both) of the applications.
On that page (or in the error message), it says that the error could be caused by one o these:
You might have mismatching versions of React and React DOM.
You might be breaking the Rules of Hooks.
You might have more than one copy of React in the same app.
I have checked that #1 is not true, and I'm not even using hooks in any way that I am aware of, so the problem is seems to be multiple versions of React.
I gathered from reading about this that it was a mistake for my Core library to declare React as a dependency, and that it should instead declare it in peerDependencies. That made the Application stop giving the error, but it also made my Core library start having a bunch of Typescript errors and failing to be able to run the unit tests (which rely on React, using Jest/Enzyme to render and validate DOM).
Since specifying React in peerDependencies caused it not to be installed in the node_modules of Core, I decided that I should probably include React in both the peerDependencies and the devDependencies of Core. That fixes the Core library again but breaks the Application.
I'm not really sure of the following:
Why one of my applications fail due to duplicate React copies and the other doesn't, since they seem pretty symmetrical to each other.
Why, even though I only specify React in peerDependencies and devDepenencies in Core I still would get a duplicate copy of React in the dependent application
Whether the method used to bring Core in to the application has any bearing on this. (one method I'm trying is package.json I specify core as a "file:../" style of URL. Another alternative is to use "yarn link", or possibly to do both of these, and I'm not sure whether this has any effect on what ends up in node_modules underneath the application folder or on what gets bundled)
What is the right way to include React in both an Application and a library, in such a way that both of those projects have React available but there does not end up being duplicates in the Application causing this hook error (or, just taking up extra space).
Answering my own question.
I found the following issue helpful: https://github.com/facebook/react/issues/14257
Various different suggestions were made in the comments of ways to solve this problem, either by npm link or yarn linking the react library from the library to the application, or vice versa. Those all seemed promising, since the idea is to make sure that all of the different references to React are actually pointing to the same place. Unfortunately none of those worked for me. (e.g. the answers by JerryGreen and Kas in that issue)
Another user, dcecile, suggested using webpack's alias feature, but I'm not using webpack.
resolve: {
alias: { react: require.resolve("react") }
},
Parcel has a similar alias feature but can't be used in quite the same way because it's used in the package.json file so things like require.resolve can't be called like they are in webpack's js config file.
I ended up finding a way to use Parcel's alias feature to do what I wanted, based on another example from https://github.com/jaredpalmer/tsdx/issues/64 from user jaredpalmer. In my situation, I'm adding this to the application's package.json, and it appears to get rid of the duplication problem and the "Invalid Hook Call" error:
"alias": {
"react": "../my-core-library/node_modules/react",
},

React Styled Components stripped out from production build

I use Styled Components as CSS alternative for my React App. In development everything works fine (first screenshot), but when I run a production build (npm build), styles within style tags are stripped out (second screenshot). As a result, there're no styles in the production build.
Here is the production version: http://projects.loratadin.com.s3-website-us-east-1.amazonaws.com/weather-app/
Here is the source code: https://github.com/Loratadin/weather-app
I had a similar issue with empty style tags in production. I'm using a headless browser for server-side rendering and this issue caused the server-side rendered pages to appear with no styles before JS assets are loaded.
After a lot of searching around, I finally found out the reason. The Styled Components library uses something called the "Speedy mode" to inject styles on production. This makes the styles bypass the DOM` and be injected directly inside the CSSOM, thus, appearing in the inspector, but totally invisible on the DOM.
Fortunately, Styled Components 4.1.0 came with a fix for this issue! Now you can set a global variable called SC_DISABLE_SPEEDY to true in order to disable the Speedy mode and get the styles to appear on Production as well. Keep in mind that you should do it at the very beginning of your application's entry file, before importing any styled component, otherwise it will not work.
The way I did it is by creating a new file called globals.js that contains global.SC_DISABLE_SPEEDY = true
and importing it as the very first thing in my index.js.
Reference: https://www.styled-components.com/releases#v4.1.0
For the Create React App folks out there you can add a .env file in your root and add:
REACT_APP_SC_DISABLE_SPEEDY=true
I was able to replicate your issue and it looks like when the application is in production, it can't select html elements within a styled component (the styles don't apply to the element). Instead, you'll need to create additional stylized components for input and button.
Working example: https://github.com/mattcarlotta/Weather-App
I refactored your application to simplify its structure. Please read the README for instructions on how to run it in development and in production (DO NOT use the above repository for production, as it's highly unnecessary to have an express backend -- I only did this so that you can run a local production build).
What I changed:
Moved any styled components to the components folder for modularity
Any sort of global stylization was put into a styles folder
Moved assets to images and imported them into the styled component that needed them (eliminating the need to use require('../path/to/image'))
Simplified the App.js file. Children are controlled by state and class methods. Most importantly, turned your form into a controlled component, fixed the getWeather method to: Not allow an empty submission, if the AJAX calls fails, it'll catch the error (instead of breaking your app), and reset the form inputs on successful submission.
Added prop-types to ensure props were consistent in declaration (string remains a string, number remains a number, and so on).

CSS Reset with PostCSS autoreset

I'm trying out postCSS on a project using the create-react-app starter repo with postCSS and importing .css files for each component. That is, each component includes a import 'styles/componentA.css';, or several.
In order to create consistent styles across browsers I'd like to import a CSS reset. I've tried a few things:
Importing a .css file containing a standard CSS Reset based on Eric Meyer's reset.
Using the autoreset plugin for postCSS.
For the first option , in dev mode the CSS imports are added as <style> tags to the document. The CSS Reset appears last in the list of <style> tags, though it's imported in the top-level component. Ideally the CSS Reset would be imported first. Being last means it would overwrite any styles I apply to base elements (like h1 {font-size: 40px;})
For the 2nd option (using autoreset), it doesn't appear there's a way to apply specific rules to specific elements. For example, I want to apply list-style: none only to ul and ol elements.
Is it possible to use the autoreset plugin to do a Eric Meyer-like CSS Reset in postCSS? Or am I going in a completely wrong direction and misunderstanding the purpose of it?
I don't use react but I faced a similar issue when using components and resets alongside PostCSS (I'm building things as described here: http://ecss.io/chapter7.html). I didn't want a reset loaded for each component, just a global one as would be used traditionally.
Therefore, I went with a 'component' purely for 'globalCSS'. This is the first component that gets loaded and loads in the reset (and also global PostCSS variables and mixins) so subsequent components have access to them without redeclaring.
Is that an approach you could use/adapt?

Resources