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

React项目从Webpack迁移至Vite后页面无响应的配置问题

问题

尝试将React Office插件从Webpack迁移到Vite,编译成功但访问localhost:3000页面无响应。以下是原Webpack配置和当前Vite配置:

原Webpack配置

/* eslint-disable no-undef */

const devCerts = require("office-addin-dev-certs");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
const webpack = require("webpack");
const DotenvWebpackPlugin = require("dotenv-webpack");
const TerserPlugin = require("terser-webpack-plugin");

const urlDev = "localhost:3000/";

async function getHttpsOptions() {
  const httpsOptions = await devCerts.getHttpsServerOptions();
  return { ca: httpsOptions.ca, key: httpsOptions.key, cert: httpsOptions.cert };
}

module.exports = async (env, options) => {
  const dev = options.mode === "development";
  const prod = options.mode === "production";
  const config = {
    cache: false,
    devtool: "source-map",
    entry: {
      polyfill: ["core-js/stable", "regenerator-runtime/runtime"],
      vendor: ["react", "react-dom", "core-js", "@fluentui/react"],
      taskpane: ["react-hot-loader/patch", "./src/taskpane/index.tsx", "./src/taskpane/taskpane.html"],
      loginfile: "./src/login/loginfile.ts",
      logoutfile: "./src/logout/logoutfile.ts",
    },
    output: {
      clean: true,
      publicPath: "/",
    },

    resolve: {
      extensions: [".ts", ".tsx", ".html", ".js"],
    },
    module: {
      rules: [
        {
          test: /\.css$/i,
          include: path.resolve(__dirname, "src"),
          use: ["style-loader", "css-loader", "postcss-loader"],
        },
        {
          test: /\.ts$/,
          exclude: /node_modules/,
          use: {
            loader: "babel-loader",
            options: {
              presets: ["@babel/preset-typescript"],
            },
          },
        },
        {
          test: /\.tsx?$/,
          exclude: /node_modules/,
          use: ["react-hot-loader/webpack", "ts-loader"],
        },
        {
          test: /\.html$/,
          exclude: /node_modules/,
          use: "html-loader",
        },
        {
          test: /\.(png|jpg|jpeg|gif|ico)$/,
          type: "asset/resource",
          generator: {
            filename: "assets/[name][ext][query]",
          },
        },
      ],
    },
    plugins: [
      new DotenvWebpackPlugin({ path: `.env.${options.mode}` }),
      new CopyWebpackPlugin({
        patterns: [
          {
            from: "assets/*",
            to: "assets/[name][ext][query]",
          },
          {
            from: "manifest*.xml",
            to: "[name]" + "[ext]",
            transform(content) {
              if (dev) {
                return content;
              } else {
                return content.toString().replace(new RegExp(urlDev, "g"), urlProd);
              }
            },
          },
        ],
      }),
      new HtmlWebpackPlugin({
        filename: "taskpane.html",
        template: "./src/taskpane/taskpane.html",
        chunks: ["taskpane", "vendor", "polyfills"],
      }),
      new HtmlWebpackPlugin({
        filename: "loginfile.html",
        template: "./src/login/loginfile.html",
        chunks: ["loginfile"],
      }),
      new HtmlWebpackPlugin({
        filename: "redirect.html",
        template: "./src/redirectPage/redirect.html",
        chunks: ["redirect"],
      }),
      new HtmlWebpackPlugin({
        filename: "logoutfile.html",
        template: "./src/logout/logoutfile.html",
        chunks: ["logoutfile"],
      }),
      new HtmlWebpackPlugin({
        filename: "endlogout.html",
        template: "./src/taskpane/endlogout.html",
        chunks: ["login", "vendor", "polyfills"],
      }),
      new HtmlWebpackPlugin({
        filename: "privacy.html",
        template: "./src/privacy.html",
        chunks: ["privacy"],
      }),
      new webpack.ProvidePlugin({
        Promise: ["es6-promise", "Promise"],
      }),
    ],
    devServer: {
      https: await devCerts.getHttpsServerOptions(),
      hot: true,
      headers: {
        "Access-Control-Allow-Origin": "*",
      },
      server: {
        type: "https",
        options: env.WEBPACK_BUILD || options.https !== undefined ? options.https : await getHttpsOptions(),
      },
      port: process.env.npm_package_config_dev_server_port || 3000,
    },
  };

  return config;
};

当前Vite配置

import { defineConfig } from "vite";
import reactRefresh from "@vitejs/plugin-react-refresh";
import { resolve } from "path";
import copy from "rollup-plugin-copy";
import html from "rollup-plugin-html";
import dotenv from "rollup-plugin-dotenv";
import { terser } from "rollup-plugin-terser";
import autoprefixer from "autoprefixer";

