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实现:
- 安装
rollup-plugin-inject - 在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
相关产品推荐
相关产品推荐

