Webpack 编译在 {Node} 上的 react-native-web 中失败,即流语法说 "You may need an appropriate loader to handle this file type"

Webpack compilation failed in react-native-web on {Node} i.e. Flow Syntax saying "You may need an appropriate loader to handle this file type"

我是 node/npmreactreact-native 的新手,所以对 react-native-web 也是新手。我用了 3 天时间将 react-native-web 集成到使用 npx react-native init as per the doc 生成的 Hello World App 中。我尝试使用两种模板:有和没有打字稿,但到目前为止没有成功。我得到的最远的是 运行 在 index.web.js 中编写的应用程序代码但是如果我从 ./src/components/ 添加任何组件然后我得到错误,主要是 webpack.


我创建了一个测试仓库以便于重新生成错误,所以重现的步骤如下:

  1. 在您的系统中下载 this repo
  2. npm install
  3. npm run web

现在您将在终端中看到错误。

版本:


我遵循了官方文档,但使用了 webpack@^4 并参考了 this article 并以某种方式设法达到了以下情况:

工作index.web.js:

import React, {Component} from 'react';
import {AppRegistry, StyleSheet, Text, View} from 'react-native';

class ReactNativeWeb extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>Welcome to React Native!</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#142a3d',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
    color: '#ffffff',
  },
});

AppRegistry.registerComponent('ReactNativeWeb', () => ReactNativeWeb);
AppRegistry.runApplication('ReactNativeWeb', {
  rootTag: document.getElementById('react-native-web-app'),
});

新错误index.web.js:

import React from 'react';
import {AppRegistry} from 'react-native';
import App from './src/components/App';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

AppRegistry.runApplication(appName, {
  rootTag: document.getElementById('react-native-web-app'),
});

src/components/App.jsx

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 *
 * @format
 */

import React from 'react';
// import {Node} from 'react';
import {
  SafeAreaView,
  ScrollView,
  StatusBar,
  StyleSheet,
  Text,
  useColorScheme,
  View,
} from 'react-native';

import {
  Colors,
  DebugInstructions,
  Header,
  LearnMoreLinks,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

const Section = ({children, title}) => {
  const isDarkMode = useColorScheme() === 'dark';
  return (
    <View style={styles.sectionContainer}>
      <Text
        style={[
          styles.sectionTitle,
          {
            color: isDarkMode ? Colors.white : Colors.black,
          },
        ]}>
        {title}
      </Text>
      <Text
        style={[
          styles.sectionDescription,
          {
            color: isDarkMode ? Colors.light : Colors.dark,
          },
        ]}>
        {children}
      </Text>
    </View>
  );
};

const App = () => {
  const isDarkMode = useColorScheme() === 'dark';

  const backgroundStyle = {
    backgroundColor: isDarkMode ? Colors.darker : Colors.lighter,
  };

  return (
    <SafeAreaView style={backgroundStyle}>
      <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} />
      <ScrollView
        contentInsetAdjustmentBehavior="automatic"
        style={backgroundStyle}>
        <Header />
        <View
          style={{
            backgroundColor: isDarkMode ? Colors.black : Colors.white,
          }}>
          <Section title="Step One">
            Edit <Text style={styles.highlight}>App.js</Text> to change this
            screen and then come back to see your edits.
          </Section>
          <Section title="See Your Changes">
            <ReloadInstructions />
          </Section>
          <Section title="Debug">
            <DebugInstructions />
          </Section>
          <Section title="Learn More">
            Read the docs to discover what to do next:
          </Section>
          <LearnMoreLinks />
        </View>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  sectionContainer: {
    marginTop: 32,
    paddingHorizontal: 24,
  },
  sectionTitle: {
    fontSize: 24,
    fontWeight: '600',
  },
  sectionDescription: {
    marginTop: 8,
    fontSize: 18,
    fontWeight: '400',
  },
  highlight: {
    fontWeight: '700',
  },
});

export default App;

现在我得到以下错误,似乎与 babel 或 flow 相关的 webpack 加载器有关:

ERROR in ./node_modules/react-native/Libraries/NewAppScreen/components/DebugInstructions.js 11:12
Module parse failed: Unexpected token (11:12)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
|  */
|
> import type {Node} from 'react';
| import {Platform, StyleSheet, Text} from 'react-native';
| import React from 'react';
 @ ./node_modules/react-native/Libraries/NewAppScreen/index.js 17:0-63 20:0-27:2
 @ ./src/components/App.jsx
 @ ./index.web.js
 @ multi ./index.web.js

我所有的设置文件如下:

package.json

