├── app
├── alpha.js
├── beta.ts
├── omega.tsx
└── README.md
├── assets
├── icon.png
├── favicon.png
├── splash.png
└── adaptive-icon.png
├── tsconfig.json
├── babel.config.js
├── .expo-shared
└── assets.json
├── .gitignore
├── metro.config.js
├── App.tsx
├── app.json
├── package.json
├── README.md
└── types
└── metroRequire.d.ts
/app/alpha.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/beta.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/omega.tsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/README.md:
--------------------------------------------------------------------------------
1 | # Demo
2 |
3 | Add or remove js files to this directory to see the updates...
4 |
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvanBacon/Metro-Context-Modules-Demo/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvanBacon/Metro-Context-Modules-Demo/HEAD/assets/favicon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvanBacon/Metro-Context-Modules-Demo/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "include": ["./types/metroRequire"]
4 | }
5 |
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/EvanBacon/Metro-Context-Modules-Demo/HEAD/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function(api) {
2 | api.cache(true);
3 | return {
4 | presets: ['babel-preset-expo'],
5 | };
6 | };
7 |
--------------------------------------------------------------------------------
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "12bb71342c6255bbf50437ec8f4441c083f47cdb74bd89160c15e4f43e52a1cb": true,
3 | "40b842e832070c58deac6aa9e08fa459302ee3f9da492c7e77d93d2fbf4a56fd": true
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .expo/
3 | dist/
4 | npm-debug.*
5 | *.jks
6 | *.p8
7 | *.p12
8 | *.key
9 | *.mobileprovision
10 | *.orig.*
11 | web-build/
12 |
13 | # macOS
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/metro.config.js:
--------------------------------------------------------------------------------
1 | // Learn more https://docs.expo.io/guides/customizing-metro
2 | const { getDefaultConfig } = require("expo/metro-config");
3 |
4 | const config = getDefaultConfig(__dirname);
5 | config.transformer = {
6 | // `require.context` support
7 | unstable_allowRequireContext: true,
8 | };
9 |
10 | module.exports = config;
11 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { StatusBar } from "expo-status-bar";
3 | import { StyleSheet, Text, View } from "react-native";
4 |
5 | const mod = require.context("./app");
6 |
7 | export default function App() {
8 | console.log("app context:", mod.keys());
9 | return (
10 |
11 | Files in `app/`
12 |
13 | {mod.keys().map((key) => (
14 | {key}
15 | ))}
16 |
17 |
18 |
19 | );
20 | }
21 |
22 | const styles = StyleSheet.create({
23 | container: {
24 | flex: 1,
25 | backgroundColor: "#fff",
26 | alignItems: "center",
27 | justifyContent: "center",
28 | },
29 | });
30 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "context-irl",
4 | "slug": "context-irl",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "updates": {
15 | "fallbackToCacheTimeout": 0
16 | },
17 | "assetBundlePatterns": [
18 | "**/*"
19 | ],
20 | "ios": {
21 | "supportsTablet": true
22 | },
23 | "android": {
24 | "adaptiveIcon": {
25 | "foregroundImage": "./assets/adaptive-icon.png",
26 | "backgroundColor": "#FFFFFF"
27 | }
28 | },
29 | "web": {
30 | "favicon": "./assets/favicon.png"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "metro-context-modules-demo",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "@types/react": "~18.0.0",
13 | "@types/react-native": "~0.69.1",
14 | "expo": "~46.0.7",
15 | "expo-status-bar": "~1.4.0",
16 | "react": "18.0.0",
17 | "react-native": "0.69.4",
18 | "typescript": "^4.6.3"
19 | },
20 | "resolutions": {
21 | "metro": "0.72.1",
22 | "metro-babel-transformer": "0.72.1",
23 | "metro-cache": "0.72.1",
24 | "metro-cache-key": "0.72.1",
25 | "metro-config": "0.72.1",
26 | "metro-core": "0.72.1",
27 | "metro-hermes-compiler": "0.72.1",
28 | "metro-minify-uglify": "0.72.1",
29 | "metro-react-native-babel-preset": "0.72.1",
30 | "metro-react-native-babel-transformer": "0.72.1",
31 | "metro-resolver": "0.72.1",
32 | "metro-source-map": "0.72.1",
33 | "metro-symbolicate": "0.72.1",
34 | "metro-transform-plugins": "0.72.1",
35 | "metro-transform-worker": "0.72.1"
36 | },
37 | "devDependencies": {
38 | "@babel/core": "^7.12.9"
39 | },
40 | "private": true
41 | }
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Metro Context Modules Demo
2 |
3 | This is a demo of using the experimental 'Context Module' (`require.context`) feature in Metro bundler.
4 |
5 | ## Setup
6 |
7 | `metro.config.js`
8 |
9 | ```js
10 | const { getDefaultConfig } = require("expo/metro-config");
11 |
12 | const config = getDefaultConfig(__dirname);
13 |
14 | config.transformer = {
15 | // `require.context` support
16 | unstable_allowRequireContext: true,
17 | };
18 |
19 | module.exports = config;
20 | ```
21 |
22 | At the moment, you'll need to force the Metro versions in your `package.json`:
23 |
24 | ```json
25 | {
26 | "resolutions": {
27 | "metro": "0.72.1",
28 | "metro-babel-transformer": "0.72.1",
29 | "metro-cache": "0.72.1",
30 | "metro-cache-key": "0.72.1",
31 | "metro-config": "0.72.1",
32 | "metro-core": "0.72.1",
33 | "metro-hermes-compiler": "0.72.1",
34 | "metro-minify-uglify": "0.72.1",
35 | "metro-react-native-babel-preset": "0.72.1",
36 | "metro-react-native-babel-transformer": "0.72.1",
37 | "metro-resolver": "0.72.1",
38 | "metro-source-map": "0.72.1",
39 | "metro-symbolicate": "0.72.1",
40 | "metro-transform-plugins": "0.72.1",
41 | "metro-transform-worker": "0.72.1"
42 | }
43 | }
44 | ```
45 |
46 | If you want to add TypeScript support to `require.context`, include the [`types/metroRequire.d.ts`](./types/metroRequire.d.ts), then extend the `tsconfig.json`:
47 |
48 | ```json
49 | {
50 | "extends": "expo/tsconfig.base",
51 | "include": ["./types/metroRequire"]
52 | }
53 | ```
54 |
55 | ## Usage
56 |
57 | - Run `yarn start`
58 | - Add files to the `app/` folder to see the UI update dynamically.
59 |
60 | For more info, see the [Webpack docs on `require.context`](https://webpack.js.org/guides/dependency-management/#requirecontext).
61 |
62 | ```tsx
63 | const ctx = require.context("./app");
64 |
65 | const myModule = ctx[ctx.keys()[0]];
66 | ```
67 |
--------------------------------------------------------------------------------
/types/metroRequire.d.ts:
--------------------------------------------------------------------------------
1 | // Based on https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/webpack-env/index.d.ts
2 | // Adds support for the runtime `require.context` method.
3 | // https://github.com/facebook/metro/pull/822/
4 |
5 | declare var module: NodeModule;
6 |
7 | declare namespace __MetroModuleApi {
8 | interface RequireContext {
9 | /** Return the keys that can be resolved. */
10 | keys(): string[];
11 | (id: string): any;
12 | (id: string): T;
13 | /** **Unimplemented:** Return the module identifier for a user request. */
14 | resolve(id: string): string;
15 | /** **Unimplemented:** Readable identifier for the context module. */
16 | id: string;
17 | }
18 |
19 | interface RequireFunction {
20 | /**
21 | * Returns the exports from a dependency. The call is sync. No request to the server is fired. The compiler ensures that the dependency is available.
22 | */
23 | (path: string): any;
24 | (path: string): T;
25 |
26 | /**
27 | * **Experimental:** Import all modules in a given directory. This module dynamically updates when the files in a directory are added or removed.
28 | *
29 | * **Enabling:** This feature can be enabled by setting the `transformer.unstable_allowRequireContext` property to `true` in your Metro configuration.
30 | *
31 | * @param path File path pointing to the directory to require.
32 | * @param recursive Should search for files recursively. Optional, default `true` when `require.context` is used.
33 | * @param filter Filename filter pattern for use in `require.context`. Optional, default `.*` (any file) when `require.context` is used.
34 | * @param mode Mode for resolving dynamic dependencies. Defaults to `sync`.
35 | */
36 | context(
37 | path: string,
38 | recursive?: boolean,
39 | filter?: RegExp,
40 | mode?: "sync" | "eager" | "weak" | "lazy" | "lazy-once"
41 | ): RequireContext;
42 | }
43 | }
44 |
45 | /**
46 | * Declare process variable
47 | */
48 | declare namespace NodeJS {
49 | interface Require extends __MetroModuleApi.RequireFunction {}
50 | }
51 | declare var process: NodeJS.Process;
52 |
--------------------------------------------------------------------------------