{"id":15388311,"url":"https://github.com/canyuegongzi/qiankun-simple-main-app-web","last_synced_at":"2025-10-10T16:42:00.807Z","repository":{"id":127725304,"uuid":"326352950","full_name":"canyuegongzi/qiankun-simple-main-app-web","owner":"canyuegongzi","description":"react微前端聚合系统（qiankun.js）","archived":false,"fork":false,"pushed_at":"2021-07-26T15:35:16.000Z","size":463,"stargazers_count":19,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-15T19:54:38.256Z","etag":null,"topics":["micro-frontends","qiankun","react"],"latest_commit_sha":null,"homepage":"http://canyuegongzi.xyz:10010","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/canyuegongzi.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-01-03T07:34:49.000Z","updated_at":"2024-06-20T03:44:55.000Z","dependencies_parsed_at":null,"dependency_job_id":"632ab16d-4d6f-4b5f-8e2e-1757eed1e8ca","html_url":"https://github.com/canyuegongzi/qiankun-simple-main-app-web","commit_stats":{"total_commits":3,"total_committers":1,"mean_commits":3.0,"dds":0.0,"last_synced_commit":"b790ccee83425bac27785f6eb47a72397d23b526"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/canyuegongzi/qiankun-simple-main-app-web","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canyuegongzi%2Fqiankun-simple-main-app-web","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canyuegongzi%2Fqiankun-simple-main-app-web/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canyuegongzi%2Fqiankun-simple-main-app-web/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canyuegongzi%2Fqiankun-simple-main-app-web/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/canyuegongzi","download_url":"https://codeload.github.com/canyuegongzi/qiankun-simple-main-app-web/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/canyuegongzi%2Fqiankun-simple-main-app-web/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279004702,"owners_count":26083751,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["micro-frontends","qiankun","react"],"created_at":"2024-10-01T14:56:22.591Z","updated_at":"2025-10-10T16:42:00.792Z","avatar_url":"https://github.com/canyuegongzi.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"### 概述\n主系统采用乾坤集成react技术栈，子系统技术栈不限，docker部署;\n[乾坤](https://qiankun.umijs.org/)\n### 预览\n### 技术栈： \n* 主系统：React + Antd + qiankun\n* 子系统：Vue\n### 演示地址\n[演示地址](http://148.70.150.131:10010)\n[github](https://github.com/canyuegongzi/qiankun-simple-main-app-web)\n### 系统截图\n![rootwebapp1.png](http://qiniu.canyuegongzi.xyz/rootwebapp1.png)\n![rootwebapp1.png](http://qiniu.canyuegongzi.xyz/rootwebapp2.png)\n![rootwebapp1.png](http://qiniu.canyuegongzi.xyz/rootwebapp3.png)\n### 主系统设计\n#### 框架搭建\n1. 系统基础搭建\n```\n// 脚手架创建基础代码\nnpx create-react-app simple-main-app-web\n// 暴露配置(能更好的优化webpack打包)\nnpm run eject\n```\n2. qiankun常用api\na. registerMicroApps服务注册\n[registerMicroApps](https://qiankun.umijs.org/api#registermicroappsapps-lifecycles)\n```\nconst app1 = [\n    {\n        name: 'app1',                 // 微服务名：需要和子系统中package.json中的name保持一致\n        entry: '//localhost:8080',    // 微服务系统地址：子系统入口地址(注意：采坑点)\n        container: '#container',      // 子系统需要挂载的节点\n        activeRule: '/react',         // 子系统的触发规则可以自定义函数(见genActiveRule)\n    }\n]\nfunction genActiveRule(routerPrefix) {\n    return location =\u003e location.pathname.startsWith(routerPrefix);\n}\nregisterMicroApps(app1, {\n        beforeLoad: [\n            app =\u003e {},\n        ],\n        beforeMount: [\n            app =\u003e {},\n        ],\n        afterUnmount: [\n            app =\u003e {},\n        ],\n    });\n```\nb. start主应用启动\n[start](https://qiankun.umijs.org/api#registermicroappsapps-lifecycles)\n```\n// 自定义请求方式\nconst request = url =\u003e  {return fetch(url, {referrerPolicy: 'origin-when-cross-origin'})};\nstart({ prefetch: true, fetch: request });\n```\nc. setDefaultMountApp设置默认应用\n[setDefaultMountApp](https://qiankun.umijs.org/api#registermicroappsapps-lifecycles)\n```\nsetDefaultMountApp()\n```\nd. runAfterFirstMounted\n第一个微应用 mount 后需要调用的方法，比如开启一些监控或者埋点脚本。\n[runAfterFirstMounted](https://qiankun.umijs.org/api#registermicroappsapps-lifecycles)\n```\nrunAfterFirstMounted(() =\u003e doSoming());\n```\n#### 用户权限\n用户鉴权实现\n```\n/**\n * 初始化用户信息\n * @returns {Promise\u003cany\u003e}\n */\nexport const initUserLogin = (token) =\u003e {\n    return new Promise(async (resolve) =\u003e {\n        const userInfo = await $post(userCenterApi.getUerInfoByToken.url, {token: token}, userCenterApi.getUerInfoByToken.server)\n        resolve(userInfo);\n    })\n}\n/**\n * 获取微服务列表\n * @returns {Promise\u003cvoid\u003e}\n */\nexport const getMicroApps = async () =\u003e {\n    return new Promise(async (resolve) =\u003e {\n        const res = await $get(userCenterApi.systemList.url, {page: 1, pageSize: 20}, userCenterApi.systemList.server);\n        let systemList = [];\n        const resultList = [];\n        if (res \u0026\u0026 res.data \u0026\u0026 res.data.data) {\n            systemList = res.data.data.data;\n        }\n        for (let i = 0; i \u003c systemList.length; i ++) {\n            const value = systemList[i].attrValue.split('::');\n            if (value.length \u003e 2 \u0026\u0026 value[2] === true + '') {\n                resultList.push({\n                    name: value[0], entry: value[1]\n                })\n            }\n        }\n        resolve(resultList);\n    })\n}\n/**\n * 校验用户登录信息\n * @returns {Promise\u003cany\u003e}\n */\nexport const checkUserInfo = () =\u003e {\n    return new Promise(async(resolve) =\u003e {\n        const mimeStorage = new MimeStorage();\n        let sessionStorageToken = mimeStorage.getItem('token') || sessionStorage.getItem('token');\n        if (sessionStorageToken === \"null\" || !sessionStorageToken) {\n            illegalityUserCallback();\n            return;\n        }\n        const userInfo = await initUserLogin(sessionStorageToken);\n        if (userInfo \u0026\u0026 userInfo.data \u0026\u0026 userInfo.data.success) {\n            const appWebList = await getMicroApps();\n            const menus = await getUserMenusList(userInfo.data.data.name, userInfo.data.data.role.id);\n            let firstMenus = {}\n            if (window.location.hash \u0026\u0026 window.location.pathname) {\n                firstMenus = getFirstmenusByUrl(menus);\n            }else {\n                firstMenus = getFirstMenus(menus);\n            }\n            if (firstMenus \u0026\u0026 firstMenus.firstKey) {\n                window.history.pushState({}, '聚合管理系统', firstMenus.firstKey);\n            }\n            resolve({success: userInfo.data.success, userInfo: userInfo.data.data, appWebList: appWebList, menus, firstMenus })\n            return;\n        }\n        illegalityUserCallback();\n    })\n}\n```\n#### 自定义hooks\n1. 个人配置hooks\n主要用户存储用户的个性化的配置信息\n```\n/**\n * 个人配置\n */\nexport function usePerrsonSetting() {\n    const [theme, setTheme] = useState(loadPersonConfig() ? loadPersonConfig().theme : 'dark');\n    const [navModal, setNavModal] = useState(loadPersonConfig() ? loadPersonConfig().navModal : 'vertical');\n\n    /**\n     * 加载个人配置\n     * @returns {null}\n     */\n    const loadConfig = () =\u003e {\n        const storeConfig = window.localStorage.getItem('personConfig');\n        return storeConfig ? JSON.parse(storeConfig) : null;\n    }\n\n    /**\n     * 保存配置\n     * @type {Function|*}\n     */\n    const setConfig = useCallback((data = {theme: 'dark', navModal: 'vertical'}) =\u003e {\n        setNavModal(data.navModal);\n        setTheme(data.theme);\n        const newConfig = loadConfig() ? {...loadConfig(), theme: data.theme, navModal: data.navModal }: {theme: data.theme, navModal: data.navModal};\n        window.localStorage.setItem('personConfig', JSON.stringify(newConfig));\n    }, [theme, navModal]);\n\n    return {\n        setConfig, theme, navModal, loadConfig\n    }\n}\n```\n2. 上一状态hooks\n```\n/**\n * 上一状态\n * @type\n */\nexport const usePrevious = (function (state, compare) {\n    const prevRef = useRef();\n    const curRef = useRef();\n    const needUpdate = typeof compare === 'function' ? compare(curRef.current, state) : true;\n\n    if (needUpdate) {\n        prevRef.current = curRef.current;\n        curRef.current = state;\n    }\n\n    return prevRef.current;\n});\n```\n### 子系统设计\n#### 生命周期改造\n1. webpack配置修改\na. publicPath保持绝对路径或者 './'\n```\npublicPath: './', || 'http://canyuegongzi.xyz/simple-user-center-web/'\n```\nb. output打包格式修改\n打包输出文件修改为umd， 方便主应用采用fetch拉取代码\n```\noutput: {\n            library: '[name]',\n            filename: '[name].js',\n            libraryTarget: 'umd',\n        },\n```\nc. 生命周期暴露\n主应用需要识别子系统暴露的生命周期函数\n```\n/**\n * bootstrap 只会在微应用初始化的时候调用一次，下次微应用重新进入时会直接调用 mount 钩子，不会再重复触发 bootstrap。\n * 通常我们可以在这里做一些全局变量的初始化，比如不会在 unmount 阶段被销毁的应用级别的缓存等。\n */\nexport async function bootstrap() {\n  console.log(\"VueMicroApp bootstraped\");\n}\n\n/**\n * 应用每次进入都会调用 mount 方法，通常我们在这里触发应用的渲染方法\n */\nexport async function mount(props: any) {\n  console.log(\"VueMicroApp mount\", props);\n  render(props);\n}\n\n/**\n * 应用每次 切出/卸载 会调用的方法，通常在这里我们会卸载微应用的应用实例\n */\nexport async function unmount() {\n  console.log(\"VueMicroApp unmount\");\n  instance.$destroy();\n  instance = null;\n  router = null;\n}\n\n```\nd. 子应用保持活性\n子应用需要通过判断是否是微服务聚合系统中，来创建不同性质的Vue实例(子系需要能单独访问也能在聚合系统中访问)\n```\nif (window.__POWERED_BY_QIANKUN__) {\n  // 动态设置 webpack publicPath，防止资源加载出错\n  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;\n}\n\n```\n##### example\n```\nlet router: any = null;\nlet instance: any = null;\n\nfunction render({ data = {} , container } = {}) {\n  console.log(container)\n  instance = new Vue({\n    router: getRouter(allMenus),\n    store,\n    render: h =\u003e h(App),\n  }).$mount(\"#app\");\n}\nif (!window.__POWERED_BY_QIANKUN__) {\n  escapeCheckSession$().then((router: any) =\u003e {\n    instance = new Vue({\n      router,\n      store,\n      render: (h) =\u003e h(App),\n    }).$mount(\"#app\");\n  });\n}\n```\n### 部署\n为最大限度的保持系统稳定性(刷新问题，微应用触发)，系统部署是主应用采用history路由格式，子应用（通常部署在二级目录下）采用hash路由\n#### 子应用部署\n![rootwebapp4.png](http://qiniu.canyuegongzi.xyz/rootwebapp4.png)\n#### 主应用部署\n主应用采用history路由，为保证刷新不存在问题部署在nginx一级目录下(也可以通过nginx的重定向配置解决);\n1. Dockerfile\n```\nFROM nginx:latest\nMAINTAINER canyuegongzi\nEXPOSE 10010\nCOPY micro-apps-web/  /usr/local/nginx/dist/\nCOPY nginx.conf /etc/nginx/nginx.conf\nRUN echo 'build image success!!'\n```\n2. nginx.conf\n```\nworker_processes auto;\n#error_log  logs/error.log;\n#error_log  logs/error.log  notice;\n#error_log  logs/error.log  info;\n\n#pid        logs/nginx.pid;\n\nevents {\n    worker_connections  1024;\n}\n\nhttp {\n    include       mime.types;\n    default_type  application/octet-stream;\n\n    #log_format  main  '$remote_addr - $remote_user [$time_local] \"$request\" '\n    #                  '$status $body_bytes_sent \"$http_referer\" '\n    #                  '\"$http_user_agent\" \"$http_x_forwarded_for\"';\n\n    #access_log  logs/access.log  main;\n\n    sendfile        on;\n    #tcp_nopush     on;\n\n    #keepalive_timeout  0;\n    keepalive_timeout  65;\n\n    #gzip  on;\n\n    client_max_body_size   20m;\n    server {\n        listen       10010;\n        server_name  localhost;\n\n        #charset koi8-r;\n\n        #access_log  logs/host.access.log  main;\n     location / {\n        root   /usr/local/nginx/dist;\n        index  index.html index.htm;\n        try_files $uri $uri/ /index.html;\n        }\n\n        error_page   500 502 503 504  /50x.html;\n        location = /50x.html {\n            root   html;\n        }\n    }\n\n    # another virtual host using mix of IP-, name-, and port-based configuration\n    #\n    #server {\n    #    listen       8000;\n    #    listen       somename:8080;\n    #    server_name  somename  alias  another.alias;\n\n    #    location / {\n    #        root   html;\n    #        index  index.html index.htm;\n    #    }\n    #}\n\n}\n\n```\n#### 容器化\n```\ndocker build -t microapp:latest .\n```\n#### 容器启动\n```\nocker run -d -it --restart=on-failure:3 --name microapp -p 10010:10010 244341b5bc37\n```\n### 采坑点\n#### 主系统接入子系统\n子系统接入系统入口必须为目录（针对子系统部署在二级目录的情况）\n![rootwebapp5.png](http://qiniu.canyuegongzi.xyz/rootwebapp5.png)\n#### 子系统部署\n子应用部署和普通工程相同(最好采用hash路由格式)\n#### 主系统部署\n主应用部署最好部署在一级目录下(多级目录下需要自定义子应用注册的activeRule函数特殊处理)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanyuegongzi%2Fqiankun-simple-main-app-web","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcanyuegongzi%2Fqiankun-simple-main-app-web","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcanyuegongzi%2Fqiankun-simple-main-app-web/lists"}