Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sharing components between apps, "create-react-component" #1492

Closed
ericvicenti opened this issue Feb 6, 2017 · 83 comments
Closed

Sharing components between apps, "create-react-component" #1492

ericvicenti opened this issue Feb 6, 2017 · 83 comments

Comments

@ericvicenti
Copy link

ericvicenti commented Feb 6, 2017

Note from Maintainers: see #1492 (comment) for the latest thinking on this.

The Problem

Say I have two sibling CRA apps, Foo and Bar. What is the best practice for sharing a common component, Baz between the two apps?

Scope

It would make sense to have a sibling folder Baz with it's own package.json, and to use npm link to access it from Foo and Bar.

But it seems that npm link has bigger issues, so maybe we should explore alternatives. One example is wml, which simply watches and copies files.

Building

Even when npm link works fine, the JSX in Baz has not been compiled.

Per this discussion, I think we should compile separate modules like Baz independently, but that will require boilerplate such as babel config. CRA helps avoid this for full apps, but we don't yet have create-react-component to help manage the boilerplate of shared components.

Optimizing for Developer Experience

There are a few ways to clean this up, but I'm curious what people think is the current best practice. What is the ideal developer experience?

@ericvicenti ericvicenti changed the title Best practices for sharing components across apps? Sharing components between apps, "create-react-component" Feb 6, 2017
@bvaughn
Copy link
Contributor

bvaughn commented Feb 7, 2017

Prior art worth considering: insin/nwb, specifically Developing React Components and Libraries with nwb

@ericvicenti
Copy link
Author

Ah, I hadn't seen nwb. It looks like a smooth experience!

Does it make more sense to add integrations and compatibility with nwb new react-component? Or should we make a create-react-component that stays in sync with CRA? It could live in the same repo and share configs like babel.

There are a few things that I would have done differently for CRC, in comparison to nwb new react-component:

  • Use jest instead of mocha, for consistency with CRA
  • No questions at init time (convention over configuration)

@gaearon
Copy link
Contributor

gaearon commented Feb 7, 2017

A few past issues:

#423
#796
#907
#1242

@izakfilmalter
Copy link

I can't stress how much I need this feature. My core problem is I have 3 apps: marketing-site, 'customer-app', and 'employee-app'. I am keeping them separate to make them as performant as possible, but there are many shared components, for instance the logo.

My current solution has been having a fourth app that gets imported into each, and then using yarn link, but as mentioned above this is painful, and has issues.

With each layer of separation, my development experience goes down. Having this built into create-react-app would significantly simplify my life.

@Timer
Copy link
Contributor

Timer commented Feb 7, 2017

What artifacts would such a feature produce? CommonJS, Harmony Modules, UMD, AMD?

@natew
Copy link

natew commented Feb 7, 2017

Something I've been wanting/thinking about for a while now. There are actually a few levels you can look at this:

  1. I want a CLI that links together all my local node projects I'm working on (like system-wide lerna)
  2. I want something that shares my common scripts/settings between my projects (like CRA)
  3. I want something that manages multiple projects (shows their status, pulls them all down initially)

There are some pretty complex considerations I think in here. We have a big stack right now with about 25 packages inside it using lerna. But I'd love to release the 8 or so that are more ready for public consumption.

Problem is, I don't want to sacrifice all the niceness of using Lerna now: shared configs, always in sync, can edit them locally easily, automatically linked together.

So, I've just started on repoman which honestly I'm not sure how will solve all this stuff.

My initial idea is to let your packages define a repoScripts key that points to a github repo, which then has some shared dotfiles/scripts that can be run. Will check into nwb more. If it only handled auto-linking things together + easy standardized/ejectable configurations would be nice.

@tbillington
Copy link

@izakfilmalter It's not ideal but here's my solution #1084 (comment)

@ericvicenti
Copy link
Author

Ok, @gaearon, based on your previous comments, it seems that this issue is pretty ripe for fixing.

RFC of a cohesive solution

Please poke this proposal full of holes 😄

Part I: The new meta Creator

We may want to give people more tools like CRA in the future, but it isn't desirable to force people to install a new global dependency for each tool.

Instead, I'd like to propose one tool to rule them all: create-react:

npm install -g create-react

# This is equivalent to `create-react-app MyApp`
create-react app MyApp

# Now we can support:
create-react component Foo