// Define HTTPS options
// Note: Vite does not support custom HTTPS options out of the box. You may need to use a reverse proxy for this.

// Define config
export default defineConfig(({ command, mode }) => {
  const isDev = mode === "development";
  const isProd = mode === "production";

  return {
    plugins: [
      reactRefresh(),
      copy({
        targets: [
          { src: "assets/*", dest: "dist/assets" },
          { src: "manifest*.xml", dest: "dist" },
        ],
        hook: "writeBundle", // notice here
      }),
      html({
        include: "**/*.html",
        htmlMinifierOptions: {
          collapseWhitespace: true,
        },
      }),
      dotenv({ path: `.env.${mode}` }),
      isProd && terser(),
    ],
    resolve: {
      alias: {
        "@": resolve(__dirname, "src"),
      },
    },
    css: {
      postcss: {
        plugins: autoprefixer({}),
      },
    },
    server: {
      host: true,
      port: process.env.npm_package_config_dev_server_port || 3000,
      strictPort: true,
      hmr: {
        overlay: false,
      },
    },
    build: {
      outDir: "dist",
      emptyOutDir: true,
      sourcemap: true,
    },
  };
});

排查与解决建议

1. 替换不兼容的Rollup插件,改用Vite原生能力

当前使用的rollup-plugin-html和rollup-plugin-dotenv会与Vite原生逻辑冲突,导致页面无法加载入口脚本:

  • 移除rollup-plugin-html:Vite默认支持HTML作为入口,无需额外插件处理。
  • 移除rollup-plugin-dotenv:Vite原生支持.env文件,只需将环境变量以VITE_前缀命名(如VITE_API_URL),即可在代码中通过import.meta.env.VITE_API_URL访问。

2. 配置多入口(匹配原Webpack多页面结构)

原Webpack是多页面应用,Vite默认单入口,需显式配置多入口:
在build.rollupOptions中添加input配置,对应原Webpack的entry:

build: {
  // ... 原有配置
  rollupOptions: {
    input: {
      taskpane: resolve(__dirname, 'src/taskpane/taskpane.html'),
      loginfile: resolve(__dirname, 'src/login/loginfile.html'),
      redirect: resolve(__dirname, 'src/redirectPage/redirect.html'),
      logoutfile: resolve(__dirname, 'src/logout/logoutfile.html'),
      endlogout: resolve(__dirname, 'src/taskpane/endlogout.html'),
      privacy: resolve(__dirname, 'src/privacy.html'),
    },
  },
}

同时确保每个HTML文件中正确引入对应TS/TSX入口脚本,比如taskpane.html中添加:

<script type="module" src="./index.tsx"></script>

3. 修复HTTPS配置(适配Office插件要求)

原Webpack使用office-addin-dev-certs生成证书,Vite可直接集成该工具,无需反向代理:

import devCerts from 'office-addin-dev-certs';

export default defineConfig(async ({ command, mode }) => {
  const httpsOptions = await devCerts.getHttpsServerOptions();
  const isDev = mode === "development";
  const isProd = mode === "production";

  return {
    // ... 其他配置
    server: {
      https: httpsOptions,
      headers: {
        'Access-Control-Allow-Origin': '*',
      },
      // ... 原有server配置
    },
  };
});

4. 处理全局依赖与Polyfill

原Webpack的ProvidePlugin全局注入Promise,Vite中可通过rollup-plugin-inject实现:

  1. 安装rollup-plugin-inject
  2. 在plugins中添加:
import inject from 'rollup-plugin-inject';

plugins: [
  // ... 其他插件
  inject({
    Promise: ['es6-promise', 'Promise'],
  }),
]

5. 调整文件复制逻辑

当前rollup-plugin-copy仅在生产构建时生效,开发模式下需添加watch确保文件同步,同时补全manifest的URL替换逻辑:

copy({
  targets: [
    { src: "assets/*", dest: "dist/assets" },
    { 
      src: "manifest*.xml", 
      dest: "dist",
      transform: (content) => {
        const urlDev = "localhost:3000/";
        const urlProd = "your-production-domain/"; // 替换为实际生产地址
        return isDev ? content : content.toString().replace(new RegExp(urlDev, "g"), urlProd);
      }
    },
  ],
  hook: command === 'build' ? "writeBundle" : "buildStart",
  watch: isDev && {
    include: ["assets/*", "manifest*.xml"],
  },
})

6. 检查React挂载逻辑

确保每个页面的TS/TSX入口脚本正确挂载到DOM节点,比如taskpane/index.tsx:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(<App />);

同时对应HTML文件中存在<div id="root"></div>节点。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 09:03:10