Skip to content

Vite 工程源码子应用接入自定义页面示例

INFO

vite工程本地开发(dev / start)和最终运行(build)产物不同,只有最终运行(build)之后的产物能够以 es module 方式嵌入。

示例工程(github)

<bookmark name="GitHub - Lovrabet/sub-app-vite-demo: 在lovrabet体系下使用源码页面的例子(vite工程)" href="https://github.com/Lovrabet/sub-app-vite-demo/tree/main"></bookmark>

  • 开发:执行pnpm run start,可以完全以独立应用方式开发

    • 由于vite工程开发/构建方式不同,源码嵌入无法直接使用开发状态的产物,建议先以独立应用方式完成开发,再调试嵌入
  • 构建:执行pnpm run build,构建产物可同时用于独立使用和微应用嵌入(即“资源加载列表”)

  • 调试源码嵌入:执行pnpm run build产出构建产物后,再执行pnpm run preview启动本地服务,即可直接访问本地的构建产物。将本地产物填入“资源加载列表”或使用代理工具,都可以实现调试

主要改造点说明

【重要】vite 构建配置修改以及平台生成接口的调用 代码示例

本地开发需要解决解决跨域问题:

TypeScript
本地浏览器 (https://dev.yuntooai.com:5173)

直接发送fetch请求(带CORS headers)

API服务器 (https://api.yuntooai.com)

返回响应(允许跨域)

浏览器接收数据

具体配置如下:

JavaScript
import { defineConfig, loadEnv } from "vite";
import { dirname } from "node:path";
import { fileURLToPath } from "node:url";
import react from "@vitejs/plugin-react";
import htmlPlugin from "vite-plugin-index-html";
import pluginExternal from "vite-plugin-external";
import pkgJson from "./package.json";

const version = pkgJson.version;
const appName = pkgJson.name.split("/").pop();
const __dirname = dirname(fileURLToPath(import.meta.url));

// https://vitejs.dev/config/
export default defineConfig(async ({ mode }) => {
  const env = loadEnv(mode, __dirname);
  const PORT = Number(env.VITE_APP_PORT) || 5173;
  const isCdn = Boolean(process.env.CDN_DOMAIN);
  const outDir = isCdn ? `dist/${appName}/${version}` : "dist";
  const base = isCdn ? `${process.env.CDN_DOMAIN}${outDir}/` : "/";

  return {
    base,
    envDir: __dirname,
    plugins: [
      react(),
      // 关键配置:提供 vite lib 打包 + html plugin 能力
      htmlPlugin({
        input: "src/main.tsx",
        preserveEntrySignatures: "exports-only",
      }),
      pluginExternal({
        externals: {
          react: "React",
          "react-dom": "ReactDOM",
          antd: "antd",
          dayjs: "dayjs",
        },
      }),
    ],
    resolve: {
      alias: {
        "@": "/src",
      },
    },
    // 可选配置:提供https自签名证书及跨域访问能力
    // 因为接口域名为 api.yuntooai.com 存在跨域,服务端配置了允许 dev.yuntooai.com 的跨域请求,从而实现本地开发能够正常请求接口
    // 这些配置不是必须的,你也可以使用 proxy 等任意手段自行处理跨域问题
    server: {
      port: PORT,
      open: `https://dev.yuntooai.com:${PORT}`,
      strictPort: true,
      host: "dev.yuntooai.com",
      https: await (await fetch("https://g.yuntooai.com/cert/dev.json")).json(),
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods":
          "GET, POST, PUT, DELETE, PATCH, OPTIONS",
        "Access-Control-Allow-Headers":
          "X-Requested-With, Content-Type, Authorization",
      },
    },
    build: {
      outDir,
      target: "esnext",
      rollupOptions: {
        output: {
          format: "es",
          entryFileNames: `assets/[name].js`,
          assetFileNames: `assets/[name].css`,
        },
      },
    },
    optimizeDeps: {
      include: ["react", "react-dom", "antd", "dayjs"],
    },
  };
});

【重要】入口改造 代码示例

  • 暴露 mount unmount,供微应用加载和销毁时使用
  • 根据 isInIcestark() 判断当前的运行环境,可同时兼容独立使用和嵌入使用
TypeScript
import React from "react";
import { createRoot } from "react-dom/client";
import { isInIcestark } from "@ice/stark-app";
import { ConfigProvider } from "antd";
import zhCN from "antd/locale/zh_CN";
import App from "./router";
import "./style.css";

// 可选:根据 isInIcestark() 判断当前的运行环境,可同时兼容独立使用和嵌入使用
if (!isInIcestark()) {
  const container = document.getElementById("root");
  if (container) {
    const root = createRoot(container);
    root.render(
      <ConfigProvider locale={zhCN}>
        <App />
      </ConfigProvider>,
    );
  }
}

// 关键:暴露 mount 供主应用加载时调用
export function mount({
  container,
  customProps,
}: {
  container: HTMLElement;
  customProps: object;
}) {
  const root = createRoot(container);
  root.render(
    <React.StrictMode>
      <ConfigProvider locale={zhCN}>
        <App {...customProps} />
      </ConfigProvider>
    </React.StrictMode>,
  );
  return root;
}

// 关键:暴露 unmount 供主应用卸载时调用
export function unmount({ container }: { container: HTMLElement }) {
  // React 18 中不再需要手动卸载,但为了兼容性保留
  const root = (container as any)._reactRoot;
  if (root) {
    root.unmount();
  }
}

【可选】前端路由适配 代码示例

  • 通过getBasename()获取到微应用运行时的basename并传入
JavaScript
const router = createBrowserRouter(
  [
    {
      path: "/",
      element: <MainLayout />,
      children: [
        {
          index: true,
          element: <Home />,
        },
        {
          path: "about",
          element: <About />,
        },
        {
          path: "settings",
          element: <Settings />,
        },
      ],
    },
  ],
  {
    basename: getBasename() || "/",
  }
);

【可选】布局适配 代码示例

  • 根据isInIcestark()判断当前运行环境,被嵌入时,不渲染layout布局
TypeScript
import React from "react";
import { isInIcestark } from "@ice/stark-app";
import { Outlet, useNavigate, useLocation } from "react-router";
import { Layout, Menu } from "antd";

const MainLayout: React.FC = () => {
  
  // 可选:根据isInIcestark()判断当前运行环境,被嵌入时,不渲染layout布局
  if (isInIcestark()) {
    return (
      <div style={{ padding: "16px 20px" }}>
        <Outlet />
      </div>
    );
  }
    
  return (
    <Layout>
      .....
    </Layout>
  );
};

export default MainLayout;

【可选】请求 lovrabet 平台已有接口

  • 页面中使用 fetch 请求时,会自动带上 CORS headers,从而允许跨域访问
TypeScript
// 简单封装 apiRequest
const apiRequest = async (path, options = {}) => {
  const response = await fetch(`https://api.yuntooai.com${path}`, {
    credentials: 'include', // credentials: 'include' - 关键配置:跨域请求携带Cookie
    headers: {
      'Content-Type': 'application/json',
      ...options.headers,
    },
    ...options,
  });
  return response.json();
};


// 页面中实际使用文生应用的接口
const data = await apiRequest('/smartapi/runtime/yuntoo/app-f4c03acb/9c1bfbd319174461b6b58d19d7bf040f/getList', {
  method: 'POST',
  body: {"pageSize":10,"currentPage":1}
});
// 页面中实际使用DB生成应用的接口
const data = await apiRequest('/dbapi/runtime/yuntoo/app-f4c03acb/b0a8936ffd2542499da5a165a09cc078/getList', {
  method: 'POST',
  body: {"pageSize":10,"currentPage":1}
});

查看更多页面示例 DEMO

将 github 工程切换到 crm-demo 分支,即可查看 dashboard、客户360 等更丰富的示例

<bookmark name="GitHub - Lovrabet/sub-app-vite-demo at crm-demo" href="https://github.com/Lovrabet/sub-app-vite-demo/tree/crm-demo"></bookmark>

<grid> <column width-ratio="0.500000"> 图片展示的是Vite工程源码子应用接入自定义页面示例中CRM应用的界面… </column> <column width-ratio="0.500000"> 图片展示的是Vite工程源码子应用接入自定义页面示例中Loverabet CRM系统的界面… </column> </grid>

基于飞书知识库同步生成,内容以飞书源文档为准