Behind the scenes, this tool will look up the latest npm package to do the heavy lifting. For example, create-react app actually is implemented by the react-creator-app package. The react-creator-component package would define the behavior of create-react component.

This opens up more innovation in the space, because a random company could publish react-creator-mycompany-project, and everybody can easily try out their environment by running create-react mycompany-project.

Also, this could replace react-native init, which could become create-react mobile, create-react native or something.

Part II: The Component Dev Setup

One of the new creators would be the 'component' creator, which creates a new React component package.

Like a CRA app, it includes scripts as devDependencies, and has local commands like npm run build, which builds using the standard Babel configuration. It would output whatever module standard is expected by standard CRA apps.

One major difference is that npm start will build the component and watch for changes, rather than launching a server.

At this point, developers could use npm start alongside linklocal to hack on their components from a neighboring react app.

One huge argument for doing this is to improve tooling for React Native components. Currently, most RN libraries publish uncompiled code, which has become a nightmare when attempting to share libraries or components in other environments that expect node_modules to be compiled. By introducing a common set of library scripts, we can build a comfortable path forward to compiled modules.

Part III: Improve experience within an app

The only bad part about the above experience is the manual usage of linklocal and the npm start for each component you may want to hack on.

In the future, the react-scripts of CRA could be upgraded to automatically run linklocal, so that changes made in neighboring modules are immediately reflected in the development app.

Then, react-scripts can detect when an app's linked component is supported by the same scripts, and can automatically run the watch-and-build behavior for each of the compatible components.

So, if I had Foo and Bar as independent apps that each depended on a sibling Baz component, I could run either app and still see the updates as I make changes to Baz.

Thoughts?

Let me ping a few more smart people who have a lot of context on this: @cpojer @thejameskyle @skevy @taion @ljharb

I'm excited to work on this because I've felt this pain a lot- especially when trying to share code between React Native and web/CRA.

Please let me know if you think this makes sense! I'm happy to put together a prototype, if people like this approach.

@ljharb
Copy link

ljharb commented Feb 7, 2017

