You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

跨端React项目动态加载组件:web端expo-barcode-generator解析失败

问题:Expo Web端解析expo-barcode-generator等RN专属库失败

项目背景

我有一个移动端与Web端集成在同一仓库的React项目,通过环境变量动态加载对应端组件,移动端运行正常,但Web端启动时出现模块解析错误。

动态加载组件的核心代码:

import React from "react";
class Sheet extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      componentLoaded: false,
      Component: null,
    };
  }
  componentDidMount() {
    if (!!process.env.EXPO_PUBLIC_PROJECT_ROOT) {
      this.setState({ Component: require('./mobile').default, componentLoaded: true });
    } else {
      this.setState({ Component: require('./web').default, componentLoaded: true });
    }
  };
  render = () => {
    if (!this.state.componentLoaded) {
      return null; // 或加载指示器,如<div>Loading...</div>
    }
    return React.createElement(this.state.Component, this.props);
  };
}
export default Sheet;

错误信息

Module parse failed: Unexpected token (44:4)
| return (
<Svg
x={0}
y={0}

涉及代码

包含react-native-view-shot的打印逻辑

import * as Print from 'expo-print';
import * as FileSystem from 'expo-file-system';
import { captureRef } from 'react-native-view-shot';

import ProductionOrderSheet from "../../ProductionOrderSheet"

const printSheet = async (viewRef) => {
    try {
        const uri = await captureRef(viewRef, {
            format: "png",
            quality: 1.0
        })
        // 后续逻辑省略
    } catch (err) {
        console.error(err);
    }
};

const Sheet = (props) => {
    const { appContext, type, sheet } = props;
    
    switch (type) {
        case "productionOrderSheet":
            return (
                <ProductionOrderSheet
                    {...sheet}
                    type={"productionOrderSheet"}
                    printSheet={printSheet}
                    appContext={appContext}
                />
            );

        default:
            throw new Error(`(Sheet) the sheet type ${type} not found`);
    }
}

export default Sheet

包含expo-barcode-generator的移动端组件

import Table from "../../Table";
import Button from '../../Button';
import Container from "../../Container"
import TextFormatter from "../../TextFormatter"
import { Barcode } from 'expo-barcode-generator';

const MobileProductionOrderSheet = (props) => {
    const { productionLot } = props;
    
    const getHeader = () => {
        return (
            <Container>
                <Container>
                    <Barcode
                        value={productionLot}
                        options={{ format: 'CODE128', background: 'white', height: 70, fontSize: 18 }}
                    />
                </Container>
            </Container>
        )
    }
    // 组件其他逻辑省略
}

export default MobileProductionOrderSheet

package.json配置

{
  "name": "@monorepo/appmobile",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "expo start",
    "android": "expo run:android",
    "ios": "expo run:ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@apollo/client": "^3.10.8",
    "@expo/ngrok": "^4.1.3",
    "@fortawesome/fontawesome-svg-core": "^6.5.2",
    "@fortawesome/free-solid-svg-icons": "^6.5.2",
    "@fortawesome/react-native-fontawesome": "^0.3.2",
    "@react-native-community/datetimepicker": "8.0.1",
    "@react-native-picker/picker": "2.7.5",
    "@react-navigation/drawer": "^6.6.15",
    "@react-navigation/material-top-tabs": "^6.6.13",
    "@react-navigation/native": "^6.1.17",
    "@react-navigation/native-stack": "^6.10.0",
    "core-js": "^3.37.1",
    "expo": "~51.0.21",
    "expo-barcode-generator": "^3.0.2",
    "expo-navigation-bar": "^3.0.6",
    "expo-print": "^13.0.1",
    "expo-screen-orientation": "~7.0.5",
    "expo-secure-store": "~13.0.2",
    "expo-status-bar": "~1.12.1",
    "graphql": "^16.9.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-native": "0.74.3",
    "react-native-gesture-handler": "~2.16.1",
    "react-native-media-query": "^2.0.1",
    "react-native-pager-view": "6.3.0",
    "react-native-reanimated": "~3.10.1",
    "react-native-safe-area-context": "4.10.5",
    "react-native-screens": "3.31.1",
    "react-native-svg": "15.2.0",
    "react-native-tab-view": "^3.5.2",
    "react-native-vector-icons": "^10.1.0",
    "react-native-view-shot": "^3.8.0",
    "react-native-web": "~0.19.10",
    "socket.io-client": "^4.7.5"
  },
  "devDependencies": {
    "@babel/core": "7.19.6",
    "@types/react-native": "^0.73.0"
  },
  "private": true
}

已尝试无效方案

  • 注释expo-barcode-generator和react-native-view-shot的导入:Web端正常但移动端无法使用对应组件
  • 注释移动端条件导入:Web端正常但移动端无法加载组件
  • 对库使用动态require:
    if (!!process.env.EXPO_PUBLIC_PROJECT_ROOT) {
        const Barcode = require('expo-barcode-generator');
    }
    
    无效果

可行解决方法

方案1:使用Expo Platform模块+ES动态导入(推荐)

利用Expo的Platform模块精准区分平台,结合ES动态导入确保Web打包时完全不引入RN专属库:

import React, { useState, useEffect } from "react";
import { Platform } from 'react-native';

const Sheet = (props) => {
  const [Component, setComponent] = useState(null);

  useEffect(() => {
    const loadComponent = async () => {
      const componentModule = Platform.OS === 'web' 
        ? await import('./web') 
        : await import('./mobile');
      setComponent(componentModule.default);
    };

    loadComponent();
  }, []);

  if (!Component) {
    return <div>Loading...</div>;
  }

  return <Component {...props} />;
};

export default Sheet;

方案2:为Web端提供Mock组件/库

在Web环境下将RN专属库替换为Mock实现:

  1. 项目根目录创建web-mocks文件夹,添加expo-barcode-generator.js:
// web-mocks/expo-barcode-generator.js
export const Barcode = ({ value, options }) => {
  // 可使用web兼容的条码库(如jsbarcode)实现完整功能,此处为简化示例
  return <div style={{padding: '10px', border: '1px solid #ccc'}}>{value}</div>;
};
  1. 添加react-native-view-shot.js Mock:
// web-mocks/react-native-view-shot.js
export const captureRef = async (ref, options) => {
  // Web端可使用html2canvas实现真实截图逻辑,此处返回Mock URI
  return Promise.resolve('web-screenshot-uri');
};
  1. 修改babel.config.js,在Web环境下重定向导入:
module.exports = function(api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    env: {
      web: {
        plugins: [
          [
            'module-resolver',
            {
              alias: {
                'expo-barcode-generator': './web-mocks/expo-barcode-generator.js',
                'react-native-view-shot': './web-mocks/react-native-view-shot.js'
              }
            }
          ]
        ]
      }
    }
  };
};

方案3:调整Webpack配置排除RN专属库

修改Expo Web的Webpack配置,打包时忽略不兼容的RN模块:

  1. 安装依赖:npm install @expo/webpack-config --save-dev
  2. 创建webpack.config.js:
const createExpoWebpackConfigAsync = require('@expo/webpack-config');

module.exports = async function(env, argv) {
  const config = await createExpoWebpackConfigAsync(env, argv);
  
  // 配置fallback忽略RN专属库
  config.resolve.fallback = {
    ...config.resolve.fallback,
    'expo-barcode-generator': false,
    'react-native-view-shot': false
  };

  return config;
};

注意:此方案需配合条件渲染,确保Web端代码不调用这些库的API。

内容的提问来源于stack exchange,提问作者Denis Valone

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 02:37:11