react-native-monorepo-config 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/index.js +113 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Krister Kari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# react-native-monorepo-config
|
|
2
|
+
|
|
3
|
+
Helper to configure Metro for a React Native app in a monorepo.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
[Metro](https://metrobundler.dev/) ([React Native](https://reactnative.dev)'s bundler) doesn't work well with monorepos out of the box. This package is intended to make the configuration easier.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
yarn add -D react-native-monorepo-config
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Let's consider the following monorepo structure:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
my-monorepo
|
|
21
|
+
├── apps
|
|
22
|
+
│ └── my-app
|
|
23
|
+
└── packages
|
|
24
|
+
├── a/
|
|
25
|
+
└── b/
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Here, `my-app` is a React Native app, and `a` and `b` are libraries that are used in the app.
|
|
29
|
+
|
|
30
|
+
To configure Metro for `my-app`, you can create a `metro.config.js` file in the `my-app` directory with the following content:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
const { getDefaultConfig } = require("@react-native/metro-config"); // Import from `@expo/metro-config` if using Expo CLI
|
|
34
|
+
const { withMetroConfig } = require("react-native-monorepo-config");
|
|
35
|
+
|
|
36
|
+
module.exports = withMetroConfig(
|
|
37
|
+
getDefaultConfig(__dirname), // Metro config to extend
|
|
38
|
+
{
|
|
39
|
+
root: path.resolve(__dirname, "../.."), // Path to the monorepo root
|
|
40
|
+
dirname: __dirname, // Path to the current directory
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It's also recommended to disable hoisting for the React Native app's dependencies to avoid issues.
|
|
46
|
+
|
|
47
|
+
For Yarn 4, hoisting can be disabled by setting `nmHoistingLimits: 'workspaces'` in `.yarnrc.yml`. See [Yarn documentation](https://yarnpkg.com/configuration/yarnrc#nmHoistingLimits) for more details.
|
|
48
|
+
|
|
49
|
+
If you want to customize the returned Metro config, remember to merge the returned config with your custom config. For example:
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
const monoRepoConfig = withMetroConfig(getDefaultConfig(__dirname), {
|
|
53
|
+
root: path.resolve(__dirname, "../.."),
|
|
54
|
+
dirname: __dirname,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
...monoRepoConfig,
|
|
59
|
+
|
|
60
|
+
resolver: {
|
|
61
|
+
...monoRepoConfig.resolver,
|
|
62
|
+
|
|
63
|
+
assetExts: resolver.assetExts.filter((ext) => ext !== "svg"),
|
|
64
|
+
sourceExts: [...resolver.sourceExts, "svg"],
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How it works
|
|
70
|
+
|
|
71
|
+
This configuration will setup a few things:
|
|
72
|
+
|
|
73
|
+
- Configure Metro to watch for changes in other packages in the monorepo instead of just the current package. This may slow down the bundling process, in large monorepos. In that case, you can override `watchFolders` to add specific folders to watch instead.
|
|
74
|
+
- Block packages defined in `peerDependencies` of other packages in the monorepo to avoid duplicate versions from being loaded. Loading duplicate versions of some packages such as `react` can cause issues. Make sure to specify `peerDependencies` for your packages appropriately.
|
|
75
|
+
- If the packages defined in `peerDependencies` have been hoisted to the monorepo root, point Metro to them so they can be found.
|
|
76
|
+
- Configure Metro's resolve to prioritize `package.json#source` or the `source` condition in `package.json#exports` so that the app can import source code directly from other packages in the monorepo. To utilize this, make sure to add `"source": "src/index.ts"` or `"exports": { ".": { "source": "./src/index.ts" } }` to the `package.json` of the packages you want to import from.
|
package/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import escape from 'escape-string-regexp';
|
|
2
|
+
import glob from 'fast-glob';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configure Metro for a React Native app in a monorepo.
|
|
8
|
+
*
|
|
9
|
+
* @param {import('metro-config').MetroConfig} baseConfig Base Metro config to extend.
|
|
10
|
+
* @param {string} options.root Root directory path of the monorepo.
|
|
11
|
+
* @param {string} options.dirname Directory path of the current package.
|
|
12
|
+
*
|
|
13
|
+
* @returns {import('metro-config').MetroConfig}
|
|
14
|
+
*/
|
|
15
|
+
export function withMetroConfig(baseConfig, { root, dirname }) {
|
|
16
|
+
const pkg = JSON.parse(
|
|
17
|
+
fs.readFileSync(path.join(root, 'package.json'), 'utf8')
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
if (pkg.workspaces == null) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`No 'workspaces' field found in the 'package.json' at '${root}'.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Get the list of monorepo packages except current package
|
|
27
|
+
const packages = (pkg.workspaces.packages || pkg.workspaces)
|
|
28
|
+
.flatMap((pattern) =>
|
|
29
|
+
glob.sync(pattern, {
|
|
30
|
+
cwd: root,
|
|
31
|
+
onlyDirectories: true,
|
|
32
|
+
ignore: [`**/node_modules`, `**/.git`, `**/.yarn`],
|
|
33
|
+
})
|
|
34
|
+
)
|
|
35
|
+
.filter((p) => {
|
|
36
|
+
const dir = path.join(root, p);
|
|
37
|
+
|
|
38
|
+
// Ignore current package
|
|
39
|
+
if (path.relative(dir, dirname) === '') {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Ignore packages that don't have a package.json
|
|
44
|
+
return fs.existsSync(path.join(dir, 'package.json'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Get the list of peer dependencies for all packages in the monorepo
|
|
48
|
+
const peers = packages
|
|
49
|
+
.flatMap((it) => {
|
|
50
|
+
const pak = JSON.parse(
|
|
51
|
+
fs.readFileSync(path.join(root, it, 'package.json'), 'utf8')
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
return pak.peerDependencies ? Object.keys(pak.peerDependencies) : [];
|
|
55
|
+
})
|
|
56
|
+
.sort()
|
|
57
|
+
.filter(
|
|
58
|
+
(m, i, self) => self.lastIndexOf(m) === i // Remove duplicates
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
// We need to exclude the peerDependencies we've collected in packages' node_modules
|
|
62
|
+
// Otherwise duplicate versions of the same package will be loaded
|
|
63
|
+
const blockList = new RegExp(
|
|
64
|
+
'(' +
|
|
65
|
+
packages
|
|
66
|
+
.flatMap((it) =>
|
|
67
|
+
peers.map((m) => `^${escape(path.join(it, 'node_modules', m))}\\/.*$`)
|
|
68
|
+
)
|
|
69
|
+
.join('|') +
|
|
70
|
+
')$'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// When we import a package from the monorepo, metro won't be able to find their deps if they are hoisted
|
|
74
|
+
// We need to specify them in `extraNodeModules` to tell metro where to find them
|
|
75
|
+
const extraNodeModules = peers.reduce((acc, name) => {
|
|
76
|
+
if (fs.existsSync(path.join(root, 'node_modules', name))) {
|
|
77
|
+
acc[name] = path.join(root, 'node_modules', name);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return acc;
|
|
81
|
+
}, {});
|
|
82
|
+
|
|
83
|
+
/** @type {import('metro-config').MetroConfig} */
|
|
84
|
+
return {
|
|
85
|
+
...baseConfig,
|
|
86
|
+
|
|
87
|
+
projectRoot: dirname,
|
|
88
|
+
|
|
89
|
+
// We need to watch the root of the monorepo
|
|
90
|
+
// This lets Metro find the monorepo packages automatically using haste
|
|
91
|
+
// This also lets us import modules from monorepo root
|
|
92
|
+
watchFolders: [root],
|
|
93
|
+
|
|
94
|
+
resolver: {
|
|
95
|
+
...baseConfig.resolver,
|
|
96
|
+
|
|
97
|
+
blockList,
|
|
98
|
+
extraNodeModules,
|
|
99
|
+
resolveRequest: (context, realModuleName, platform) => {
|
|
100
|
+
// Prefer the source field for monorepo packages to consume source code
|
|
101
|
+
if (packages.includes(realModuleName)) {
|
|
102
|
+
context.mainFields = ['source', ...context.mainFields];
|
|
103
|
+
context.unstable_conditionNames = [
|
|
104
|
+
'source',
|
|
105
|
+
...context.unstable_conditionNames,
|
|
106
|
+
];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return context.resolveRequest(context, realModuleName, platform);
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-monorepo-config",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Configure Metro for a React Native app in a monorepo",
|
|
5
|
+
"repository": "https://github.com/satya164/react-native-monorepo-config",
|
|
6
|
+
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/)",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public",
|
|
10
|
+
"registry": "https://registry.npmjs.org/"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "index.js",
|
|
14
|
+
"files": [
|
|
15
|
+
"index.js"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"escape-string-regexp": "^5.0.0",
|
|
19
|
+
"fast-glob": "^3.3.3"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@release-it/conventional-changelog": "^10.0.1",
|
|
23
|
+
"prettier": "^3.5.3",
|
|
24
|
+
"release-it": "^19.0.1"
|
|
25
|
+
},
|
|
26
|
+
"prettier": {
|
|
27
|
+
"quoteProps": "consistent",
|
|
28
|
+
"tabWidth": 2,
|
|
29
|
+
"useTabs": false,
|
|
30
|
+
"singleQuote": true,
|
|
31
|
+
"trailingComma": "es5"
|
|
32
|
+
},
|
|
33
|
+
"release-it": {
|
|
34
|
+
"git": {
|
|
35
|
+
"commitMessage": "chore: release ${version}",
|
|
36
|
+
"tagName": "v${version}"
|
|
37
|
+
},
|
|
38
|
+
"npm": {
|
|
39
|
+
"publish": true
|
|
40
|
+
},
|
|
41
|
+
"github": {
|
|
42
|
+
"release": true
|
|
43
|
+
},
|
|
44
|
+
"plugins": {
|
|
45
|
+
"@release-it/conventional-changelog": {
|
|
46
|
+
"preset": {
|
|
47
|
+
"name": "conventionalcommits"
|
|
48
|
+
},
|
|
49
|
+
"infile": "CHANGELOG.md"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|