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

NextJS+Antd在Sunmi D2设备WebView中语法错误及样式失效问题

问题描述

在React Native WebView中运行NextJS+Antd项目时,在Sunmi D2设备(Android 11系统,WebView内核为Chrome 83.0.4103.120)上出现两个问题:

  1. 控制台报错Uncaught SyntaxError: Unexpected token '('(来自_app-455-3437e82bbe2dfd49.js:1)
  2. Antd组件无样式,仅显示原始元素

已尝试在package.json中配置"browserslist": ["chrome 83"],但问题仍未解决。


环境信息

  • WebView User Agent: Mozilla/5.0 (Linux; Android 11; D2_2nd_STGL Build/942; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.120 Safari/537.36
  • 运行设备:Sunmi D2

相关配置文件

package.json

{
  "name": "mona-fnb",
  "version": "0.1.0",
  "private": true,
  "browserslist": [
    "chrome 83"
  ],
  "scripts": {
    "dev": "next dev -p 3030",
    "build": "next build",
    "start": "next start  -H 192.168.3.121",
    "prd-start": "bun run build && bun start",
    "lint": "next lint",
    "export": "next build",
    "host": "next dev -p 3030 -H 192.168.3.121",
    "export:live": "env-cmd -f .env.live yarn build && yarn export",
    "export:mona": "env-cmd -f .env.mona yarn build && yarn export"
  },
  "dependencies": {
    "@ant-design/cssinjs": "^1.21.1",
    "@dnd-kit/core": "^6.1.0",
    "@dnd-kit/sortable": "^9.0.0",
    "@microsoft/signalr": "^8.0.7",
    "@nivo/bar": "^0.88.0",
    "@nivo/boxplot": "^0.88.0",
    "@nivo/core": "^0.88.0",
    "@nivo/line": "^0.88.0",
    "@nivo/pie": "^0.88.0",
    "@number-flow/react": "^0.4.2",
    "@react-google-maps/api": "^2.20.3",
    "@studio-freight/lenis": "^1.0.42",
    "@studio-freight/react-lenis": "^0.0.47",
    "@tanstack/react-query": "^5.59.8",
    "@tinymce/tinymce-react": "^6.0.0",
    "@types/tinymce": "^4.6.9",
    "antd": "^5.21.3",
    "axios": "^1.7.7",
    "clsx": "^2.1.1",
    "date-fns": "^4.1.0",
    "dayjs": "^1.11.13",
    "devtools-detector": "2.0.22",
    "gsap": "^3.12.5",
    "html2pdf.js": "^0.10.2",
    "html5-qrcode": "2.2.7",
    "js-cookie": "^3.0.5",
    "jszpl": "^1.1.8",
    "jwt-decode": "^4.0.0",
    "leaflet": "^1.9.4",
    "leaflet-geosearch": "^4.0.0",
    "lodash": "^4.17.21",
    "next": "14.2.13",
    "nprogress": "^0.2.0",
    "react": "^18",
    "react-confetti": "^6.1.0",
    "react-date-range": "^2.0.1",
    "react-dnd": "^16.0.1",
    "react-dnd-html5-backend": "^16.0.1",
    "react-dom": "^18",
    "react-grid-layout": "^1.5.0",
    "react-icons": "^5.4.0",
    "react-intersection-observer": "^9.13.1",
    "react-leaflet": "^4.2.1",
    "react-lottie-player": "^2.1.0",
    "react-player": "^2.16.0",
    "react-thermal-printer": "^0.19.3",
    "react-to-print": "^3.0.5",
    "react-zoom-pan-pinch": "^3.4.4",
    "sass": "^1.79.4",
    "sharp": "^0.33.5",
    "uuid": "^10.0.0",
    "zustand": "4.5.4"
  },
  "devDependencies": {
    "@tanstack/eslint-plugin-query": "^5.59.7",
    "@tanstack/react-query-devtools": "^5.59.8",
    "@types/js-cookie": "^3.0.6",
    "@types/leaflet": "^1.9.14",
    "@types/lodash": "^4.17.13",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-date-range": "^1.4.9",
    "@types/react-dom": "^18",
    "@types/uuid": "^10.0.0",
    "@types/w3c-web-usb": "^1.0.10",
    "@types/web-bluetooth": "^0.0.21",
    "autoprefixer": "^10.4.20",
    "env-cmd": "^10.1.0",
    "eslint": "^8",
    "eslint-config-next": "14.2.13",
    "postcss": "^8",
    "tailwind-merge": "^2.5.4",
    "tailwindcss": "^3.4.1",
    "typescript": "^5"
  }
}

_document.tsx

import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";
import type { DocumentContext } from "next/document";
import Document, { Head, Html, Main, NextScript } from "next/document";

const MyDocument = () => (
  <Html lang="en">
    <Head />
    <body>
      <Main />
      <NextScript />
    </body>
    <div id="root-printer"></div>
  </Html>
);

MyDocument.getInitialProps = async (ctx: DocumentContext) => {
  const cache = createCache();
  const originalRenderPage = ctx.renderPage;
  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) =>
        (
          <StyleProvider cache={cache}>
            <App {...props} />
          </StyleProvider>
        ),
    });

  const initialProps = await Document.getInitialProps(ctx);
  const style = extractStyle(cache, true);

  return {
    ...initialProps,
    styles: (
      <>
        {initialProps.styles}
        <style dangerouslySetInnerHTML={{ __html: style }} />{" "}
      </>
    ),
  };
};

