Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serve a React component library dependent on Styled Components to another library that also has a Styled Comopnents dependency?

I've recently converted an internal component library I'm working on to Styled Components. That component library has Styled Components listed as a peerDep and a devDep in the package.json. From there I'm importing Styled Comopnents into every component that needs styling. All works great up to that point.

I have a Create React App (CRA) application that also imports Styled Comopnents as a regular dependency. I need this to be able to build one-off components for this specific project. BUT I also need the ability to import my component library to build out core components for this new CRA-based app.

Now this is my problem: I'm testing my recently converted Styled Components-based component library in this CRA app by linking directly to the component library in package.json (file:../component-lib). I've installed all my deps, imported components from the component library, built a new project specific Styled Component within the CRA project, and ran it locally only to see this same error: "It looks like there are several instances of 'styled-components' initialized in this application. This may cause dynamic styles not rendering properly, errors happening during rehydration process and makes your application bigger without a good reason." I've read that section of docs and learned not to use npm link and to serve SC as a peerDep and a devDep in my component library.

I believe this issue is not allowing me to access theme props I'm passing into a custom ThemeProvider from the component library since I'm running multiple instances in the CRA project?

This problem is definitely due to my lack of knowledge in dependency management. I'm just wondering if anyone else has encountered a similar issue or what I should be doing to avoid duplicate instances of Styled Comopnents?

Component library index

export { default as Button } from "./components/Button";

export {
  default as CustomThemeProvider
} from "./components/utils/CustomThemeProvider";

Component library package.json

"scripts": {
    "build": "nwb build-react-component --copy-files",
    "clean": "nwb clean-module && npm clean-demo",
    "start": "nwb serve-react-demo",
    "lint": "eslint src/**",
    "test": "nwb test-react",
    "styleguide": "styleguidist server",
    "styleguide:build": "styleguidist build",
    "test:coverage": "nwb test-react --coverage",
    "test:watch": "nwb test-react --server",
    "publish": "npm run build && npm publish"
},
"dependencies": {
    "@rebass/grid": "^6.0.0-4",
    "prop-types": "^15.6.0",
    "react-portal": "^4.1.2"
},
"peerDependencies": {
    "react": "16.x",
    "styled-components": "^4.0.3"
},
"devDependencies": {
    "babel-eslint": "^8.2.2",
    "eslint": "^4.18.2",
    "eslint-plugin-react": "^7.7.0"
    "prettier": "1.14.3",
    "nwb": "0.22.x",
    "react": "^16.4.0",
    "react-dom": "^16.4.0",
    "react-styleguidist": "^7.2.0",
    "styled-components": "^4.0.3"
},

Component library NWB config

module.exports = {
  type: "react-component",
  npm: {
    esModules: false,
    umd: false,
  },
  babel: {
    stage: 1
  }
};

CRA Project package.json

"dependencies": {
  "component-library": "0.16.6",
  "react": "^16.6.0",
  "react-dom": "^16.6.0",
  "react-scripts": "2.1.0",
  "styled-components": "^4.0.3"
},
like image 957
Corey Bruyere Avatar asked Nov 01 '18 20:11

Corey Bruyere


People also ask

Is styled-components a dev dependency?

We need to install some dependencies because styled-components doesn't come with create-react-app. This installs the styled-components types for TypeScript as a dev dependency.

Can you use styled-components with SCSS?

In SCSS, you can use variables and formulas for expressing colors, font sizes, widths and spacing variants. However, when you switch from SCSS to styled components, the syntax isn't valid anymore. Styled component files are CSS-in-JS, so they are actually JavaScript files.

How can you apply dynamic styles to styled-components?

One way to dynamically change css properties with styled components is to insert a custom prop into your React component and access said property using the dollar sign and curly braces commonly used for template literals. Our current example is testing to see if our use pointer prop is true.


2 Answers

This is only a partial answer that I'm hoping will enable you or someone else to figure out the rest. I'm far from an expert on managing these kind of dependencies and am helping with this to further my own knowledge since I may want to do a similar setup soon.

