I'm not sure if it's a well supported path or if there are strong arguments for preferring webpack, but I'd love to see more docs and information about using metro.
My goal here would be to leverage consistency of build tooling for my react native vs. react native web projects. But I also know that web projects tend to require a lot of the magic that webpack offers which maybe metro doesn't have?
I agree that it will be good once a single bundler can be used for all platforms. And Metro has some nice perf benefits over webpack (laziness). But this is something for the Metro project to eventually document when it is considered stable enough for public consumption. It's not something I can do or promote at this stage. I'd encourage you to reach out to the Metro team if you'd like to discuss this with them, or get pointers to what would be involved!
@necolas - Sounds good, thanks for the info.
So, as of at least the date of our discussion here, best bet is to prefer webpack, eh?
I managed to get Metro bundling react-native-web with the following steps:
Add the following metro.config.js to the root project directory, which is based off the react-native-windows config (https://github.com/microsoft/react-native-windows/blob/3bab85d89f4b01adc59b26a3e523fc4fe6b4ac93/vnext/local-cli/generator-windows/templates/metro.config.js)
const fs = require('fs');
const path = require('path');
const blacklist = require('metro-config/src/defaults/blacklist');
const rnPath = fs.realpathSync(
path.resolve(require.resolve('react-native/package.json'), '..'),
);
const rnwPath = fs.realpathSync(
path.resolve(require.resolve('react-native-web/package.json'), '..'),
);
module.exports = {
resolver: {
extraNodeModules: {
// Redirect react-native to react-native-web
'react-native': rnwPath,
'react-native-web': rnwPath,
},
// Include the macos platform in addition to the defaults because the fork includes macos, but doesn't declare it
platforms: ['ios', 'android', 'windesktop', 'windows', 'web', 'macos'],
providesModuleNodeModules: ['react-native-web'],
// Since there are multiple copies of react-native, we need to ensure that metro only sees one of them
// This should go in RN 0.61 when haste is removed
blacklistRE: blacklist([
new RegExp(
`${(path.resolve(rnPath) + path.sep).replace(/[/\\\\]/g, '[/\\\\]')}.*`,
),
// This stops "react-native run-web" from causing the metro server to crash if its already running
new RegExp(
`${path
.resolve(__dirname, 'web')
.replace(/[/\\\\]/g, '[/\\\\]')}.*`,
),
]),
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};
Edit the function getSha1 in node_modules\metro\src\node-haste\DependencyGraph.js to match the following, as described in https://github.com/facebook/metro/issues/330
getSha1(filename) {
const resolvedPath = fs.realpathSync(filename);
const sha1 = this._hasteFS.getSha1(resolvedPath);
if (!sha1) {
return getFileHash(resolvedPath)
function getFileHash(file) {
return require('crypto')
.createHash('sha1')
.update(fs.readFileSync(file))
.digest('hex')
}
}
return sha1;
}
You can then create a bundle using the command
react-native bundle --platform web --dev false --entry-file index.js --bundle-output <path-to-output>/web.bundle.js --assets-dest <path-to-output>/assets
Edit:
Apparently I hadn't tested running the android build after these changes. To get other builds working:
--config ../../../../metroWeb.config.js to the react-native bundle command@howlettt thanks for sharing, this worked well for me as well :)
I had to add "assetRegistryPath" to the transformer, since some of my libraries include images.
Adding the path
const assetRegistryPath = fs.realpathSync(
path.resolve(require.resolve('react-native-web/src/modules/AssetRegistry/index.js'), '..'),
)
and changing the transformer
transformer: {
assetRegistryPath: assetRegistryPath,
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
In my case I also had to replace "react-native-linear-gradient" with "react-native-web-linear-gradient" using the same method you used to replace "react-native" with "react-native-web".
@howlettt does this implementation also allow for Fast Refresh to be used for web?
@tehOPEologist no, you have to manually refresh the page after each change
ok, was hoping there's a metro for web solution that uses fast refresh =\
Updated steps for react-native 0.62
Add a new file metroWeb.config.js to the root project directory with content:
const fs = require('fs');
const path = require('path');
const blacklist = require('metro-config/src/defaults/blacklist');
const rnPath = fs.realpathSync(
path.resolve(require.resolve('react-native/package.json'), '..'),
);
const rnwPath = fs.realpathSync(
path.resolve(require.resolve('react-native-web/package.json'), '..'),
);
module.exports = {
resolver: {
extraNodeModules: {
// Redirect react-native to react-native-web
'react-native': rnwPath,
'react-native-web': rnwPath,
},
blacklistRE: blacklist([
// Since there are multiple copies of react-native, we need to ensure that metro only sees one of them
new RegExp(
`${(path.resolve(rnPath) + path.sep).replace(/[/\\\\]/g, '\\\\')}.*`,
),
// This stops "react-native run-web" from causing the metro server to crash if its already running
new RegExp(
`${path.resolve(__dirname, 'web').replace(/[/\\]/g, '/')}.*`,
),
]),
platforms: ['ios', 'android', 'web'],
},
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
};
Edit the getSha1 function in both the following files to fix https://github.com/facebook/metro/issues/330
[project]\node_modules\metro\src\node-haste\DependencyGraph.js
C:\Users\[User]\AppData\Roaming\npm\node_modules\@react-native-community\cli\node_modules\metro\src\node-haste\DependencyGraph.js
by changing
if (!sha1) {
throw new ReferenceError(
`SHA-1 for file ${filename} (${resolvedPath}) is not computed`,
);
}
to
if (!sha1) {
return getFileHash(resolvedPath)
function getFileHash(file) {
return require('crypto')
.createHash('sha1')
.update(fs.readFileSync(file))
.digest('hex')
}
}
You can then create a bundle using the command:
react-native bundle --platform web --dev false --entry-file index.js --bundle-output [path-to-output]/bundle.js --assets-dest [path-to-output] --config metroWeb.config.js
But I don't have this file require('metro-config/src/defaults/blacklist')
@mrKorg looks like they renamed it to exclusionList https://github.com/facebook/metro/blob/master/packages/metro-config/src/defaults/exclusionList.js
Updated steps for react-native 0.62
Step 1
Add a new file metroWeb.config.js to the root project directory with content:
const fs = require('fs'); const path = require('path'); const blacklist = require('metro-config/src/defaults/blacklist'); const rnPath = fs.realpathSync( path.resolve(require.resolve('react-native/package.json'), '..'), ); const rnwPath = fs.realpathSync( path.resolve(require.resolve('react-native-web/package.json'), '..'), ); module.exports = { resolver: { extraNodeModules: { // Redirect react-native to react-native-web 'react-native': rnwPath, 'react-native-web': rnwPath, }, blacklistRE: blacklist([ // Since there are multiple copies of react-native, we need to ensure that metro only sees one of them new RegExp( `${(path.resolve(rnPath) + path.sep).replace(/[/\\\\]/g, '\\\\')}.*`, ), // This stops "react-native run-web" from causing the metro server to crash if its already running new RegExp( `${path.resolve(__dirname, 'web').replace(/[/\\]/g, '/')}.*`, ), ]), platforms: ['ios', 'android', 'web'], }, transformer: { getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, inlineRequires: false, }, }), }, };Step 2
Edit the getSha1 function in both the following files to fix facebook/metro#330
[project]\node_modules\metro\src\node-haste\DependencyGraph.js C:\Users\[User]\AppData\Roaming\npm\node_modules\@react-native-community\cli\node_modules\metro\src\node-haste\DependencyGraph.jsby changing
if (!sha1) { throw new ReferenceError( `SHA-1 for file ${filename} (${resolvedPath}) is not computed`, ); }to
if (!sha1) { return getFileHash(resolvedPath) function getFileHash(file) { return require('crypto') .createHash('sha1') .update(fs.readFileSync(file)) .digest('hex') } }Step 3
You can then create a bundle using the command:
react-native bundle --platform web --dev false --entry-file index.js --bundle-output [path-to-output]/bundle.js --assets-dest [path-to-output] --config metroWeb.config.js
Hi.
I test this solution but create the bundle command return me this error:
Unable to resolve module ./Libraries/Components/AccessibilityInfo/AccessibilityInfo from node_modules/react-native/index.js:
I see that in this folder(./Libraries/Components/AccessibilityInfo) we just have AccessibilityInfo.android.js and AccessibilityInfo.ios.js .
Is there any solution?
edit:
this is output of npx react-native info:
info Fetching system and libraries information...
System:
OS: macOS 11.1
CPU: (4) x64 Intel(R) Core(TM) i5-5350U CPU @ 1.80GHz
Memory: 57.63 MB / 8.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 15.2.1 - /usr/local/bin/node
Yarn: Not Found
npm: 7.4.3 - ~/Desktop/saman-authenticator/node_modules/.bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Managers:
CocoaPods: 1.10.0 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: iOS 14.2, DriverKit 20.0, macOS 11.0, tvOS 14.2, watchOS 7.1
Android SDK:
API Levels: 29
Build Tools: 28.0.3, 29.0.2
System Images: android-29 | Intel x86 Atom_64, android-29 | Google Play Intel x86 Atom
Android NDK: Not Found
IDEs:
Android Studio: 4.1 AI-201.8743.12.41.6953283
Xcode: 12.2/12B45b - /usr/bin/xcodebuild
Languages:
Java: 1.8.0_275 - /usr/bin/javac
Python: 2.7.16 - /usr/bin/python
npmPackages:
@react-native-community/cli: Not Found
react: ^16.13.1 => 16.13.1
react-native: ^0.63.4 => 0.63.4
react-native-macos: Not Found
npmGlobalPackages:
react-native: Not Found
@samaneh-kamalian I just tested this locally with react-native 0.64.0 & react-native-web 0.15.7 and it works. The only change I made was replacing
const blacklist = require('metro-config/src/defaults/blacklist');
with
const blacklist = require('metro-config/src/defaults/exclusionList');
Check these links out, they may help
https://microsoft.github.io/react-native-windows/docs/next/metro-config-out-tree-platforms
https://github.com/microsoft/react-native-windows/issues/5965
Thanks for your reply.
In Diagnosing metro config issues section of this doc, show that there is not any solution for my problem!
Most helpful comment
I managed to get Metro bundling react-native-web with the following steps:
Add the following metro.config.js to the root project directory, which is based off the react-native-windows config (https://github.com/microsoft/react-native-windows/blob/3bab85d89f4b01adc59b26a3e523fc4fe6b4ac93/vnext/local-cli/generator-windows/templates/metro.config.js)
Edit the function getSha1 in node_modules\metro\src\node-haste\DependencyGraph.js to match the following, as described in https://github.com/facebook/metro/issues/330
You can then create a bundle using the command
react-native bundle --platform web --dev false --entry-file index.js --bundle-output <path-to-output>/web.bundle.js --assets-dest <path-to-output>/assetsEdit:
Apparently I hadn't tested running the android build after these changes. To get other builds working:
--config ../../../../metroWeb.config.jsto the react-native bundle command