跨端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实现:
- 项目根目录创建
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>; };
- 添加
react-native-view-shot.jsMock:
// web-mocks/react-native-view-shot.js export const captureRef = async (ref, options) => { // Web端可使用html2canvas实现真实截图逻辑,此处返回Mock URI return Promise.resolve('web-screenshot-uri'); };
- 修改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模块:
- 安装依赖:
npm install @expo/webpack-config --save-dev - 创建
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
相关产品推荐
相关产品推荐