Though it seems this should be doable with nwb, I switched to using webpack directly in order to have more control. Even using webpack directly I have only made it part of the way there. Everything works correctly when I do a build, but in dev mode (npm start) for the CRA app, the styled-components package is still getting pulled in twice and the styling doesn't work correctly. This seems like a potential webpack issue since the dev and production modes behave so differently, but it could be something with the CRA webpack dev configuration or (more likely) some aspect of this that I don't yet understand.

This is a good example to reference for the component library configuration: https://github.com/kalcifer/webpack-library-example

Here's my package.json for my test component lib (component-lib3 just because of trying several other approaches):

{
  "name": "component-lib3",
  "version": "1.0.7",
  "description": "component-lib3 React component",
  "main": "dist/component-lib3.js",
  "peerDependencies": {
    "react": "^16.6.0",
    "react-dom": "^16.6.0",
    "styled-components": "^4.0.3"
  },
  "devDependencies": {
    "react": "^16.6.0",
    "react-dom": "^16.6.0",
    "styled-components": "^4.0.3",
    "@babel/core": "^7.1.2",
    "@babel/preset-env": "^7.1.0",
    "@babel/preset-react": "^7.0.0",
    "babel-loader": "^8.0.4",
    "webpack": "^4.24.0",
    "webpack-cli": "^3.1.2"
  },
  "scripts": {
    "build": "webpack"
  }
}

Here's the webpack.config.js:

var path = require('path');

module.exports = {
  entry: './src/index.js',
  mode: 'production',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'component-lib3.js',
    libraryTarget: 'umd',
    library: 'component-lib3'
  },
  externals: {
     "styled-components": {
       commonjs: 'styled-components',
       commonjs2: 'styled-components',
       amd: 'styled-components'
     },
     "react": {
       commonjs: 'react',
       commonjs2: 'react',
       amd: 'react'
     },
     "react-dom": {
       commonjs: 'react-dom',
       commonjs2: 'react-dom',
       amd: 'react-dom'
     }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader"
        }
      }
    ]
  }
};

And then also .babelrc:

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

I tried a number of variations, and I don't understand why this won't work right for dev mode.

like image 54
Ryan Cogswell Avatar answered Oct 11 '22 13:10

Ryan Cogswell


I am facing the same issue at this moment and I'm still not sure. I could fix it properly or not, so please don't take this answer as a guaranteed solution. There are two things you can try.

The first one is something that appears in the styled-components FAQs section. They suggest configuring Webpack (I assume you are using Webpack too) with an alias so that it always looks for the styled-components dependency wherever you specify. This should avoid having several instances of styled-components, but so far I couldn't make this work for me.

The second option is to use an alternative no npm link. Apparently when you have a symlinked dependency (this is what npm linkdoes actually). The bundler's resolver tries to look for the dependency in the same project. This is also mentioned in the linked styled components FAQs section. What you should do if you follow this approach is to avoid creating a symlink that points to your component library from the CRA app and copy the folder instead.

There is a tool called wml that can copy the contents of your component library into the node_modules folder of your CRA app. wml is also able to watch for changes in your component library folder and it re-copies the changes to your CRA app's node_modules. So, we could say wml is kind of alternative to npm link. In my initial attempts it worked fine for me so maybe this is something you can try.

Be careful: There is an issue in wml that causes the files on your project to be removed on the first time you set it up. It didn't happen to me but I saw others suffering the problem so if you are going to try wml, please ensure you have all the changes pushed to your source code version control system.

These are the steps you would need to follow to setup wml for your projects using the command line:

wml add <component-library-folder> <CRA-app-folder>/node_modules/<name-of-component-library>
wml start

Ideally I would prefer to solve this without having to use any external tool but if what styled-components maintainers suggest on their site is not valid, it could serve as a workaround.

like image 32
Markel Arizaga Avatar answered Oct 11 '22 15:10

Markel Arizaga