@rm30/metro-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/README.md +67 -0
- package/index.cjs +72 -0
- package/index.d.ts +26 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# @rm30/metro-config
|
|
2
|
+
|
|
3
|
+
The shared Metro configuration for RM30's Expo apps.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm add -D @rm30/metro-config
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// apps/mobile/metro.config.cjs
|
|
11
|
+
const { getDefaultConfig } = require('expo/metro-config')
|
|
12
|
+
const { withRm30Metro } = require('@rm30/metro-config')
|
|
13
|
+
|
|
14
|
+
module.exports = withRm30Metro(getDefaultConfig(__dirname), {
|
|
15
|
+
workspaceAliases: { '@myproject/types': '../../packages/types/src' },
|
|
16
|
+
uniwind: { cssEntryFile: './global.css', dtsFile: './src/uniwind-types.d.ts' },
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Why the base config is passed in
|
|
21
|
+
|
|
22
|
+
The projects do not agree on how the base config is made, and both ways are right:
|
|
23
|
+
most call `getDefaultConfig` from `expo/metro-config`, while superwallet calls
|
|
24
|
+
`getSentryExpoConfig` so Sentry can attach to the bundle. Creating the config inside this
|
|
25
|
+
package would take that choice away, so it takes one instead.
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const { getSentryExpoConfig } = require('@sentry/react-native/metro')
|
|
29
|
+
module.exports = withRm30Metro(getSentryExpoConfig(__dirname), { /* … */ })
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Options
|
|
33
|
+
|
|
34
|
+
| Option | Default | |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `projectRoot` | `config.projectRoot` | Throws if neither is set, rather than silently building paths from `undefined`. |
|
|
37
|
+
| `workspaceAliases` | `{}` | Package name to path, absolute or relative to `projectRoot`. |
|
|
38
|
+
| `blockList` | `[]` | Extra patterns, **added** to the built-in test-file ones. |
|
|
39
|
+
| `platforms` | `ios, android, native, web` | |
|
|
40
|
+
| `uniwind` | omitted | `{ cssEntryFile, dtsFile }`, or omitted/`false` to skip the wrapper. |
|
|
41
|
+
|
|
42
|
+
## What it does for you
|
|
43
|
+
|
|
44
|
+
- **Blocks test files from the bundle.** All three apps had their own copy of the same two
|
|
45
|
+
regexes.
|
|
46
|
+
- **Adds every alias target to `watchFolders`.** Metro watches only the project root by
|
|
47
|
+
default, so a symlinked workspace package outside it is read once and never again —
|
|
48
|
+
edits to a shared package appear to do nothing until the bundler is restarted. This is
|
|
49
|
+
the single easiest thing to forget, which is why it is automatic here.
|
|
50
|
+
- **Applies Uniwind outermost.** It has to be the last wrapper, and doing it by hand is
|
|
51
|
+
how it ends up in the wrong place.
|
|
52
|
+
|
|
53
|
+
`watchFolders` are merged and de-duplicated, so passing an alias that is already watched
|
|
54
|
+
is harmless.
|
|
55
|
+
|
|
56
|
+
## Project-specific blocks stay in the project
|
|
57
|
+
|
|
58
|
+
superpool blocks Hardhat's build output:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
blockList: [/[/\\]packages[/\\]contracts[/\\](cache|artifacts|coverage|typechain-types)[/\\].*/]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
That is not shared policy — it exists because Metro watches the whole monorepo while
|
|
65
|
+
Hardhat creates and deletes lock files under `packages/contracts/cache` during compilation,
|
|
66
|
+
and a file vanishing mid-watch kills the watcher with `ENOENT`, taking the dev server down
|
|
67
|
+
whenever contracts are compiled. Real variance, and it belongs where the reason lives.
|
package/index.cjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const path = require('node:path')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Test files are never bundled. Every RM30 mobile app blocked these already,
|
|
5
|
+
* each with its own copy of the same two regexes.
|
|
6
|
+
*/
|
|
7
|
+
const TEST_FILE_PATTERNS = [/.*\.test\.(js|jsx|ts|tsx)$/, /.*\.spec\.(js|jsx|ts|tsx)$/]
|
|
8
|
+
|
|
9
|
+
const DEFAULT_PLATFORMS = ['ios', 'android', 'native', 'web']
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Apply the shared RM30 Metro configuration.
|
|
13
|
+
*
|
|
14
|
+
* The base config is passed in rather than created here, because the projects do not
|
|
15
|
+
* agree on how it is made: most call `getDefaultConfig` from `expo/metro-config`, and
|
|
16
|
+
* superwallet calls `getSentryExpoConfig` so Sentry can attach. Both are valid, and
|
|
17
|
+
* wrapping either would take that choice away.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} config A Metro config from expo/metro-config or @sentry/react-native.
|
|
20
|
+
* @param {object} [options]
|
|
21
|
+
* @param {string} [options.projectRoot] Defaults to `config.projectRoot`.
|
|
22
|
+
* @param {Record<string,string>} [options.workspaceAliases]
|
|
23
|
+
* Package name to path, absolute or relative to `projectRoot`. Each alias target is
|
|
24
|
+
* also added to `watchFolders`, so Metro reloads when the shared package changes —
|
|
25
|
+
* forgetting that is the usual reason edits to a workspace package appear to do
|
|
26
|
+
* nothing.
|
|
27
|
+
* @param {RegExp[]} [options.blockList] Extra patterns, added to the test-file ones.
|
|
28
|
+
* @param {string[]} [options.platforms]
|
|
29
|
+
* @param {{cssEntryFile: string, dtsFile: string}|false} [options.uniwind]
|
|
30
|
+
* Uniwind options, or false to skip the wrapper. Uniwind must be outermost, so this
|
|
31
|
+
* function applies it last.
|
|
32
|
+
* @returns {object} The configured Metro config.
|
|
33
|
+
*/
|
|
34
|
+
function withRm30Metro(config, options = {}) {
|
|
35
|
+
const { projectRoot = config.projectRoot, workspaceAliases = {}, blockList = [], platforms = DEFAULT_PLATFORMS, uniwind } = options
|
|
36
|
+
|
|
37
|
+
if (!projectRoot) {
|
|
38
|
+
throw new Error('@rm30/metro-config: projectRoot is required when config.projectRoot is unset')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const resolved = Object.fromEntries(
|
|
42
|
+
Object.entries(workspaceAliases).map(([name, target]) => [name, path.isAbsolute(target) ? target : path.resolve(projectRoot, target)])
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
config.resolver = config.resolver ?? {}
|
|
46
|
+
config.resolver.alias = { ...config.resolver.alias, ...resolved }
|
|
47
|
+
config.resolver.blockList = [...TEST_FILE_PATTERNS, ...blockList]
|
|
48
|
+
config.resolver.platforms = platforms
|
|
49
|
+
|
|
50
|
+
// Metro only watches the project root by default; a symlinked workspace package
|
|
51
|
+
// outside it is read once and never again.
|
|
52
|
+
const watched = new Set([...(config.watchFolders ?? []), ...Object.values(resolved)])
|
|
53
|
+
config.watchFolders = [...watched]
|
|
54
|
+
|
|
55
|
+
if (uniwind === false || uniwind === undefined) return config
|
|
56
|
+
|
|
57
|
+
// Resolved from the consuming project, not from here. A bare require would look in
|
|
58
|
+
// this package's own directory, and under pnpm's symlinked store an optional peer is
|
|
59
|
+
// not present there — so it resolves only if something happened to hoist it.
|
|
60
|
+
let withUniwindConfig
|
|
61
|
+
try {
|
|
62
|
+
withUniwindConfig = require(require.resolve('uniwind/metro', { paths: [projectRoot] })).withUniwindConfig
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`@rm30/metro-config: uniwind options were given but 'uniwind/metro' could not be resolved from ${projectRoot}. Install uniwind in that project, or pass uniwind: false.`,
|
|
66
|
+
{ cause }
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
return withUniwindConfig(config, uniwind)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { withRm30Metro, TEST_FILE_PATTERNS, DEFAULT_PLATFORMS }
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface UniwindOptions {
|
|
2
|
+
cssEntryFile: string
|
|
3
|
+
dtsFile: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface Rm30MetroOptions {
|
|
7
|
+
/** Defaults to `config.projectRoot`. */
|
|
8
|
+
projectRoot?: string
|
|
9
|
+
/** Package name to path, absolute or relative to `projectRoot`. Also watched. */
|
|
10
|
+
workspaceAliases?: Record<string, string>
|
|
11
|
+
/** Extra block patterns, added to the built-in test-file ones. */
|
|
12
|
+
blockList?: RegExp[]
|
|
13
|
+
/** Defaults to ios, android, native, web. */
|
|
14
|
+
platforms?: string[]
|
|
15
|
+
/** Uniwind options, or false/omitted to skip the wrapper. */
|
|
16
|
+
uniwind?: UniwindOptions | false
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Metro's config object is untyped upstream; this mirrors that rather than inventing a shape. */
|
|
20
|
+
// biome-ignore lint/suspicious/noExplicitAny: Metro does not ship a config type
|
|
21
|
+
export type MetroConfig = any
|
|
22
|
+
|
|
23
|
+
export declare function withRm30Metro(config: MetroConfig, options?: Rm30MetroOptions): MetroConfig
|
|
24
|
+
|
|
25
|
+
export declare const TEST_FILE_PATTERNS: RegExp[]
|
|
26
|
+
export declare const DEFAULT_PLATFORMS: string[]
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rm30/metro-config",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared RM30 Metro configuration for Expo monorepos.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Rafael Miziara",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/rafamiziara/cli.git",
|
|
10
|
+
"directory": "packages/metro-config"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["metro", "expo", "react-native", "uniwind"],
|
|
13
|
+
"main": "./index.cjs",
|
|
14
|
+
"types": "./index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./index.d.ts",
|
|
18
|
+
"default": "./index.cjs"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": ["index.cjs", "index.d.ts", "README.md"],
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"uniwind": ">=1.9.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"uniwind": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
}
|
|
33
|
+
}
|