├── 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 | --------------------------------------------------------------------------------