export default MyDocument;

React Native Webview.tsx

import React, {useEffect, useRef} from 'react';
import {
  ActivityIndicator,
  BackHandler,
  Dimensions,
  StyleSheet,
  View,
} from 'react-native';
import {WebView} from 'react-native-webview';

interface WebViewComponentProps {
  uri: string;
}

const INJECTED_JS =
  'window.ReactNativeWebView.postMessage(navigator.userAgent);';

const WebViewComponent: React.FC<WebViewComponentProps> = ({uri}) => {
  const webViewRef = useRef<any>(null);

  useEffect(() => {
    const handleBackPress = () => {
      if (webViewRef.current) {
        webViewRef.current?.goBack();
        return true;
      }
      return false;
    };

    const backHandler = BackHandler.addEventListener(
      'hardwareBackPress',
      handleBackPress,
    );

    return () => {
      backHandler.remove();
    };
  }, []);

  const renderLoadingView = () => (
    <View style={styles.loadingContainer}>
      <ActivityIndicator size="large" color="#0000ff" />
    </View>
  );

  const handleError = () => {
    console.error('WebView failed to load');
  };

  return (
    <WebView
      source={{uri}}
      style={styles.container}
      onError={handleError}
      androidLayerType="software"
      javaScriptEnabled={true}
      domStorageEnabled={true}
      startInLoadingState={false}
      scalesPageToFit={true}
      injectedJavaScript={INJECTED_JS}
      onMessage={event => console.log('User Agent:', event.nativeEvent.data)}
    />
  );
};
const deviceWidth = Dimensions.get('window').width;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    width: deviceWidth,
  },
  loadingContainer: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: 'red',
  },
});

export default WebViewComponent;

解决方案

一、解决语法错误(Unexpected token '(')

Chrome 83不支持部分ES6+语法,仅靠browserslist无法完全覆盖依赖包的编译降级,需手动配置Next.js的编译规则:

  1. 创建/修改next.config.js,添加以下配置:
/** @type {import('next').NextConfig} */
const nextConfig = {
  transpilePackages: ['antd', '@ant-design/cssinjs'],
  webpack: (config, { isServer }) => {
    config.module.rules.push({
      test: /\.(js|jsx|ts|tsx)$/,
      exclude: /node_modules\/(?!(antd|@ant-design)\/)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: [
            ['next/babel', {
              'preset-env': {
                targets: { chrome: '83' },
                useBuiltIns: 'usage',
                corejs: 3,
              }
            }]
          ]
        }
      }
    });
    return config;
  },
}

module.exports = nextConfig;
  1. 安装必要依赖:
npm install core-js@3 babel-loader @babel/core --save-dev
  1. 重新构建项目:
npm run build

注意:开发环境next dev不会完全应用降级编译,必须用生产构建测试。


二、解决Antd样式失效问题

Antd v5依赖cssinjs动态生成样式,WebView环境下需确保样式注入逻辑正确:

  1. 修复_document.tsx的DOM结构错误:
    将div#root-printer移到body标签内部(原代码中放在body外会导致DOM解析异常):
import { createCache, extractStyle, StyleProvider } from "@ant-design/cssinjs";
import type { DocumentContext } from "next/document";
import Document, { Head, Html, Main, NextScript } from "next/document";

const MyDocument = () => (
  <Html lang="en">
    <Head />
    <body>
      <Main />
      <NextScript />
      <div id="root-printer"></div>
    </body>
  </Html>
);

MyDocument.getInitialProps = async (ctx: DocumentContext) => {
  const cache = createCache();
  const originalRenderPage = ctx.renderPage;
  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => (
        <StyleProvider cache={cache}>
          <App {...props} />
        </StyleProvider>
      ),
    });

  const initialProps = await Document.getInitialProps(ctx);
  const style = extractStyle(cache, true);

  return {
    ...initialProps,
    styles: (
      <>
        {initialProps.styles}
        <style dangerouslySetInnerHTML={{ __html: style }} />
      </>
    ),
  };
};

export default MyDocument;
  1. 在_app.tsx中导入Antd全局重置样式:
import 'antd/dist/reset.css';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}
  1. 确认WebView配置:
    保留现有WebView的androidLayerType="software"、javaScriptEnabled={true}、domStorageEnabled={true}配置,这些是样式动态生成的必要条件。

额外排查步骤

  1. 验证User Agent传递:在_app.tsx中打印navigator.userAgent,确认Next.js能识别到Chrome 83的环境。
  2. 简化测试:创建仅包含基础Antd组件的页面,排除其他依赖的干扰,确认问题是否由特定包导致。

内容的提问来源于stack exchange,提问作者ANH TỪ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 20:24:58