開發環境配置:
Node.js 版本 < 18.0.0
pnpm 腳手架示例模版基于 pnpm + turborepo 管理項目
如果您的當前環境中需要切換 node.js 版本, 可以使用 nvm or fnm 進行安裝.
以下是通過 nvm 或者nvs 安裝 Node.js 16 LTS 版本
nvs安裝教程 https://blog.csdn.net/glorydx/article/details/134056903
C:\>node -v
v20.18.1C:\>nvs use 16
Specified version not found.
To add this version now: nvs add node/16C:\>nvs add node/16
Extracting [###########################################################################################] 100%
Added at: %LOCALAPPDATA%\nvs\node\16.20.2\x64\node.exe
To use this version now: nvs use node/16.20.2/x64C:\>npx create-wujie@latest
Need to install the following packages:
create-wujie@0.4.0
Ok to proceed? (y) y📦 Welcome To Create Template for WuJie! V0.3.2
√ Project name: ... wujie-main
√ What framework do you choose as your main application ? ? Webpack + Vue2
√ Select the main application route pattern ? history
√ What framework do you choose as your sub application ? ? Vite, Vue2, Vue3, React16, React17
√ Select the sub application route pattern ? history
安裝完成以后,分別單獨啟動wujie的主應用,和子應用,記得將node的版本都統一設置為 16 這樣就可以正常體驗wujie官方提供的demo。
wujie代碼分析 vue2主應用
import "whatwg-fetch"; // fetch polyfill
import "custom-event-polyfill"; // CustomEvent polyfillimport Vue from "vue";
import App from "./App.vue";
import router from "./router";
import WujieVue from "wujie-vue2";
import hostMap from "../wujie-config/hostMap";
import credentialsFetch from "../wujie-config/fetch";
import Switch from "ant-design-vue/es/switch";
import Tooltip from "ant-design-vue/es/tooltip";
import button from "ant-design-vue/es/button/index";
import Icon from "ant-design-vue/es/icon/index";
import "ant-design-vue/es/button/style/index.css";
import "ant-design-vue/es/style/index.css";
import "ant-design-vue/es/switch/style/index.css";
import "ant-design-vue/es/tooltip/style/index.css";
import "ant-design-vue/es/icon/style/index.css";
import lifecycles from "../wujie-config/lifecycle";
import plugins from "../wujie-config/plugin";const isProduction = process.env.NODE_ENV === "production";
const { setupApp, preloadApp, bus } = WujieVue;
Vue.use(WujieVue).use(Switch).use(Tooltip).use(button).use(Icon);Vue.config.productionTip = false; // 關閉生產提示bus.$on("click", (msg) => window.alert(msg));// 在 xxx-sub 路由下子應用將激活路由同步給主應用,主應用跳轉對應路由高亮菜單欄
bus.$on("sub-route-change", (name, path) => {const mainName = `${name}-sub`;const mainPath = `/${name}-sub${path}`;const currentName = router.currentRoute.name;const currentPath = router.currentRoute.path;if (mainName === currentName && mainPath !== currentPath) {router.push({ path: mainPath });}
});// 根據瀏覽器的版本,如果不支持 Proxy,則降級 Object.defineProperty 如果不支持webcomponent 則降級iframe 理論上可以兼容到 IE 9
const degrade =window.localStorage.getItem("degrade") === "true" ||!window.Proxy ||!window.CustomElementRegistry;const props = {// 將主應用的router.push方法傳遞給子應用,這樣子應用就能通過獲取jump方法,來控制主應用的跳轉jump: (name) => {router.push({ name });},
};
/*** 大部分業務無需設置 attrs* 此處修正 iframe 的 src,是防止github pages csp報錯* 因為默認是只有 host+port,沒有攜帶路徑*/
const attrs = isProduction ? { src: hostMap("//localhost:8000/") } : {};
/*** 配置應用,主要是設置默認配置* preloadApp、startApp的配置會基于這個配置做覆蓋*/setupApp({name: "react16",url: hostMap("//localhost:7600/"),attrs,exec: true,props, // 給子應用傳遞的參數fetch: credentialsFetch,plugins,// prefix 用于改變子路徑過長,在主應用中進行替換prefix: { "prefix-dialog": "/dialog", "prefix-location": "/location" },degrade,...lifecycles,
});setupApp({name: "react17",url: hostMap("//localhost:7100/"),attrs,exec: true, //是否預先執行子應用alive: true, // 是否保存子應用的狀態props,fetch: credentialsFetch,degrade,...lifecycles,
});
setupApp({name: "vue2",url: hostMap("//localhost:6100/"),attrs,exec: true,props,fetch: credentialsFetch,degrade,...lifecycles,
});setupApp({name: "vue3",url: hostMap("//localhost:8082/"),attrs,exec: true,alive: true,plugins: [{cssExcludes: ["https://stackpath.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css",],},],props,// 引入了的第三方樣式不需要添加credentialsfetch: (url, options) =>url.includes(hostMap("//localhost:8082/"))? credentialsFetch(url, options): window.fetch(url, options),degrade,...lifecycles,
});
setupApp({name: "vite",url: hostMap("//localhost:8083/"),attrs,exec: true,props,fetch: credentialsFetch,degrade,...lifecycles,
});// 因為已經setupApp注冊了,這里可以簡寫,預加載只寫name就可以了
if (window.localStorage.getItem("preload") !== "false") {preloadApp({name: "react16",});preloadApp({name: "react17",});preloadApp({name: "vue2",});if (window.Proxy) {preloadApp({name: "vue3",});preloadApp({name: "vite",});}
}new Vue({router,render: (h) => h(App),
}).$mount("#app");
主應用中,用來顯示子應用的配置
wujieVue這個組件只要url一變化,在非保活模式下,子應用就會重新加載
<template><!--保活模式,name相同則復用一個子應用實例,改變url無效,必須采用通信的方式告知路由變化 --><WujieVue width="100%" height="100%" name="react17" :url="react17Url"></WujieVue>
</template><script>
import hostMap from "../../wujie-config/hostMap";
import wujieVue from "wujie-vue2"; // 引入wujie-vue2,如果是vue3請引入wujie-vue3 用來顯示子應用
export default {data() {return {react17Url: hostMap("//localhost:7100/") + this.$route.params.path, //hostMap區分開發環境和生產環境};},watch: {// 保活模式,name相同則復用一個子應用實例,改變url無效,必須采用通信的方式告知路由變化"$route.params.path": {handler: function () {wujieVue.bus.$emit("react17-router-change", `/${this.$route.params.path}`);},immediate: true,},},
};
</script><style lang="scss" scoped></style>
非保活模式的子應用在主應用中的配置
<template><!--單例模式,name相同則復用一個無界實例,改變url則子應用重新渲染實例到對應路由 --><WujieVue width="100%" height="100%" name="vite" :url="viteUrl"></WujieVue>
</template><script>
import hostMap from "../../wujie-config/hostMap";export default {// 如果是非保活模式,不需要watch這個$route.params.path變化,并去調用wujieVue.bus.$emitcomputed: {viteUrl() {return hostMap("//localhost:8083/") + this.$route.params.path;},},
};
</script><style lang="scss" scoped></style>
vue2主應用,路由的配置
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import Multiple from "../views/Multiple.vue";
import Vue2 from "../views/Vue2.vue";
import Vue2Sub from "../views/Vue2-sub.vue";
import Vue3 from "../views/Vue3.vue";
import Vue3Sub from "../views/Vue3-sub.vue";
import Vite from "../views/Vite.vue";
import ViteSub from "../views/Vite-sub.vue";
import React16 from "../views/React16.vue";
import React16Sub from "../views/React16-sub.vue";
import React17 from "../views/React17.vue";
import React17Sub from "../views/React17-sub.vue";const basename = process.env.NODE_ENV === "production" ? "/demo-main-vue/" : ""; // 區分不同環境下,資源所在的不同文件夾
Vue.use(VueRouter);const routes = [{path: "/all",name: "all",component: Multiple,},{path: "/",redirect: "/home",},{path: "/home",name: "home",component: Home,},{path: "/vue2",name: "vue2",component: Vue2,},{path: "/vue2-sub/:path",name: "vue2-sub",component: Vue2Sub,},{path: "/vue3",name: "vue3",component: Vue3,},{path: "/vue3-sub/:path",name: "vue3-sub",component: Vue3Sub,},{path: "/vite",name: "vite",component: Vite,},{path: "/vite-sub/:path",name: "vite-sub",component: ViteSub,},{path: "/react16",name: "react16",component: React16,},{path: "/react16-sub/:path",name: "react16-sub",component: React16Sub,},{path: "/react17",name: "react17",component: React17,},{path: "/react17-sub/:path",name: "react17-sub",component: React17Sub,},
];// 3. 創建路由實例并傳遞 `routes` 配置
// 你可以在這里輸入更多的配置const router = new VueRouter({mode: "history",base: basename,routes,
});export default router;
vue2主應用vue.config 配置
// vue.config.js/*** @type {import('@vue/cli-service').ProjectOptions}*/
module.exports = {publicPath: process.env.NODE_ENV === "production" ? "/demo-main-vue/" : "/", // 區分開發和生產服務器的路徑devServer: {headers: {"Access-Control-Allow-Origin": "*", // 如果需要跨域,請打開此配置},open: process.env.NODE_ENV === "development", // 只有開發環境需要使用devServerport: "8000",},lintOnSave: false // 是否關閉eslint檢查,只在保存時才檢查
};
react子應用代碼分析
子應用可以用到的wujie的數據 https://wujie-micro.github.io/doc/api/wujie.html#VPSidebarNav
import "react-app-polyfill/stable";
import "react-app-polyfill/ie11";import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
import "./styles.css";const basename = process.env.NODE_ENV === "production" ? "/demo-react16/" : "";// 如果作為無界的子應用打開,就需要使用 window.__POWERED_BY_WUJIE__ 判斷,然后掛載函數__WUJIE_MOUNT和卸載函數__WUJIE_UNMOUNT
if (window.__POWERED_BY_WUJIE__) {// eslint-disable-next-line no-undefwindow.__WUJIE_MOUNT = () => {ReactDOM.render(<Router basename={basename}><App /></Router>,document.getElementById("root"));};window.__WUJIE_UNMOUNT = () => {ReactDOM.unmountComponentAtNode(document.getElementById("root"));};
} else {ReactDOM.render(<Router basename={basename}><App /></Router>,document.getElementById("root"));
}
react 子應用,嵌套其他子應用
import React from "react";
import WujieReact from "wujie-react"; // 需要引入的wujie react 組件
import lifecycles from "./lifecycle"; // 對應的生命周期
import hostMap from "./hostMap"; // 對應的一些開發環境和生產環境的host映射function selfFetch(url, options) {const includeFlag = process.env.NODE_ENV === "production";return window.fetch(url, { ...options, credentials: includeFlag ? "include" : "omit" });
}export default function React17() {const react17Url = hostMap("//localhost:7100/");const degrade = window.localStorage.getItem("degrade") === "true";const props = {jump: (name) => {window?.$wujie.props.jump(name); // 從主應用vue2中得到的改變主應用router的函數jump,再傳遞給嵌套的子應用},};return (<div><h2>子應用嵌套</h2><div className="content" style={{ border: "1px dashed #ccc", overflow: "auto" }}><WujieReactwidth="100%"height="500px"name="react17"url={react17Url}alive={true}sync={true}fetch={selfFetch}props={props}degrade={degrade}beforeLoad={lifecycles.beforeLoad}beforeMount={lifecycles.beforeMount}afterMount={lifecycles.afterMount}beforeUnmount={lifecycles.beforeUnmount}afterUnmount={lifecycles.afterUnmount}></WujieReact></div></div>);
}
子應用可能還會遇到的問題
無界快速上手 https://wujie-micro.github.io/doc/guide/start.html