I'm not sure I understand - isn't the solution to sharing Baz between two apps, "publish it to npm"? npm link is fine for developing, but something that works across two apps deserves to be a separate package - if not published publicly, then to an internal npm registry (sinopia is free, for example, and using an npm registry gives you semver benefits that git installs don't).

Can you help me understand what the problem is here that needs solving?

@ericvicenti
Copy link
Author

ericvicenti commented Feb 7, 2017

@ljharb, I don't think most people who use CRA know about sinopia. If that is the recommended solution, then we can at least do a better job on education. Lots of people seem to like the lerna approach, and/or linklocal.

Aside from the reported issues about npm link, it can be a pain to start up a dev server for each package that you want to edit. If you're codebase is nicely decoupled, you usually have a ton of separate modules. Ideally we could get CRA apps to automatically watch and re-build these other modules when they change.

And yes, publishing is another aim. If we have a CRA-like thing for react native components, we can fix that problem you always rightfully complain about: the RN ecosystem is publishing tons of uncompiled modules that is wreaking havoc on people who try to share code between universes. If we encourage them to use our devDependencies and scripts, their published library/component/nativeModule can easily evolve with best practices.

For the rest of the React ecosystem, it may be handy to publish a component without any build configuration.

@admmasters
Copy link

So I would (personally) summarise the problem domain as the same one as create-react-app, just with respect to painlessly creating a component without any of the build concerns etc as @ericvicenti has stated. Having previously used sinopia (not picking on it) - but its now not even in maintenance mode (last commit was over a year ago) rlidwka/sinopia#376 and it is a solution that adds another cog in the toolchain rather than making it easier to develop imho.
@ericvicenti that approach outlined in the RFC, looks good to me, by the way.

@mkonicek
Copy link

mkonicek commented Feb 7, 2017

Why not just:

Projects
  Components
    SharedComponent.js
  App1
    app1.js
    package.json (3rd-party app1's dependencies)
  App2
    app.js
    package.json (3rd-party app2's dependencies)

Run the packager from the root (Projects), when bundling it will only include files that are used. E.g. if SharedComponent.js is only imported / required by app2.js, it will only be included in app2's bundle.

If SharedComponent uses a 3rd-party dependency, all apps using ShareComponent should include that dep in their package.json.

@ericvicenti
Copy link
Author

@mkonicek, there is no npm start that is available outside of App1 and App2, so there is no way to "run the packager from the root". How can developers set that up?

Also, what if App1 is React Native and uses RN packager, while App2 is made with CRA and uses webpack?

@admmasters, yes exactly. My aim is to deliver a good development experience for standalone components, just like CRA delivers a great dev experience for apps.

@gaearon
Copy link
Contributor

gaearon commented Feb 7, 2017

Also semi-related: #741 #1065

@ericvicenti
Copy link
Author

@gaearon, does the src/node_modules proposal aim to fix the same problem? I'm curious what the DX would look like in that world, as a developer shares a component between two apps.

@bebbi
Copy link
Contributor

bebbi commented Feb 7, 2017

I like the solution discussed in #741:
A packages/ (node_modules?) folder inside a repo created by create-react-app.
I think it's a great approach to co-locate components with the app mostly driving them. In early dev stage, this allows juggling code between app and component as things evolve in a seamless manner - without nasty versioning/publish/download overhead.
When ready, packages can be published by create-react-app.
If there's only components and no app, just have a wrapper app in /src and the components in packages.
No need for a separate create-react-component app in this case.

Remains just the problem of using it in another app. Some node/npm related solutions mentioned above (publish, sinopia, link local, NODE_PATH) could be just fine.

@gaearon
Copy link
Contributor

gaearon commented Feb 7, 2017

@ericvicenti It's related but it's solving a slightly different problem: avoiding relative imports inside the project rather than sharing components between projects. Since people don't use Haste in open source, they have this issue of ../../../../components/abc which is often obnoxious. Whatever solution we settle on, I think we should consider both problems since they're similar.

@ericvicenti
Copy link
Author

@bebbi, I'm still confused. What would my directory structure look like? I need two independent apps "Foo" and "Bar", which each share a component "Baz". Baz may be an independent repo, but I would like to easily edit it while developing my Foo and Bar apps.

@gaearon, Gotcha, I agree that the problems are related and should be thought about together. I'm spoiled by using Haste at FB. Its not perfect but it means we never worry about this sort of thing.

@bebbi
Copy link
Contributor

bebbi commented Feb 7, 2017

It's a partial solution for your scenario. But with the assumption that often one project is driving any given components dev, it might still be a nice one.
If Foo's the driver and we call the components folder packages for now:

Projects
  cra_Foo
    src
    packages
      Baz
  cra_Bar
    src

and Bar accesses Baz through some less direct way, e.g. via NODE_PATH or publish etc.
CRA could publish Foo as an app including Baz, but there would be a script building Baz and anything else in packages as components.

@bmurzin
Copy link

bmurzin commented Feb 7, 2017

I use git submodules to share components placed in src/Baz of every app. In this way you can edit Baz in Foo, push it and then just git submodule update in Bar.

@ericvicenti
Copy link
Author

Ok, it seems that there is enough support to give this a try, so I'm going to implement parts one and two of my proposal as a PR to this repo.

That way we will have create-react component Baz, which will create a nice experience around developing a component.

@robcaldecott
Copy link

I heartily recommend including react-storybook in any component tool: you could have npm start launch the stories. We created our own create-react-component tool in-house that is basically CRA with a custom template and Babel config that we use to transpile and publish shared components to internal npm (originally Sinopia, now Artifactory) but it's limited. But because it's based on CRA we get all the other useful stuff like Jest, webpack, etc.

@mkonicek
Copy link

I'm a bit late to the party but I think WebPack (and now the React Native packager too) support symlinks?

So with this setup:

/sharedJSComponents
/myWebApp
/myReactNativeApp

Can I just 'npm link' the sharedComponents into both myWebApp/node_modules and myReactNativeApp/node_modules? Then we don't need anything else, right?

@ericvicenti
Copy link
Author

@mkonicek, yes but how will you configure the build for those shared components?

Also, npm link is non-ideal because it clobbers the global installation, in comparison to linklocal which lets you use relative dependencies and link them in for development.

@stereobooster
Copy link
Contributor

stereobooster commented May 28, 2017

my case:

I have project with component react-lazy-load and examples/basic folder inside, which is CRA project. I want to use

"react-lazy-load": "../../../react-lazy-load"

in examples/basic/package.json. But this doesn't work

UPD: so CRA solution is to use this

cd ../..
yarn link
cd examples/basic
yarn link react-lazy-load

which is not that nice solution in that case

UPD2:

endep up with using symlink ln -s ../../../src/ src/react-lazy-load

@BrodaNoel
Copy link
Contributor

+1. Kind of blocked by this issue.
Is there a way to help to push this feature?

@5angel
Copy link

5angel commented Jun 15, 2017

Man I hoped there would be a solution by the time I scrolled to the bottom. Boy, did my dreams shatter.

To be clear, I just want to publish packages from create-react-app without the need of reusing webpack configs and other scripts etc.

Well, hope @ericvicenti's PR will help, in the meantime I'll have to copy all my configs =(

@cyberfifi
Copy link

cyberfifi commented Jun 15, 2017

Guys, solution is here.

Assume you have 3 React apps:

  1. A reusable component as a standalone React app. Let's call it GoodComponent. We keep it as a standalone app since we want to use it in multiple apps. Its name in package.json is good-component. Assume the main file of this GoodComponent is src/index.js.
  2. An actual React application called FooApp.
  3. Another React application called BarApp.

cd to root directory of GoodComponent
npm link
cd to root directory of FooApp
npm link good-component
cd to root directory of BarApp
npm link good-component
In the code of FooApp or BarApp, if you want to use GoodComponnet, you can just import it as below:
import GoodComponent from 'good-component/src'

Here we go! You can now have your own temporary npm module. You can reuse it just as an npm module without publishing it.

@BrodaNoel
Copy link
Contributor

Using npm link should works.
Check this article: https://medium.com/@BrodaNoel/how-to-create-a-react-component-and-publish-it-in-npm-668ad7d363ce

Forget the part about "and share it in npm", just check how the example works (it's sharing the component using npm link and npm link component-name.

@5angel
Copy link

5angel commented Jun 15, 2017

@shiyang-fei it's not a solution if you want internal components published to internal repository

@cyberfifi
Copy link

cyberfifi commented Jun 15, 2017

@5angel I consider the npm link creates an internal repository on local as a dev solution. If you are really looking for a long term internal respository solution, you can define a git repo url to a dependency like this.
Hope this can help you.

@5angel
Copy link

5angel commented Jun 15, 2017

@shiyang-fei not really. When I say internal repo, I mean we have a private npm registry from which we distribute packages to different teams in the company. And doing the whole webpack routing for every little shared component is just tedious.

I only need the publish feature, that's all.

@rwoody
Copy link

rwoody commented Jun 21, 2017

Leaving this here since I didn't see it mentioned - Lerna wasn't working correctly to link shared libraries/dependencies with React Native in my monorepo; however, I found whackage and got it working, although it was just a proof of concept.

@gaearon
Copy link
Contributor

gaearon commented Jun 22, 2017

Is there a way to help to push this feature?

Yes, by implementing a proof of concept addressing all the different issues in this thread. It is very unlikely we'll get time to work on this, so it's really up to the community.

@natew
Copy link

natew commented Jun 22, 2017

We made some progress on repoman a while ago, but haven't touched it in a while. The structure of the project itself though is laid out well for someone wanting to fork.

It's basically a lot of Lerna but instead of per-project, it's per-computer (it controls a folder for you, default is ~/projects).

So repoman get motion/pundle would clone into ~/projects/motion/pundle.

Then you could have a monorepo and run repoman eject ./packages/some-package and it would move it out and into a parent folder, and symlink it in.

It also has a subset Lerna functionality baked in, so you can have it cross-link and install and such. It needs some love in terms of someone taking care of it, testing it it and probably patching a couple things and focusing it's scope.

@eseQ
Copy link

eseQ commented Jul 11, 2017

What about webpack alias?

alias: {
  'my-local-package': path.resolve( __dirname, '..', '..', 'to', 'local', 'modules', 'MyPackage', 'src' )
}

That work for me. Just and to postcss (if u use it)

{
  loader: 'postcss-loader',
  options: {
    config: {
      path: 'path/to/postcss.config.js'
    }
  }
}

Also i add package.json and babel config for each local module.

@themre
Copy link
Contributor

themre commented Sep 15, 2017

My colleague tried yarn workspaces to share code between projects and he used the following steps:

  • create a yarn workspace (packages dir, package.json with workspace key)
  • make a simple project in packages that just exports a number or something
  • import simple project in CRA -> produces crash (failed to compile module index)
    He tried also moving CRA outside the workspace, delete node_modules and move it back to packages, but dev-server compains about building index.

He did however tried thess starters (https://github.com/emyann/typescript-webpack-starter, https://github.com/vikpe/react-webpack-typescript-starter) and they work. Does anyone know what could be the cause of this?

@transitive-bullshit
Copy link
Contributor

For anyone who ends up here an looking for a good React library starting point that uses create-react-app for an example project, check out react-modern-library-boilerplate.

@bradfordlemley
Copy link
Contributor

bradfordlemley commented Nov 10, 2017

I just submitted a proposal (#3436) that I think could support the original use case called out in this issue and some others that have come up in this thread.

The proposal is to allow apps to specify additional source roots in package.json.

The app can then import the "shared" modules as absolute imports and they are treated the same as if they were under /src (transpiled, etc.).

monorepo
  |--app1
    |--package.json: srcPaths: ["../shared"]
    |--src
      |--file.js: import comp1 from 'comp1' <-- ok, shared/comp1
      |--file.js: import comp1 from '../../shared/comp1'  <-- fail, not permitted
  |--app2
    |--package.json: srcPaths: ["./components", "../shared"]
    |--src
      |--file.js: import comp2 from 'comp2' <-- convenience absolute import, components/comp2
      |--file.js: import comp1 from 'comp1'  <-- from shared/comp1
    |--components
      |--comp2
  |--shared
    |--comp1

The proposal is implemented, a working monorepo and more info is here: https://github.com/bradfordlemley/cra-share

Update: @arnihermann -- Just realized this basically implements what you had proposed earlier in this thread. Wondering if you have already done it this way and if you've run into any issues doing it this way? I agree with your comments about publishing/linking/building/complication being an issue with some other solutions.

Update2: added convenience absolute import example (in app2 above)

@mrasoahaingo
Copy link

Hi, any updates?

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

I'll close since so far nobody took ownership of this.

Hi, any updates?

No, if there were any updates, they would be on this issue.

@gaearon gaearon closed this as completed Jan 8, 2018
@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

(that said, I think #1333 is still on the roadmap and is sort of related)

@gaearon
Copy link
Contributor

gaearon commented Jan 17, 2018

I think the most recent thinking on this is:

  • For the main use case in this thread (sharing components between apps), Support Lerna and/or Yarn Workspaces #1333 is the proposed solution. We even have Add support for yarn and lerna monorepos. #3741 as a PR and although we probably won’t go with that specific approach, we’ll probably merge something in that spirit.

  • If you just need to share a React component you created don’t forget you can just publish it to npm (including a custom registry). I think this thread may give people searching for a solution a wrong impression it’s impossible. People publish React components on npm all the time, and you can use tools like nwb or Neutrino React Component preset to automate the build setup.

Hope this helps!

@sorahn
Copy link

sorahn commented Feb 15, 2018

I just wanted to chime in here, it was pretty easy for me to set up a tiny webpack build, and just leverage the react-scripts setups from inside a fresh create-react app.

  • run create-react-app.
  • add babel-preset-react-app to the top level node modules.
  • use the react-scripts version of webpack:
    "build:lib": "NODE_ENV=production ./node_modules/react-scripts/node_modules/.bin/webpack",

Here's the webpack.config.js file.

module.exports = {
  entry: { main: "./src/lib" },
  output: {
    path: __dirname + "/dist",
    filename: "[name].js",
    libraryTarget: "umd",
    library: "<lib name>",
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: { loader: "babel-loader" },
      },
    ],
  },
  externals: { react: "react" },
};

I split my src folder in to demo and lib. Then use the "app" part of create-react-app to run the demo. then src/index.js just exports the default from the demo directory for the main app.

https://github.com/sorahn/react-fuse

edit: added link to github repo

epatters added a commit to IBM/datascienceontology-frontend that referenced this issue Oct 15, 2018
Also, begin switch from npm/yarn link to yalc for local dependencies.
Npm link is broken in so many ways. It's really embarassing that sharing
React components between multiple apps is still so difficult.

npm/npm#17287
microsoft/TypeScript#6496
facebook/create-react-app#1492
@lock lock bot locked and limited conversation to collaborators Jan 20, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests