Skip to content
⚛️ Zero Config Create-React-App Monorepos with Yarn Workspaces, Lerna and React Storybook.
JavaScript CSS HTML
Branch: master
Clone or download
Latest commit 8b6405f Jul 19, 2019

README.md

React Workspaces Playground Screenshots

Features

  • ⚛️ Create React App 3 (React 16.8)
  • 📖 Storybook 5
  • 🐈 Yarn Workspaces
  • 🐉 Lerna 3
  • Host Multiple CRA Apps, Component Libraries & Storybooks in one Monorepo
  • 🔥 Hot Reload all Apps, Components & Storybooks
  • 👨‍🔬 Test all workspaces with Eslint & Jest using one command
  • :octocat: Deploy your apps to Github Pages using one command

Contents

Setup

Pre-Requisites

  • Yarn 1.13.0
  • Node 11.14.0

Installation

git clone git@github.com:react-workspaces/cra-workspaces-playground
cd cra-workspaces-playground
yarn

Usage

Starting The React App

cd packages/apps/app-one
yarn start

Starting The Storybook

cd packages/storybook
yarn storybook

Linting & Testing

cd <workspace-root>
yarn test

Deploying to GitHub Pages

Update the homepage URL in app-one/package.json to reflect your GitHub Pages URL.

{
  "name": "@project/app-one",
  "private": true,
  "homepage": "https://react-workspaces.github.io/react-workspaces-playground",
  "scripts": {
    "deploy": "gh-pages -d build"
  }
}

Run the deploy script.

cd <workspace-root>
yarn deploy

Creating a New CRA App

Use Create React App's --scripts-version to create a new React App with Yarn Workspaces support.

create-react-app --scripts-version @react-workspaces/react-scripts my-app

How Does It Work?

React Workspaces Playground uses a custom version of react-scripts under the hood. The custom react-scripts is an NPM package to use in place of the react-scripts dependency that usually ships with Create React App. See: (@react-workspaces/react-scripts) on NPM.

Support for Yarn Workspaces was added by:

  1. Adding yarn-workspaces.js file to resolve workspaces modules.

  2. Updating the Webpack config:

    • Use main:src in package.json for loading development source code.

    • Use production or development settings based on your yarn workspaces settings in your <workspaces-root>/package.json:

      {
        "workspaces": {
          "packages": [
            "packages/apps/*",
            "packages/components",
            "packages/storybook"
          ],
          "production": true,
          "development": true,
          "package-entry": "main:src"
        }
      }

Minimal updates to the Webpack config were required.

Diff: webpack.config.js

--- a/./facebook/react-scripts/config/webpack.config.js
+++ b/react-workspaces/react-scripts/config/webpack.config.js
@@ -9,7 +9,6 @@
'use strict';

const fs = require('fs');
const isWsl = require('is-wsl');
const path = require('path');
const webpack = require('webpack');
const resolve = require('resolve');
@@ -28,15 +27,14 @@ const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeM
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
const paths = require('./paths');
const modules = require('./modules');
+const workspaces = require('./workspaces');
const getClientEnvironment = require('./env');
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
const typescriptFormatter = require('react-dev-utils/typescriptFormatter');
// @remove-on-eject-begin
const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier');
// @remove-on-eject-end

// Source maps are resource heavy and can cause out of memory issue for large source files.
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false';
@@ -53,12 +51,22 @@ const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;

+const workspacesConfig = workspaces.init(paths);
+
// This is the production and development configuration.
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
module.exports = function(webpackEnv) {
  const isEnvDevelopment = webpackEnv === 'development';
  const isEnvProduction = webpackEnv === 'production';

+  const workspacesMainFields = [workspacesConfig.packageEntry, 'main'];
+  const mainFields =
+    isEnvDevelopment && workspacesConfig.development
+      ? workspacesMainFields
+      : isEnvProduction && workspacesConfig.production
+        ? workspacesMainFields
+        : undefined;
+
  // Webpack uses `publicPath` to determine where the app is being served from.
  // It requires a trailing slash, or the file assets will get an incorrect path.
  // In development, we always serve from the root. This makes config easier.
@@ -279,6 +282,7 @@ module.exports = function(webpackEnv) {
      extensions: paths.moduleFileExtensions
        .map(ext => `.${ext}`)
        .filter(ext => useTypeScript || !ext.includes('ts')),
+      mainFields,
      alias: {
        // Support React Native Web
        // https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
@@ -330,7 +335,11 @@ module.exports = function(webpackEnv) {
              loader: require.resolve('eslint-loader'),
            },
          ],
-          include: paths.appSrc,
+          include: isEnvDevelopment && workspacesConfig.development
+          ? [paths.appSrc, workspacesConfig.paths]
+          : isEnvProduction && workspacesConfig.production
+            ? [paths.appSrc, workspacesConfig.paths]
+            : paths.appSrc,
        },
        {
          // "oneOf" will traverse all following loaders until one will
@@ -352,7 +361,12 @@ module.exports = function(webpackEnv) {
            // The preset includes JSX, Flow, TypeScript, and some ESnext features.
            {
              test: /\.(js|mjs|jsx|ts|tsx)$/,
-              include: paths.appSrc,
+              include:
+                isEnvDevelopment && workspacesConfig.development
+                  ? [paths.appSrc, workspacesConfig.paths]
+                  : isEnvProduction && workspacesConfig.production
+                    ? [paths.appSrc, workspacesConfig.paths]
+                    : paths.appSrc,
              loader: require.resolve('babel-loader'),
              options: {
                customize: require.resolve(
You can’t perform that action at this time.