Skip to content

Commit e6e3f33

Browse files
author
F1LT3R
committed
CRA 3
1 parent c80e3fe commit e6e3f33

File tree

4 files changed

+315
-4
lines changed

4 files changed

+315
-4
lines changed

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
# :warning: WARNING!
2+
3+
This is a custom scripts-version of React-Scripts that provides Monorepo support.
4+
5+
See [React Workspaces Playground](@react-workspaces/react-scripts) for a working demo.
6+
7+
[![React Workspaces Playground Screenshots](https://i.imgur.com/7snWXD0.png)](https://github.com/react-workspaces/react-scripts)
8+
9+
For more information on why this ways created, please read this for more info: https://github.com/F1LT3R/cra-workspaces-support-1333
10+
11+
# Create React App [![Build Status](https://travis-ci.org/facebook/create-react-app.svg?branch=master)](https://travis-ci.org/facebook/create-react-app) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://github.com/facebook/create-react-app/pulls)
12+
113
# Create React App [![Build Status](https://travis-ci.org/facebook/create-react-app.svg?branch=master)](https://travis-ci.org/facebook/create-react-app) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://github.com/facebook/create-react-app/pulls)
214

315
Create React apps with no build configuration.

packages/react-scripts/config/webpack.config.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
2929
const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent');
3030
const paths = require('./paths');
3131
const modules = require('./modules');
32+
const yarnWorkspaces = require('./yarn-workspaces');
3233
const getClientEnvironment = require('./env');
3334
const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin');
3435
const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin');
@@ -53,12 +54,22 @@ const cssModuleRegex = /\.module\.css$/;
5354
const sassRegex = /\.(scss|sass)$/;
5455
const sassModuleRegex = /\.module\.(scss|sass)$/;
5556

57+
const workspacesConfig = yarnWorkspaces.init(paths);
58+
5659
// This is the production and development configuration.
5760
// It is focused on developer experience, fast rebuilds, and a minimal bundle.
5861
module.exports = function(webpackEnv) {
5962
const isEnvDevelopment = webpackEnv === 'development';
6063
const isEnvProduction = webpackEnv === 'production';
6164

65+
const workspacesMainFields = [workspacesConfig.packageEntry, 'main'];
66+
const mainFields =
67+
isEnvDevelopment && workspacesConfig.development
68+
? workspacesMainFields
69+
: isEnvProduction && workspacesConfig.production
70+
? workspacesMainFields
71+
: undefined;
72+
6273
// Webpack uses `publicPath` to determine where the app is being served from.
6374
// It requires a trailing slash, or the file assets will get an incorrect path.
6475
// In development, we always serve from the root. This makes config easier.
@@ -279,6 +290,7 @@ module.exports = function(webpackEnv) {
279290
extensions: paths.moduleFileExtensions
280291
.map(ext => `.${ext}`)
281292
.filter(ext => useTypeScript || !ext.includes('ts')),
293+
mainFields,
282294
alias: {
283295
// Support React Native Web
284296
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
@@ -330,7 +342,11 @@ module.exports = function(webpackEnv) {
330342
loader: require.resolve('eslint-loader'),
331343
},
332344
],
333-
include: paths.appSrc,
345+
include: isEnvDevelopment && workspacesConfig.development
346+
? [paths.appSrc, workspacesConfig.paths]
347+
: isEnvProduction && workspacesConfig.production
348+
? [paths.appSrc, workspacesConfig.paths]
349+
: paths.appSrc,
334350
},
335351
{
336352
// "oneOf" will traverse all following loaders until one will
@@ -352,7 +368,12 @@ module.exports = function(webpackEnv) {
352368
// The preset includes JSX, Flow, TypeScript, and some ESnext features.
353369
{
354370
test: /\.(js|mjs|jsx|ts|tsx)$/,
355-
include: paths.appSrc,
371+
include:
372+
isEnvDevelopment && workspacesConfig.development
373+
? [paths.appSrc, workspacesConfig.paths]
374+
: isEnvProduction && workspacesConfig.production
375+
? [paths.appSrc, workspacesConfig.paths]
376+
: paths.appSrc,
356377
loader: require.resolve('babel-loader'),
357378
options: {
358379
customize: require.resolve(
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
'use strict';
2+
3+
const fse = require('fs-extra');
4+
const path = require('path');
5+
const findUp = require('find-up');
6+
const glob = require('glob');
7+
8+
const loadPackageJson = packagePath => {
9+
try {
10+
const packageObj = fse.readJsonSync(packagePath);
11+
return packageObj;
12+
} catch (err) {
13+
throw err;
14+
}
15+
};
16+
17+
const getWorkspacesRootConfig = dir => {
18+
const packageJsonUp = findUp.sync('package.json', {cwd: dir});
19+
20+
if (packageJsonUp === null) {
21+
return false;
22+
}
23+
24+
const packageObj = loadPackageJson(packageJsonUp);
25+
26+
if (Reflect.has(packageObj, 'workspaces')) {
27+
const workspacesRootConfig = {
28+
root: path.dirname(packageJsonUp),
29+
workspaces: packageObj.workspaces
30+
};
31+
return workspacesRootConfig;
32+
}
33+
34+
const dirUp = path.dirname(dir);
35+
return getWorkspacesRootConfig(dirUp);
36+
};
37+
38+
const getPackagePaths = (root, workspacesList) => {
39+
const packageList = [];
40+
41+
workspacesList.forEach(workspace => {
42+
const workspaceDir = path.dirname(workspace);
43+
const workspaceAbsDir = path.join(root, workspaceDir);
44+
const packageJsonGlob = path.join('**!(node_modules)', 'package.json');
45+
const packageJsonAbsPaths = glob
46+
.sync(packageJsonGlob, {cwd: workspaceAbsDir})
47+
.map(pkgPath => path.join(workspaceAbsDir, pkgPath));
48+
49+
packageList.push(...packageJsonAbsPaths);
50+
});
51+
52+
return packageList;
53+
};
54+
55+
const getDeep = (obj, keyChain) => {
56+
const nextKey = keyChain.shift();
57+
const has = Reflect.has(obj, nextKey);
58+
const val = obj[nextKey];
59+
60+
if (keyChain.length === 0) {
61+
return val;
62+
}
63+
64+
if (has) {
65+
return getDeep(val, keyChain);
66+
}
67+
68+
return false;
69+
};
70+
71+
const resolveBabelLoaderPaths = ({root, workspacesList}, packageEntry) => {
72+
const packageJsonPaths = getPackagePaths(root, workspacesList);
73+
const babelLoaderPaths = [];
74+
75+
packageJsonPaths.map(absPkgPath => {
76+
const packageJson = loadPackageJson(absPkgPath);
77+
const mainSrcFile = getDeep(packageJson, [packageEntry]);
78+
79+
if (mainSrcFile) {
80+
const mainSrcPath = path.dirname(mainSrcFile);
81+
const packageAbsDir = path.dirname(absPkgPath);
82+
const absSrcPath = path.join(packageAbsDir, mainSrcPath);
83+
babelLoaderPaths.push(absSrcPath);
84+
}
85+
});
86+
87+
return babelLoaderPaths;
88+
};
89+
90+
const loadAppSettings = appPackageJson => {
91+
const result = {workspaces: {}, dependencies: {}};
92+
93+
const appPackageObj = loadPackageJson(appPackageJson);
94+
95+
const dependencies = getDeep(appPackageObj, ['dependencies']);
96+
const devDependencies = getDeep(appPackageObj, ['devDependencies']);
97+
98+
if (!dependencies && !devDependencies) return result;
99+
100+
if (dependencies) {
101+
result.dependencies = Object.assign(result.dependencies, dependencies);
102+
}
103+
104+
if (devDependencies) {
105+
result.dependencies = Object.assign(
106+
result.dependencies,
107+
devDependencies
108+
);
109+
}
110+
111+
const reactScripts = getDeep(appPackageObj, ['react-scripts']);
112+
if (!reactScripts) return result;
113+
114+
const workspaces = getDeep(reactScripts, ['workspaces']);
115+
result.workspaces = workspaces;
116+
if (!workspaces) return result;
117+
118+
return workspaces;
119+
};
120+
121+
const guard = (appDirectory, appPackageJson) => {
122+
if (!appDirectory) {
123+
throw new Error('appDirectory not provided');
124+
}
125+
126+
if (typeof appDirectory !== 'string') {
127+
throw new Error('appDirectory should be a string');
128+
}
129+
130+
if (!appPackageJson) {
131+
throw new Error('appPackageJson not provided');
132+
}
133+
134+
if (typeof appPackageJson !== 'string') {
135+
throw new Error('appPackageJson should be a string');
136+
}
137+
};
138+
139+
const getPkg = path => {
140+
const pkgPath = findUp.sync('package.json', {cwd: path});
141+
const pkg = loadPackageJson(pkgPath);
142+
return pkg;
143+
};
144+
145+
const getDeps = pkg => {
146+
const deps = getDeep(pkg, ['dependencies']);
147+
const devDeps = getDeep(pkg, ['devDependencies']);
148+
149+
let dependencies = {};
150+
151+
if (deps) {
152+
dependencies = Object.assign(dependencies, deps);
153+
}
154+
155+
if (devDeps) {
156+
dependencies = Object.assign(dependencies, devDeps);
157+
}
158+
159+
return dependencies;
160+
};
161+
162+
const depsTable = {};
163+
164+
const filterDeps = deps =>
165+
Reflect.ownKeys(deps).filter(dep => Reflect.has(depsTable, dep));
166+
167+
const filterDepsTable = () => {
168+
Reflect.ownKeys(depsTable).forEach(depName => {
169+
const depsList = depsTable[depName].deps;
170+
const workspacesOnlyDeps = filterDeps(depsList);
171+
depsTable[depName].deps = workspacesOnlyDeps;
172+
});
173+
};
174+
175+
const buildDepsTable = srcPaths => {
176+
srcPaths.forEach(path => {
177+
const pkg = getPkg(path);
178+
const name = pkg.name;
179+
const deps = getDeps(pkg);
180+
depsTable[name] = {path, deps};
181+
});
182+
};
183+
184+
const filterSrcPaths = (srcPaths, dependencies) => {
185+
const filteredPaths = [];
186+
187+
srcPaths.forEach(path => {
188+
const pkg = getPkg(path);
189+
190+
if (dependencies && Reflect.has(dependencies, pkg.name)) {
191+
filteredPaths.push(path);
192+
193+
const subDeps = depsTable[pkg.name].deps;
194+
const subPaths = filterSrcPaths(srcPaths, subDeps);
195+
filteredPaths.push(...subPaths);
196+
}
197+
});
198+
199+
return filteredPaths;
200+
};
201+
202+
const init = paths => {
203+
guard(paths.appPath, paths.appPackageJson);
204+
205+
const config = {
206+
root: null,
207+
paths: [],
208+
packageEntry: 'main:src',
209+
development: true,
210+
production: true
211+
};
212+
213+
const {root, workspaces} = getWorkspacesRootConfig(paths.appPath);
214+
const workspacesList = [];
215+
216+
// Normally "workspaces" in package.json is an array
217+
if (Array.isArray(workspaces)) {
218+
workspacesList.push(...workspaces);
219+
}
220+
221+
// Sometimes "workspaces" in package.json is an object
222+
// with a ".packages" sub-array, eg: when used with "nohoist"
223+
// See: https://yarnpkg.com/blog/2018/02/15/nohoist
224+
if (workspaces && !Array.isArray(workspaces) && Reflect.has(workspaces, 'packages')) {
225+
workspacesList.push(...workspaces.packages);
226+
}
227+
228+
if (workspacesList.length === 0) {
229+
return config;
230+
}
231+
console.log('Yarn Workspaces paths detected.');
232+
config.root = root;
233+
234+
const appSettings = loadAppSettings(paths.appPackageJson);
235+
236+
if (Reflect.has(appSettings.workspaces, 'development')) {
237+
config.development = appSettings.workspaces.development ? true : false;
238+
}
239+
240+
if (Reflect.has(appSettings.workspaces, 'production')) {
241+
config.production = appSettings.workspaces.production ? true : false;
242+
}
243+
244+
if (Reflect.has(appSettings.workspaces, 'package-entry')) {
245+
config.packageEntry = appSettings.workspaces['package-entry'];
246+
}
247+
248+
const babelSrcPaths = resolveBabelLoaderPaths(
249+
{root, workspacesList},
250+
config.packageEntry
251+
);
252+
253+
buildDepsTable(babelSrcPaths);
254+
255+
const applicableSrcPaths = filterSrcPaths(
256+
babelSrcPaths,
257+
appSettings.dependencies,
258+
paths.appPath
259+
);
260+
261+
console.log(
262+
`Found ${babelSrcPaths.length} path(s) with "${
263+
config.packageEntry
264+
}" entry.`
265+
);
266+
267+
if (applicableSrcPaths.length > 0) {
268+
config.paths.push(...applicableSrcPaths);
269+
}
270+
271+
console.log('Exporting Workspaces config to Webpack.');
272+
console.log(config);
273+
return config;
274+
};
275+
276+
module.exports = {
277+
init
278+
};

packages/react-scripts/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "react-scripts",
3-
"version": "3.0.0",
2+
"name": "@react-workspaces/react-scripts",
3+
"version": "3.0.0-alpha-02",
44
"description": "Configuration and scripts for Create React App.",
55
"repository": {
66
"type": "git",

0 commit comments

Comments
 (0)