{
  "name": "awesomeproject2",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "react": "17.0.1",
    "react-dom": "^17.0.2",
    "react-native": "0.64.2",
    "react-native-web": "^0.16.5"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/preset-env": "^7.14.4",
    "@babel/preset-react": "^7.13.13",
    "@babel/runtime": "^7.12.5",
    "@react-native-community/eslint-config": "^2.0.0",
    "babel-jest": "^26.6.3",
    "babel-loader": "^8.2.2",
    "babel-plugin-module-resolver": "^4.1.0",
    "babel-plugin-react-native-web": "^0.16.5",
    "eslint": "7.14.0",
    "jest": "^26.6.3",
    "metro-react-native-babel-preset": "^0.64.0",
    "react-test-renderer": "17.0.1",
    "url-loader": "^4.1.1",
    "webpack": "^4.46.0",
    "webpack-cli": "^4.7.2",
    "webpack-dev-server": "^3.11.2"
  },
  "jest": {
    "preset": "react-native-web"
  }
}

web/webpack.config.js

const path = require('path');
const webpack = require('webpack');

const appDirectory = path.resolve(__dirname, '../');

// This is needed for webpack to compile JavaScript.
// Many OSS React Native packages are not compiled to ES5 before being
// published. If you depend on uncompiled packages they may cause webpack build
// errors. To fix this webpack can be configured to compile to the necessary
// `node_module`.
const babelLoaderConfiguration = {
  test: /(\.js)|(\.jsx)$/,
  // Add every directory that needs to be compiled by Babel during the build.
  include: [
    path.resolve(appDirectory, 'index.web.js'),
    path.resolve(appDirectory, 'src'),
    path.resolve(appDirectory, 'node_modules/react-native-uncompiled'),
  ],
  exclude: /node_modules/,
  use: {
    loader: 'babel-loader',
    options: {
      //   cacheDirectory: true,
      // The 'metro-react-native-babel-preset' preset is recommended to match React Native's packager
      presets: [
        'module:metro-react-native-babel-preset',
        '@babel/preset-env',
        '@babel/preset-react',
      ],
      // Re-write paths to import only the modules needed by the app
      plugins: ['react-native-web'],
    },
  },
};

// This is needed for webpack to import static images in JavaScript files.
const imageLoaderConfiguration = {
  test: /\.(gif|jpe?g|png|svg)$/,
  use: {
    loader: 'url-loader',
    options: {
      name: '[name].[ext]',
      esModule: false,
    },
  },
};

module.exports = {
  entry: [
    // load any web API polyfills
    // path.resolve(appDirectory, 'polyfills-web.js'),
    // your web-specific entry file
    path.resolve(appDirectory, 'index.web.js'),
  ],

  // configures where the build ends up
  output: {
    filename: 'bundle.web.js',
    path: path.resolve(appDirectory, 'dist'),
  },

  // ...the rest of your config

  module: {
    rules: [babelLoaderConfiguration, imageLoaderConfiguration],
  },

  resolve: {
    // This will only alias the exact import "react-native"
    alias: {
      'react-native$': 'react-native-web',
    },
    // If you're working on a multi-platform React Native app, web-specific
    // module implementations should be written in files using the extension
    // `.web.js`.
    extensions: ['.web.js', '.js', '.jsx', '.web.jsx'],
  },
};

.bashrc

{
  "plugins": [
    [
      "module-resolver",
      {
        "alias": {
          "^react-native$": "react-native-web"
        }
      }
    ]
  ]
}

.flowconfig(仅在 [options] 下面添加了部分)

# Alias the package name
module.name_mapper='^react-native$' -> 'react-native-web'

# Point flow to the 'module' field by default
module.system.node.main_field=module
module.system.node.main_field=main

babel.config.js

module.exports = {
  presets: [
    'module:metro-react-native-babel-preset',
    '@babel/preset-env',
    '@babel/preset-react',
  ],
};

终于解决了,我的Hello World搞定了

维护者本人 (@necolas) 在 this discussion 上的这 2 条回复帮助我解决了这个问题。

Reply 1 by @necolas This line in the stack trace is telling you that you're trying to bundle RN internal code in your web bundle: node_modules/react-native/Libraries/NewAppScreen/index.js.

First, you should not be loading any of the RN package on web, especially not parts that aren't part of the public API . Second, as mentioned in the inline comments of the config you pasted above, you need to explicitly list everything in node_modules that needs compiling.

Reply 2 by @necolas

import {
  Colors,
  DebugInstructions,
  Header,
  LearnMoreLinks,
  ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

All this code of the problem. You should not import from Libraries. This is not part of the public API. And you need to actually read the webpack code comments and follow the instructions when importing code from node_modules.

所以我从 'react-native/Libraries/NewAppScreen' 中删除了导入组件,仅此而已。 this commit.

中有更多详细信息

我添加了an article for setting up React Native for Web from Scratch