Repository: yuzhanglong/mf-lite Branch: master Commit: 3261c127e161 Files: 198 Total size: 121.8 KB Directory structure: gitextract_b8l_l8au/ ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── examples/ │ ├── micro-app-only/ │ │ ├── README.md │ │ └── micro-app/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc.json │ │ ├── app-config.ts │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── mf-expose-types/ │ │ │ └── exposes.d.ts │ │ ├── scripts/ │ │ │ └── proxy.ts │ │ ├── src/ │ │ │ ├── app.tsx │ │ │ ├── index.tsx │ │ │ ├── init-common.ts │ │ │ ├── pages/ │ │ │ │ ├── home.less │ │ │ │ ├── home.tsx │ │ │ │ ├── page-one.tsx │ │ │ │ └── page-two.tsx │ │ │ ├── routes.ts │ │ │ └── types/ │ │ │ └── global.d.ts │ │ └── tsconfig.json │ ├── multiply-micro-app/ │ │ ├── README.md │ │ ├── base-app/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc.json │ │ │ ├── app-config.ts │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ ├── index.html │ │ │ │ └── mf-expose-types/ │ │ │ │ └── exposes.d.ts │ │ │ ├── scripts/ │ │ │ │ └── proxy.ts │ │ │ ├── src/ │ │ │ │ ├── app.tsx │ │ │ │ ├── common/ │ │ │ │ │ └── const.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── pages/ │ │ │ │ │ ├── home.less │ │ │ │ │ └── home.tsx │ │ │ │ ├── routes.ts │ │ │ │ ├── types/ │ │ │ │ │ └── global.d.ts │ │ │ │ └── utils/ │ │ │ │ ├── create-micro-app.tsx │ │ │ │ ├── init-common.ts │ │ │ │ └── shared-utils.ts │ │ │ └── tsconfig.json │ │ ├── micro-app-one/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc.json │ │ │ ├── app-config.ts │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ ├── index.html │ │ │ │ └── mf-expose-types/ │ │ │ │ └── exposes.d.ts │ │ │ ├── scripts/ │ │ │ │ └── proxy.ts │ │ │ ├── src/ │ │ │ │ ├── app.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── init-common.ts │ │ │ │ ├── pages/ │ │ │ │ │ ├── home.less │ │ │ │ │ ├── home.tsx │ │ │ │ │ ├── page-one.tsx │ │ │ │ │ └── page-two.tsx │ │ │ │ ├── routes.ts │ │ │ │ └── types/ │ │ │ │ └── global.d.ts │ │ │ └── tsconfig.json │ │ ├── micro-app-two/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc.json │ │ │ ├── app-config.ts │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ ├── index.html │ │ │ │ └── mf-expose-types/ │ │ │ │ └── exposes.d.ts │ │ │ ├── scripts/ │ │ │ │ └── proxy.ts │ │ │ ├── src/ │ │ │ │ ├── app.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── init-common.ts │ │ │ │ ├── pages/ │ │ │ │ │ ├── home.less │ │ │ │ │ ├── home.tsx │ │ │ │ │ ├── page-one.tsx │ │ │ │ │ └── page-two.tsx │ │ │ │ ├── routes.ts │ │ │ │ └── types/ │ │ │ │ └── global.d.ts │ │ │ └── tsconfig.json │ │ └── package.json │ ├── quick-start/ │ │ ├── README.md │ │ ├── base-app/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc.json │ │ │ ├── app-config.ts │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ ├── index.html │ │ │ │ └── mf-expose-types/ │ │ │ │ └── exposes.d.ts │ │ │ ├── scripts/ │ │ │ │ └── proxy.ts │ │ │ ├── src/ │ │ │ │ ├── app.tsx │ │ │ │ ├── common/ │ │ │ │ │ └── const.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── pages/ │ │ │ │ │ ├── home.less │ │ │ │ │ └── home.tsx │ │ │ │ ├── routes.ts │ │ │ │ ├── types/ │ │ │ │ │ └── global.d.ts │ │ │ │ └── utils/ │ │ │ │ ├── create-micro-app.tsx │ │ │ │ ├── init-common.ts │ │ │ │ └── shared-utils.ts │ │ │ └── tsconfig.json │ │ ├── micro-app/ │ │ │ ├── .eslintrc.js │ │ │ ├── .gitignore │ │ │ ├── .prettierrc.json │ │ │ ├── app-config.ts │ │ │ ├── package.json │ │ │ ├── public/ │ │ │ │ ├── index.html │ │ │ │ └── mf-expose-types/ │ │ │ │ └── exposes.d.ts │ │ │ ├── scripts/ │ │ │ │ └── proxy.ts │ │ │ ├── src/ │ │ │ │ ├── app.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── init-common.ts │ │ │ │ ├── pages/ │ │ │ │ │ ├── home.less │ │ │ │ │ ├── home.tsx │ │ │ │ │ ├── page-one.tsx │ │ │ │ │ └── page-two.tsx │ │ │ │ ├── routes.ts │ │ │ │ └── types/ │ │ │ │ └── global.d.ts │ │ │ └── tsconfig.json │ │ └── package.json │ └── remote-deploy/ │ ├── README.md │ ├── base-app/ │ │ ├── .eslintrc.js │ │ ├── .gitignore │ │ ├── .prettierrc.json │ │ ├── app-config.ts │ │ ├── package.json │ │ ├── public/ │ │ │ ├── index.html │ │ │ └── mf-expose-types/ │ │ │ └── exposes.d.ts │ │ ├── scripts/ │ │ │ └── proxy.ts │ │ ├── src/ │ │ │ ├── app.tsx │ │ │ ├── common/ │ │ │ │ └── const.ts │ │ │ ├── index.tsx │ │ │ ├── pages/ │ │ │ │ ├── home.less │ │ │ │ └── home.tsx │ │ │ ├── routes.ts │ │ │ ├── types/ │ │ │ │ └── global.d.ts │ │ │ └── utils/ │ │ │ ├── create-micro-app.tsx │ │ │ ├── init-common.ts │ │ │ └── shared-utils.ts │ │ └── tsconfig.json │ └── micro-app/ │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.json │ ├── app-config.ts │ ├── package.json │ ├── public/ │ │ ├── index.html │ │ └── mf-expose-types/ │ │ └── exposes.d.ts │ ├── scripts/ │ │ └── proxy.ts │ ├── src/ │ │ ├── app.tsx │ │ ├── index.tsx │ │ ├── init-common.ts │ │ ├── pages/ │ │ │ ├── home.less │ │ │ ├── home.tsx │ │ │ ├── page-one.tsx │ │ │ └── page-two.tsx │ │ ├── routes.ts │ │ └── types/ │ │ ├── assets.d.ts │ │ └── global.d.ts │ └── tsconfig.json ├── lerna.json ├── package.json ├── packages/ │ ├── assets/ │ │ └── README.md │ ├── cli/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── mf-lite.ts │ │ └── tsconfig.json │ ├── core/ │ │ ├── LICENSE │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── __snapshots__/ │ │ │ │ └── ts-bundle.spec.ts.snap │ │ │ ├── assets/ │ │ │ │ ├── app-config.ts │ │ │ │ ├── bar-tmp.d.ts │ │ │ │ ├── bar.ts │ │ │ │ ├── foo-tmp.d.ts │ │ │ │ └── foo.ts │ │ │ ├── micro-app-config.spec.ts │ │ │ └── ts-bundle.spec.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── browser/ │ │ │ │ ├── inject-base-react-refresh.ts │ │ │ │ └── micro-app.tsx │ │ │ ├── common/ │ │ │ │ ├── constants.ts │ │ │ │ └── paths.ts │ │ │ ├── index.ts │ │ │ └── node/ │ │ │ ├── add-entry-attribute-webpack-plugin.ts │ │ │ ├── bundle-module-declare.ts │ │ │ ├── bundle-ts-declaration.ts │ │ │ ├── emit-mf-expose-declaration.ts │ │ │ ├── emit-mf-expose-webpack-plugin.ts │ │ │ ├── generate-mf-expose-declaration.ts │ │ │ ├── get-micro-app-webpack-config.ts │ │ │ ├── get-module-federation-exposes.ts │ │ │ ├── micro-fe-app-config.ts │ │ │ └── webpack-command.ts │ │ └── tsconfig.json │ └── proxy/ │ └── README.md ├── pnpm-workspace.yaml └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.js ================================================ module.exports = { env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'no-underscore-dangle': 'off', 'import/named': 'off', 'no-use-before-define': 'off', }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies node_modules # testing coverage # production build npm-debug.log* yarn-debug.log* yarn-error.log* .idea dist esm lib yarn.lock package.lock.json playground ================================================ FILE: .prettierrc ================================================ { "semi": true, "singleQuote": true, "trailingComma": "es5", "bracketSpacing": true, "printWidth": 120, "arrowParens": "always" } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 YuZhanglong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

基于 Webpack 5 Module Federation,优雅且实用的微前端解决方案。
[在线 DEMO](https://mf-lite-quick-start-base-app.vercel.app/) | [文档](https://yuzhanglong.feishu.cn/wiki/wikcncmRDZCUJRigluH7skQbtvg)
## 介绍 **mf-lite** 是一个基于 Webpack 5 Module Federation 来实现模块共享、[qiankun](https://github.com/umijs/qiankun) 来做隔离沙箱的微前端解决方案,它提供以下内容: - 一个通过命令行快速创建基座应用或者微前端应用的[脚手架](https://github.com/yuzhanglong/mf-lite), 提供项目初始化依赖及开发、构建脚本。[![npm Version](https://img.shields.io/npm/v/@mf-lite/cli.svg)](https://www.npmjs.com/package/@mf-lite/cli) - 一个[核心工具库](https://github.com/yuzhanglong/mf-lite/tree/master/packages/core), 它可以:[![npm Version](https://img.shields.io/npm/v/@mf-lite/core.svg)](https://www.npmjs.com/package/@mf-lite/core) - 基于 **Webpack Module Federation** 特性,让微前端架构下的的**库共享**(share library)、甚至**模块共享**(share module) 成为可能,且使用更加优雅、易于维护。 - 自动生成、处理开发、生产可用的 webpack 的复杂配置项,用户基本上无需直接接触 webpack 的相关配置。 - 支持生成远程模块的 typescript 类型定义。 - 一个基于 node.js、方便独立开发微应用的 HTTP [请求代理工具](https://github.com/yuzhanglong/attachments/tree/main/packages/proxy), 使微应用的独立开发方式更加优雅。[![npm Version](https://img.shields.io/npm/v/@attachments/proxy.svg)](https://www.npmjs.com/package/@attachments/proxy) 对于用户来说,唯一需要做的就是拉取模板、然后加上一些十分简单的配置,剩下的和平常的开发流程别无二致。 ## 特性 📦 **开箱即用**:你只需要执行几行命令即可拉取相应的模板代码并把项目跑起来,包括基座应用和微前端应用,无需处理构建工具的复杂配置。 🤩 **typescript 支持**:模块的生产者和消费者均可自动生成/消费相关的 typescript 类型定义。 🚀 **舒适的开发体验**:开发体验与常规应用一致、完美接入 qiankun 微前端沙箱库、基座和微应用开发都支持热更新,类型定义的生成也不会打断正常的开发流程。 🔨 **独立开发与部署**:基于提供的代理工具,微应用开发者在单独开发微应用时,无需启动基座或者其它微应用。 🌟 **轻量的项目模板**:脚手架生成的初始项目只保留微前端相关的核心依赖,其它第三方库的选型(如 ui 组件库、状态管理库)交由开发者全权管理。 ## 快速开始 **安装脚手架工具** ```shell npm install @mf-lite/cli -g ``` **在交互式命令行中创建项目** ```shell mf-lite create ``` **安装依赖、执行项目** ```shell npm install npm run dev:serve ``` 更多信息以及实践方案,请[查看文档](https://yuzhanglong.feishu.cn/docs/doccnGEPiy8D3DJTZw6S05QJW4f) ## 案例 [快速开始](https://github.com/yuzhanglong/mf-lite/tree/master/examples/quick-start): 最简单的项目 DEMO,开箱即用,全部在本地运行开发。子应用能够共享基座应用暴露出来的模块或者 npm 包。 [微应用独立开发](https://github.com/yuzhanglong/mf-lite/tree/master/examples/micro-app-only): 单独微应用的开发模式,基于部署在远程的基座开发,微应用基于它运行、消费其依赖。 [远程部署案例](https://github.com/yuzhanglong/mf-lite/tree/master/examples/remote-deploy): 通过配置来实现远程部署,其实现效果就是上文的 [在线 DEMO](https://mf-lite-quick-start-base-app.vercel.app/)。 [多个子应用部署案例](https://github.com/yuzhanglong/mf-lite/tree/master/examples/multiply-micro-app): 一个在同一个页面运行多个微应用的案例。 > TIP: 所有案例都可以在本仓库的 `examples` 目录下找到。 ## 它是如何工作的 请参考[这篇文章](https://zhuanlan.zhihu.com/p/422460780) ## License MIT [@yuzhanglong](https://github.com/yuzhanglong) ================================================ FILE: examples/micro-app-only/README.md ================================================ # Example: Micro App Only 单独微应用的开发模式,基于部署在远程的基座开发,在实际开发中强烈建议使用此方式。 相关实践请参考文档的[这一小节](https://ph3xmz5sya.feishu.cn/docs/doccnGEPiy8D3DJTZw6S05QJW4f#EbxQgg) ================================================ FILE: examples/micro-app-only/micro-app/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/micro-app-only/micro-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/micro-app-only/micro-app/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/micro-app-only/micro-app/app-config.ts ================================================ import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; const config: MicroAppConfig = { name: 'micro_app', url: 'http://localhost:10000/', exposes: [], remotes: [ { name: 'base_app', url: 'http://localhost:8080/', sharedLibraries: [ 'react', 'react-dom', 'react/jsx-dev-runtime', 'react-router', 'react-router-dom', 'react-router-config' ] } ] }; export default config; ================================================ FILE: examples/micro-app-only/micro-app/package.json ================================================ { "name": "micro-app", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.0", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=10000 --app-type=micro-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=micro-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=micro-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.0", "@mf-lite/core": "^0.1.0", "@attachments/utils": "^0.1.15", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/micro-app-only/micro-app/public/index.html ================================================ micro-app
================================================ FILE: examples/micro-app-only/micro-app/public/mf-expose-types/exposes.d.ts ================================================ ================================================ FILE: examples/micro-app-only/micro-app/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // micro app 代理 server.addRule( 'mf-lite-quick-start-micro-app.vercel.app', { location: '/', proxyPass: 'http://localhost:10000', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/micro-app-only/micro-app/src/app.tsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { renderRoutes } from 'react-router-config'; import './init-common'; import { routes } from './routes'; const App: React.FC = () => { return ( {renderRoutes(routes)} ); }; export const render = () => { const el = document.getElementById('micro-app'); if (el) { ReactDOM.render(, el); } }; export const destroy = () => { const el = document.getElementById('micro-app'); if (el) { ReactDOM.unmountComponentAtNode(el); } }; ================================================ FILE: examples/micro-app-only/micro-app/src/index.tsx ================================================ const App = import('./app'); const render = () => { App.then(res => res.render()); }; if (!window.__POWERED_BY_QIANKUN__) { render(); } /** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */ export async function bootstrap() { return 0; } /** * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */ export async function mount() { render(); } /** * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 */ export async function unmount() { App.then(res => res.destroy()); } /** * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 */ export async function update() { render(); } ================================================ FILE: examples/micro-app-only/micro-app/src/init-common.ts ================================================ // @ts-ignore import { injectBaseReactRefresh } from '@mf-lite/core/esm/browser/inject-base-react-refresh'; if (process.env.NODE_ENV === 'development') { injectBaseReactRefresh(); } ================================================ FILE: examples/micro-app-only/micro-app/src/pages/home.less ================================================ .react-app-home { .button-wrapper { margin-bottom: 20px; } .home-content { font-size: 20px; } } ================================================ FILE: examples/micro-app-only/micro-app/src/pages/home.tsx ================================================ import React from 'react'; import { RouteComponentProps } from 'react-router'; import './home.less'; interface HomeProps extends RouteComponentProps { } const Home: React.FC = () => { // @ts-ignore return (
这是本地微应用的代码~
); }; export default Home; ================================================ FILE: examples/micro-app-only/micro-app/src/pages/page-one.tsx ================================================ import React from 'react'; interface ProfileProps { } const PageOne: React.FC = () => { return (
Page One
); }; export default PageOne; ================================================ FILE: examples/micro-app-only/micro-app/src/pages/page-two.tsx ================================================ import React from 'react'; interface PageTwoProps { } const PageTwo: React.FC = () => { return (
Page Two
); }; export default PageTwo; ================================================ FILE: examples/micro-app-only/micro-app/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import PageOne from './pages/page-one'; import Home from './pages/home'; import PageTwo from './pages/page-two'; export const routes: RouteConfig[] = [ { component: Home, routes: [ { path: '/', exact: true, component: PageOne, }, { path: '/page-one', component: PageOne, }, { path: '/page-two', component: PageTwo, }, ], }, ]; ================================================ FILE: examples/micro-app-only/micro-app/src/types/global.d.ts ================================================ declare global { interface Window { __POWERED_BY_QIANKUN__: boolean; __REACT_DEVTOOLS_GLOBAL_HOOK__: any; } } export {}; ================================================ FILE: examples/micro-app-only/micro-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: examples/multiply-micro-app/README.md ================================================ # Example: Multiply Micro App 一个基座,多个微应用同时运行。 ================================================ FILE: examples/multiply-micro-app/base-app/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/multiply-micro-app/base-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/multiply-micro-app/base-app/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/multiply-micro-app/base-app/app-config.ts ================================================ import * as path from 'path'; import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; import { sourcePath } from '@mf-lite/core/lib/common/paths'; const config: MicroAppConfig = { remotes: [], name: 'base_app', url: 'http://localhost:8080/', exposes: [ 'react', 'react-dom', 'react-router', 'react-router-dom', 'react-router-config', 'react/jsx-dev-runtime', { name: 'shared-utils', path: path.resolve(sourcePath, 'utils', 'shared-utils.ts'), type: 'module', }, ], }; export default config; ================================================ FILE: examples/multiply-micro-app/base-app/package.json ================================================ { "name": "base-app", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.0", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=8080 --app-type=base-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=base-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=base-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.0", "@mf-lite/core": "^0.1.0", "@attachments/utils": "^0.1.15", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/multiply-micro-app/base-app/public/index.html ================================================ base-app
================================================ FILE: examples/multiply-micro-app/base-app/public/mf-expose-types/exposes.d.ts ================================================ // module name: base_app/shared-utils declare module 'base_app/shared-utils' { export const add: (a: number, b: number) => number; export const sayHello: () => void; export default add; export { }; } ================================================ FILE: examples/multiply-micro-app/base-app/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // 基座代理,将 base-app.vercel.app 的所有请求代理到 http://localhost:8080 server.addRule( 'base-app.vercel.app', { location: '/', proxyPass: 'http://localhost:8080', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/multiply-micro-app/base-app/src/app.tsx ================================================ import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import React, { Suspense } from 'react'; import { renderRoutes } from 'react-router-config'; import { routes } from '~src/routes'; import './utils/init-common'; import '@attachments/utils/src/browser/css/common.css'; const App = () => { return ( {renderRoutes(routes)} ); }; const reactRenderer = () => { ReactDOM.render(( ), document.getElementById('base-app')); }; const beforeAppStart = async () => { return true; }; beforeAppStart() .then(() => { reactRenderer(); }); ================================================ FILE: examples/multiply-micro-app/base-app/src/common/const.ts ================================================ export interface MicroAppConfig { name: string; url: string; } export const MICRO_APPS: MicroAppConfig[] = [ { name: 'micro-app-one', url: 'http://localhost:10000/', }, { name: 'micro-app-two', url: 'http://localhost:10001/', }, ]; ================================================ FILE: examples/multiply-micro-app/base-app/src/index.tsx ================================================ import ('./app'); ================================================ FILE: examples/multiply-micro-app/base-app/src/pages/home.less ================================================ .base-app-home { .title { font-size: 16px; margin-bottom: 20px; } .content { border: 1px solid; padding: 10px; min-height: 100px; } } ================================================ FILE: examples/multiply-micro-app/base-app/src/pages/home.tsx ================================================ import React from 'react'; import './home.less'; import { createMicroApp } from '~src/utils/create-micro-app'; interface HomeProps { } const Home: React.FC = () => { return (
🎉 Two micro apps will be rendered below!!!
{createMicroApp('micro-app-one')()}
{createMicroApp('micro-app-two')()}
); }; export default Home; ================================================ FILE: examples/multiply-micro-app/base-app/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import Home from './pages/home'; export const routes: RouteConfig[] = [ { component: Home, routes: [], }, ]; ================================================ FILE: examples/multiply-micro-app/base-app/src/types/global.d.ts ================================================ declare module '*.module.css'; declare module '*.module.sass'; declare module '*.module.scss'; declare global { const __APP_VERSION__: string; const __MODE__: string; const __BUILD_TIME__: string; interface Window { __POWERED_BY_QIANKUN__: boolean; } } export {}; ================================================ FILE: examples/multiply-micro-app/base-app/src/utils/create-micro-app.tsx ================================================ import React, { useEffect, useRef } from 'react'; import { MicroApp, MicroAppRef } from '@mf-lite/core/esm/browser/micro-app'; import { MICRO_APPS, MicroAppConfig } from '~src/common/const'; export const getMicroApp = (name: string): MicroAppConfig => { const item = MICRO_APPS.find(res => res.name === name); if (!item) { throw new Error(`the micro app ${name} is not exist!`); } return item; }; export const createMicroApp = (name: string) => { return () => { const ref = useRef(null); const microAppConfig = { name: name, entry: getMicroApp(name).url, } useEffect(() => { if (ref.current?.appStore) { ref.current?.appStore.microApp?.loadPromise .then(() => { console.log('micro app loaded!'); }); } }, []); return ( ); }; }; ================================================ FILE: examples/multiply-micro-app/base-app/src/utils/init-common.ts ================================================ /** * File: init-common.ts * Description: 项目全局初始化 * Created: 2021-09-28 21:51:34 * Author: yuzhanglong * Email: yuzl1123@163.com */ // patch 基座的 react-refresh import { consoleTag } from '@attachments/utils/esm/browser/console-tag'; consoleTag( { key: 'MODE', value: __MODE__, }, { key: 'VERSION', value: __APP_VERSION__, valueColor: '#409eff', }, { key: 'BUILD_TIME', value: __BUILD_TIME__, valueColor: '#ea7b27', }, ); ================================================ FILE: examples/multiply-micro-app/base-app/src/utils/shared-utils.ts ================================================ export const add = (a: number, b: number) => { return a + b; }; export const sayHello = () => { console.log('hello world!'); }; export default add; ================================================ FILE: examples/multiply-micro-app/base-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: examples/multiply-micro-app/micro-app-one/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/multiply-micro-app/micro-app-one/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/multiply-micro-app/micro-app-one/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/multiply-micro-app/micro-app-one/app-config.ts ================================================ import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; const config: MicroAppConfig = { name: 'micro_app_one', url: 'http://localhost:10000/', exposes: [], remotes: [ { name: 'base_app', url: 'http://localhost:8080/', sharedLibraries: [ 'react', 'react-dom', 'react/jsx-dev-runtime', 'react-router', 'react-router-dom', 'react-router-config' ] } ] }; export default config; ================================================ FILE: examples/multiply-micro-app/micro-app-one/package.json ================================================ { "name": "micro-app-one", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.0", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=10000 --app-type=micro-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=micro-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=micro-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.0", "@mf-lite/core": "^0.1.0", "@attachments/utils": "^0.1.15", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/multiply-micro-app/micro-app-one/public/index.html ================================================ micro-app-one
================================================ FILE: examples/multiply-micro-app/micro-app-one/public/mf-expose-types/exposes.d.ts ================================================ ================================================ FILE: examples/multiply-micro-app/micro-app-one/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // micro app 代理 server.addRule( 'mf-lite-quick-start-micro-app.vercel.app', { location: '/', proxyPass: 'http://localhost:10000', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/app.tsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { renderRoutes } from 'react-router-config'; import './init-common'; import { routes } from './routes'; const App: React.FC = () => { return ( {renderRoutes(routes)} ); }; export const render = () => { const el = document.getElementById('micro-app-one'); if (el) { ReactDOM.render(, el); } }; export const destroy = () => { const el = document.getElementById('micro-app-one'); if (el) { ReactDOM.unmountComponentAtNode(el); } }; ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/index.tsx ================================================ const App = import('./app'); const render = () => { App.then(res => res.render()); }; if (!window.__POWERED_BY_QIANKUN__) { render(); } /** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */ export async function bootstrap() { return 0; } /** * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */ export async function mount() { render(); } /** * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 */ export async function unmount() { App.then(res => res.destroy()); } /** * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 */ export async function update() { render(); } ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/init-common.ts ================================================ // @ts-ignore import { injectBaseReactRefresh } from '@mf-lite/core/esm/browser/inject-base-react-refresh'; if (process.env.NODE_ENV === 'development') { injectBaseReactRefresh(); } ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/pages/home.less ================================================ .react-app-home { .button-wrapper { margin-bottom: 20px; } .home-content { font-size: 20px; } } ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/pages/home.tsx ================================================ import React, { useState } from 'react'; import { renderRoutes } from 'react-router-config'; import { RouteComponentProps } from 'react-router'; import './home.less'; interface HomeProps extends RouteComponentProps { } const Home: React.FC = (props) => { // @ts-ignore const { route, history, location } = props; const [currentPage, setCurrentPage] = useState<'one' | 'two'>( location.pathname === '/page-two' ? 'two' : 'one' ); const handleButtonClick = () => { const nextPage = currentPage === 'one' ? 'two' : 'one'; setCurrentPage(nextPage); history.push(`page-${nextPage}`); }; return (
{renderRoutes(route.routes)}
); }; export default Home; ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/pages/page-one.tsx ================================================ import React from 'react'; interface ProfileProps { } const PageOne: React.FC = () => { return (
我是第一个微应用~
); }; export default PageOne; ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/pages/page-two.tsx ================================================ import React from 'react'; interface PageTwoProps { } const PageTwo: React.FC = () => { return (
Page Two
); }; export default PageTwo; ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import PageOne from './pages/page-one'; import Home from './pages/home'; import PageTwo from './pages/page-two'; export const routes: RouteConfig[] = [ { component: Home, routes: [ { path: '/', exact: true, component: PageOne, }, { path: '/page-one', component: PageOne, }, { path: '/page-two', component: PageTwo, }, ], }, ]; ================================================ FILE: examples/multiply-micro-app/micro-app-one/src/types/global.d.ts ================================================ declare global { interface Window { __POWERED_BY_QIANKUN__: boolean; __REACT_DEVTOOLS_GLOBAL_HOOK__: any; } } export {}; ================================================ FILE: examples/multiply-micro-app/micro-app-one/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: examples/multiply-micro-app/micro-app-two/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/multiply-micro-app/micro-app-two/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/multiply-micro-app/micro-app-two/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/multiply-micro-app/micro-app-two/app-config.ts ================================================ import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; const config: MicroAppConfig = { name: 'micro_app_two', url: 'http://localhost:10001/', exposes: [], remotes: [ { name: 'base_app', url: 'http://localhost:8080/', sharedLibraries: [ 'react', 'react-dom', 'react/jsx-dev-runtime', 'react-router', 'react-router-dom', 'react-router-config' ] }, { name: 'micro_app_one', url: 'http://localhost:10000/' } ] }; export default config; ================================================ FILE: examples/multiply-micro-app/micro-app-two/package.json ================================================ { "name": "micro-app-two", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.0", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=10001 --app-type=micro-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=micro-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=micro-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.0", "@mf-lite/core": "^0.1.0", "@attachments/utils": "^0.1.15", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/multiply-micro-app/micro-app-two/public/index.html ================================================ micro-app-two
================================================ FILE: examples/multiply-micro-app/micro-app-two/public/mf-expose-types/exposes.d.ts ================================================ ================================================ FILE: examples/multiply-micro-app/micro-app-two/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // micro app 代理 server.addRule( 'mf-lite-quick-start-micro-app.vercel.app', { location: '/', proxyPass: 'http://localhost:10000', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/app.tsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { renderRoutes } from 'react-router-config'; import './init-common'; import { routes } from './routes'; const App: React.FC = () => { return ( {renderRoutes(routes)} ); }; export const render = () => { const el = document.getElementById('micro-app-two'); if (el) { ReactDOM.render(, el); } }; export const destroy = () => { const el = document.getElementById('micro-app-two'); if (el) { ReactDOM.unmountComponentAtNode(el); } }; ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/index.tsx ================================================ const App = import('./app'); const render = () => { App.then(res => res.render()); }; if (!window.__POWERED_BY_QIANKUN__) { render(); } /** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */ export async function bootstrap() { return 0; } /** * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */ export async function mount() { render(); } /** * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 */ export async function unmount() { App.then(res => res.destroy()); } /** * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 */ export async function update() { render(); } ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/init-common.ts ================================================ // @ts-ignore import { injectBaseReactRefresh } from '@mf-lite/core/esm/browser/inject-base-react-refresh'; if (process.env.NODE_ENV === 'development') { injectBaseReactRefresh(); } ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/pages/home.less ================================================ .react-app-home { .button-wrapper { margin-bottom: 20px; } .home-content { font-size: 20px; } } ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/pages/home.tsx ================================================ import React, { useState } from 'react'; import { renderRoutes } from 'react-router-config'; import { RouteComponentProps } from 'react-router'; import './home.less'; interface HomeProps extends RouteComponentProps { } const Home: React.FC = (props) => { // @ts-ignore const { route, history, location } = props; const [currentPage, setCurrentPage] = useState<'one' | 'two'>( location.pathname === '/page-two' ? 'two' : 'one' ); const handleButtonClick = () => { const nextPage = currentPage === 'one' ? 'two' : 'one'; setCurrentPage(nextPage); history.push(`page-${nextPage}`); }; return (
{renderRoutes(route.routes)}
); }; export default Home; ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/pages/page-one.tsx ================================================ import React from 'react'; interface ProfileProps { } const PageOne: React.FC = () => { return (
我是第二个微应用
); }; export default PageOne; ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/pages/page-two.tsx ================================================ import React from 'react'; interface PageTwoProps { } const PageTwo: React.FC = () => { return (
Page Two
); }; export default PageTwo; ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import PageOne from './pages/page-one'; import Home from './pages/home'; import PageTwo from './pages/page-two'; export const routes: RouteConfig[] = [ { component: Home, routes: [ { path: '/', exact: true, component: PageOne, }, { path: '/page-one', component: PageOne, }, { path: '/page-two', component: PageTwo, }, ], }, ]; ================================================ FILE: examples/multiply-micro-app/micro-app-two/src/types/global.d.ts ================================================ declare global { interface Window { __POWERED_BY_QIANKUN__: boolean; __REACT_DEVTOOLS_GLOBAL_HOOK__: any; } } export {}; ================================================ FILE: examples/multiply-micro-app/micro-app-two/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: examples/multiply-micro-app/package.json ================================================ { "name": "micro-app-share", "version": "1.0.0", "main": "index.js", "scripts": { "start-all": "npx concurrently \"yarn:dev:*\" --prefix-colors dim", "dev:start-base-app": "cd base-app && yarn dev:serve", "dev:start-micro-app-one": "cd micro-app-one && yarn dev:serve", "dev:start-micro-app-two": "cd micro-app-two && yarn dev:serve" } } ================================================ FILE: examples/quick-start/README.md ================================================ # Example: Quick Start 最简单的项目 DEMO,开箱即用,全部在本地运行开发。 ## Usage ### 基本的开发环境 启动基座应用和微应用: ```shell yarn start-all ``` 进入 `localhost:8080` 查看效果。 ### 子应用生成父应用模块共享的类型定义 ```shell cd micro-app yarn generate ``` 接着检查微应用的 types/mf-remotes 下类型定义是否生成,其内容如下: ```typescript // module name: base_app/shared-utils declare module 'base_app/shared-utils' { export const add: (a: number, b: number) => number; export const sayHello: () => void; export default add; export {}; } ``` ### 在子应用中调用父应用暴露出来的模块 ```typescript import { sayHello } from 'base_app/shared-utils'; sayHello() // hello world ``` ### 打包 ```shell # 开发环境下打包 yarn build:dev # 生产环境下打包 yarn build:prod ``` ================================================ FILE: examples/quick-start/base-app/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/quick-start/base-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/quick-start/base-app/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/quick-start/base-app/app-config.ts ================================================ import * as path from 'path'; import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; import { sourcePath } from '@mf-lite/core/lib/common/paths'; const config: MicroAppConfig = { remotes: [], name: 'base_app', url: 'http://localhost:8080/', exposes: [ 'react', 'react-dom', 'react-router', 'react-router-dom', 'react-router-config', 'react/jsx-dev-runtime', 'antd', { name: 'shared-utils', path: path.resolve(sourcePath, 'utils', 'shared-utils.ts'), type: 'module' } ] }; export default config; ================================================ FILE: examples/quick-start/base-app/package.json ================================================ { "name": "base-app", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.0", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=8080 --app-type=base-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=base-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=base-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.0", "@mf-lite/core": "^0.1.0", "@attachments/utils": "^0.1.15", "antd": "^4.16.13", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/quick-start/base-app/public/index.html ================================================ base-app
================================================ FILE: examples/quick-start/base-app/public/mf-expose-types/exposes.d.ts ================================================ // module name: base_app/shared-utils declare module 'base_app/shared-utils' { export const add: (a: number, b: number) => number; export const sayHello: () => void; export default add; export { }; } ================================================ FILE: examples/quick-start/base-app/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // 基座代理,将 base-app.vercel.app 的所有请求代理到 http://localhost:8080 server.addRule( 'base-app.vercel.app', { location: '/', proxyPass: 'http://localhost:8080', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/quick-start/base-app/src/app.tsx ================================================ import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import React, { Suspense } from 'react'; import { renderRoutes } from 'react-router-config'; import { routes } from '~src/routes'; import './utils/init-common'; import 'antd/dist/antd.less'; import '@attachments/utils/src/browser/css/common.css'; const App = () => { return ( {renderRoutes(routes)} ); }; const reactRenderer = () => { ReactDOM.render(( ), document.getElementById('base-app')); }; const beforeAppStart = async () => { return true; }; beforeAppStart() .then(() => { reactRenderer(); }); ================================================ FILE: examples/quick-start/base-app/src/common/const.ts ================================================ export interface MicroAppConfig { name: string; url: string; } export const MICRO_APPS: MicroAppConfig[] = [ { name: 'micro-app', url: 'http://localhost:10000/', }, ]; ================================================ FILE: examples/quick-start/base-app/src/index.tsx ================================================ import ('./app'); ================================================ FILE: examples/quick-start/base-app/src/pages/home.less ================================================ .base-app-home { .title { font-size: 16px; margin-bottom: 20px; } .content { border: 1px solid; padding: 10px; min-height: 100px; } } ================================================ FILE: examples/quick-start/base-app/src/pages/home.tsx ================================================ import React from 'react'; import './home.less'; import { createMicroApp } from '~src/utils/create-micro-app'; interface HomeProps { } const Home: React.FC = () => { return (
🎉 This is Base App home page, the micro app will be rendered below! 🎉
{createMicroApp('micro-app')()}
); }; export default Home; ================================================ FILE: examples/quick-start/base-app/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import Home from './pages/home'; export const routes: RouteConfig[] = [ { component: Home, routes: [], }, ]; ================================================ FILE: examples/quick-start/base-app/src/types/global.d.ts ================================================ declare module '*.module.css'; declare module '*.module.sass'; declare module '*.module.scss'; declare global { const __APP_VERSION__: string; const __MODE__: string; const __BUILD_TIME__: string; interface Window { __POWERED_BY_QIANKUN__: boolean; } } export {}; ================================================ FILE: examples/quick-start/base-app/src/utils/create-micro-app.tsx ================================================ import React, { useEffect, useRef } from 'react'; import { MicroApp, MicroAppRef } from '@mf-lite/core/esm/browser/micro-app'; import { MICRO_APPS, MicroAppConfig } from '~src/common/const'; export const getMicroApp = (name: string): MicroAppConfig => { const item = MICRO_APPS.find(res => res.name === name); if (!item) { throw new Error(`the micro app ${name} is not exist!`); } return item; }; export const createMicroApp = (name: string) => { return () => { const ref = useRef(null); const microAppConfig = { name: name, entry: getMicroApp(name).url, } useEffect(() => { if (ref.current?.appStore) { ref.current?.appStore.microApp?.loadPromise .then(() => { console.log('micro app loaded!'); }); } }, []); return ( ); }; }; ================================================ FILE: examples/quick-start/base-app/src/utils/init-common.ts ================================================ /** * File: init-common.ts * Description: 项目全局初始化 * Created: 2021-09-28 21:51:34 * Author: yuzhanglong * Email: yuzl1123@163.com */ // patch 基座的 react-refresh import { consoleTag } from '@attachments/utils/esm/browser/console-tag'; consoleTag( { key: 'MODE', value: __MODE__, }, { key: 'VERSION', value: __APP_VERSION__, valueColor: '#409eff', }, { key: 'BUILD_TIME', value: __BUILD_TIME__, valueColor: '#ea7b27', }, ); ================================================ FILE: examples/quick-start/base-app/src/utils/shared-utils.ts ================================================ export const add = (a: number, b: number) => { return a + b; }; export const sayHello = () => { console.log('hello world!'); }; export default add; ================================================ FILE: examples/quick-start/base-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: examples/quick-start/micro-app/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/quick-start/micro-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/quick-start/micro-app/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/quick-start/micro-app/app-config.ts ================================================ import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; const config: MicroAppConfig = { name: 'micro_app', url: 'http://localhost:10000/', exposes: [], remotes: [ { name: 'base_app', url: 'http://localhost:8080/', sharedLibraries: [ 'react', 'react-dom', 'react/jsx-dev-runtime', 'react-router', 'react-router-dom', 'react-router-config' ] } ] }; export default config; ================================================ FILE: examples/quick-start/micro-app/package.json ================================================ { "name": "micro-app", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.0", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=10000 --app-type=micro-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=micro-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=micro-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.0", "@mf-lite/core": "^0.1.0", "@attachments/utils": "^0.1.15", "antd": "^4.16.13", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/quick-start/micro-app/public/index.html ================================================ micro-app
================================================ FILE: examples/quick-start/micro-app/public/mf-expose-types/exposes.d.ts ================================================ ================================================ FILE: examples/quick-start/micro-app/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // micro app 代理 server.addRule( 'mf-lite-quick-start-micro-app.vercel.app', { location: '/', proxyPass: 'http://localhost:10000', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/quick-start/micro-app/src/app.tsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { renderRoutes } from 'react-router-config'; import './init-common'; import { Button } from 'antd'; import add, { sayHello } from 'base_app/shared-utils'; import { routes } from '~src/routes'; const App: React.FC = () => { sayHello(); return (
来自基座的 add 方法:{add(1, 2)}
{renderRoutes(routes)}
); }; export const render = () => { const el = document.getElementById('micro-app'); if (el) { ReactDOM.render(, el); } }; export const destroy = () => { const el = document.getElementById('micro-app'); if (el) { ReactDOM.unmountComponentAtNode(el); } }; ================================================ FILE: examples/quick-start/micro-app/src/index.tsx ================================================ const App = import('./app'); const render = () => { App.then(res => res.render()); }; if (!window.__POWERED_BY_QIANKUN__) { render(); } /** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */ export async function bootstrap() { return 0; } /** * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */ export async function mount() { render(); } /** * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 */ export async function unmount() { App.then(res => res.destroy()); } /** * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 */ export async function update() { render(); } ================================================ FILE: examples/quick-start/micro-app/src/init-common.ts ================================================ // @ts-ignore import { injectBaseReactRefresh } from '@mf-lite/core/esm/browser/inject-base-react-refresh'; if (process.env.NODE_ENV === 'development') { injectBaseReactRefresh(); } ================================================ FILE: examples/quick-start/micro-app/src/pages/home.less ================================================ .react-app-home { .button-wrapper { margin-bottom: 20px; } .home-content { font-size: 20px; } } ================================================ FILE: examples/quick-start/micro-app/src/pages/home.tsx ================================================ import React, { useState } from 'react'; import { renderRoutes } from 'react-router-config'; import { RouteComponentProps } from 'react-router'; import './home.less'; interface HomeProps extends RouteComponentProps { } const Home: React.FC = (props) => { // @ts-ignore const { route, history, location } = props; const [currentPage, setCurrentPage] = useState<'one' | 'two'>( location.pathname === '/page-two' ? 'two' : 'one' ); const handleButtonClick = () => { const nextPage = currentPage === 'one' ? 'two' : 'one'; setCurrentPage(nextPage); history.push(`page-${nextPage}`); }; return (
{renderRoutes(route.routes)}
); }; export default Home; ================================================ FILE: examples/quick-start/micro-app/src/pages/page-one.tsx ================================================ import React from 'react'; interface ProfileProps { } const PageOne: React.FC = () => { return (
Page One
); }; export default PageOne; ================================================ FILE: examples/quick-start/micro-app/src/pages/page-two.tsx ================================================ import React from 'react'; interface PageTwoProps { } const PageTwo: React.FC = () => { return (
Page Two
); }; export default PageTwo; ================================================ FILE: examples/quick-start/micro-app/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import PageOne from './pages/page-one'; import Home from './pages/home'; import PageTwo from './pages/page-two'; export const routes: RouteConfig[] = [ { component: Home, routes: [ { path: '/', exact: true, component: PageOne, }, { path: '/page-one', component: PageOne, }, { path: '/page-two', component: PageTwo, }, ], }, ]; ================================================ FILE: examples/quick-start/micro-app/src/types/global.d.ts ================================================ declare global { interface Window { __POWERED_BY_QIANKUN__: boolean; __REACT_DEVTOOLS_GLOBAL_HOOK__: any; } } export {}; ================================================ FILE: examples/quick-start/micro-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: examples/quick-start/package.json ================================================ { "name": "quick-start", "version": "1.0.0", "main": "index.js", "license": "MIT", "scripts": { "start-all": "npx concurrently \"yarn:dev:*\" --prefix-colors dim", "dev:start-base-app": "cd base-app && yarn dev:serve", "dev:start-micro-app-one": "cd micro-app && yarn dev:serve" } } ================================================ FILE: examples/remote-deploy/README.md ================================================ # Example: Remote Deploy 远程部署案例,远程部署只需要将配置文件中的 url 改成远程 url 即可,之后和常规项目的部署方法是一样的。 远程部署的实践文档请参考文档的[这一小节](https://ph3xmz5sya.feishu.cn/docs/doccnGEPiy8D3DJTZw6S05QJW4f#f7hzk7) 。 ================================================ FILE: examples/remote-deploy/base-app/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/remote-deploy/base-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/remote-deploy/base-app/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/remote-deploy/base-app/app-config.ts ================================================ import * as path from 'path'; import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; import { sourcePath } from '@mf-lite/core/lib/common/paths'; const config: MicroAppConfig = { remotes: [], name: 'base_app', url: 'https://mf-lite-quick-start-base-app.vercel.app/', exposes: [ 'react', 'react-dom', 'react-router', 'react-router-dom', 'react-router-config', 'react/jsx-dev-runtime', { name: 'shared-utils', path: path.resolve(sourcePath, 'utils', 'shared-utils.ts'), type: 'module', }, ], }; export default config; ================================================ FILE: examples/remote-deploy/base-app/package.json ================================================ { "name": "base-app", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.15", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=8080 --app-type=base-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=base-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=base-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.0", "@mf-lite/core": "^0.1.2", "@attachments/utils": "^0.1.15", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/remote-deploy/base-app/public/index.html ================================================ base-app
================================================ FILE: examples/remote-deploy/base-app/public/mf-expose-types/exposes.d.ts ================================================ // module name: base_app/shared-utils declare module 'base_app/shared-utils' { export const add: (a: number, b: number) => number; export const sayHello: () => void; export default add; export { }; } ================================================ FILE: examples/remote-deploy/base-app/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // 基座代理,将 base-app.vercel.app 的所有请求代理到 http://localhost:8080 server.addRule( 'base-app.vercel.app', { location: '/', proxyPass: 'http://localhost:8080', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/remote-deploy/base-app/src/app.tsx ================================================ import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import React, { Suspense } from 'react'; import { renderRoutes } from 'react-router-config'; import { routes } from '~src/routes'; import './utils/init-common'; import '@attachments/utils/src/browser/css/common.css'; const App = () => { return ( {renderRoutes(routes)} ); }; const reactRenderer = () => { ReactDOM.render(( ), document.getElementById('base-app')); }; const beforeAppStart = async () => { return true; }; beforeAppStart() .then(() => { reactRenderer(); }); ================================================ FILE: examples/remote-deploy/base-app/src/common/const.ts ================================================ export interface MicroAppConfig { name: string; url: string; } export const MICRO_APPS: MicroAppConfig[] = [ { name: 'micro-app', url: 'https://mf-lite-quick-start-micro-app.vercel.app/', }, ]; ================================================ FILE: examples/remote-deploy/base-app/src/index.tsx ================================================ import ('./app'); ================================================ FILE: examples/remote-deploy/base-app/src/pages/home.less ================================================ .base-app-home { .title { font-size: 16px; margin-bottom: 20px; } .content { border: 1px solid; padding: 10px; min-height: 100px; } } ================================================ FILE: examples/remote-deploy/base-app/src/pages/home.tsx ================================================ import React from 'react'; import './home.less'; import { createMicroApp } from '~src/utils/create-micro-app'; interface HomeProps { } const Home: React.FC = () => { return (
🎉 This is Base App home page, the micro app will be rendered below! 🎉
{createMicroApp('micro-app')()}
); }; export default Home; ================================================ FILE: examples/remote-deploy/base-app/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import Home from './pages/home'; export const routes: RouteConfig[] = [ { component: Home, routes: [], }, ]; ================================================ FILE: examples/remote-deploy/base-app/src/types/global.d.ts ================================================ declare module '*.module.css'; declare module '*.module.sass'; declare module '*.module.scss'; declare global { const __APP_VERSION__: string; const __MODE__: string; const __BUILD_TIME__: string; interface Window { __POWERED_BY_QIANKUN__: boolean; } } export {}; ================================================ FILE: examples/remote-deploy/base-app/src/utils/create-micro-app.tsx ================================================ import React, { useEffect, useRef, useState } from 'react'; import { MicroApp, MicroAppRef } from '@mf-lite/core/esm/browser/micro-app'; import { MICRO_APPS, MicroAppConfig } from '~src/common/const'; export const getMicroApp = (name: string): MicroAppConfig => { const item = MICRO_APPS.find(res => res.name === name); if (!item) { throw new Error(`the micro app ${name} is not exist!`); } return item; }; export const createMicroApp = (name: string) => { return () => { const ref = useRef(null); const [isLoading, setIsLoading] = useState(true); const microAppConfig = { name: name, entry: getMicroApp(name).url, }; useEffect(() => { if (ref.current?.appStore) { ref.current?.appStore.microApp?.loadPromise .then(() => { console.log('micro app loaded!'); setIsLoading(false); }); } }, []); return (
{isLoading && 'loading...'}
); }; }; ================================================ FILE: examples/remote-deploy/base-app/src/utils/init-common.ts ================================================ /** * File: init-common.ts * Description: 项目全局初始化 * Created: 2021-09-28 21:51:34 * Author: yuzhanglong * Email: yuzl1123@163.com */ // patch 基座的 react-refresh import { consoleTag } from '@attachments/utils/esm/browser/console-tag'; consoleTag( { key: 'MODE', value: __MODE__, }, { key: 'VERSION', value: __APP_VERSION__, valueColor: '#409eff', }, { key: 'BUILD_TIME', value: __BUILD_TIME__, valueColor: '#ea7b27', }, ); ================================================ FILE: examples/remote-deploy/base-app/src/utils/shared-utils.ts ================================================ export const add = (a: number, b: number) => { return a + b; }; export const sayHello = () => { console.log('hello world!'); }; export default add; ================================================ FILE: examples/remote-deploy/base-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: examples/remote-deploy/micro-app/.eslintrc.js ================================================ module.exports = { root: true, env: { browser: true, es2021: true, node: true, jest: true, }, extends: ['airbnb-base', 'prettier'], parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 12, sourceType: 'module', }, plugins: ['@typescript-eslint', 'import'], rules: { 'semi': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], 'import/extensions': 'off', 'import/prefer-default-export': 'off', 'object-curly-newline': 'off', 'class-methods-use-this': 'off', 'no-shadow': 'off', 'no-console': 'off', 'arrow-body-style': 'off', 'no-useless-constructor': 'off', 'import/no-extraneous-dependencies': 'off', 'no-undef': 'off', 'object-shorthand': 'off', 'no-await-in-loop': 'off', 'consistent-return': 'off', 'no-restricted-syntax': 'off', 'prefer-destructuring': 'off', 'func-names': 'off', 'global-require': 'off', 'import/no-unresolved': 'off', 'no-underscore-dangle': 'off', 'no-use-before-define': 'off' }, settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, 'import/resolver': { typescript: { alwaysTryTypes: true, }, }, }, }; ================================================ FILE: examples/remote-deploy/micro-app/.gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js # testing /coverage # production /build dist # IDE Config .idea # misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* # tmp files public/static .cache # 本地生成的类型定义文件 src/types/mf-remotes # 模块提供者打包生成的类型定义 public/mf-expose/types ================================================ FILE: examples/remote-deploy/micro-app/.prettierrc.json ================================================ { "tabWidth": 2, "semi": false, "singleQuote": true, "jsxBracketSameLine": true } ================================================ FILE: examples/remote-deploy/micro-app/app-config.ts ================================================ import { MicroAppConfig } from '@mf-lite/core/lib/node/micro-fe-app-config'; const config: MicroAppConfig = { name: 'micro_app', url: 'https://mf-lite-quick-start-micro-app.vercel.app/', exposes: [], remotes: [ { name: 'base_app', url: 'https://mf-lite-quick-start-base-app.vercel.app/', sharedLibraries: [ 'react', 'react-dom', 'react/jsx-dev-runtime', 'react-router', 'react-router-dom', 'react-router-config', ], }, ], }; export default config; ================================================ FILE: examples/remote-deploy/micro-app/package.json ================================================ { "name": "micro-app", "version": "0.0.1", "main": "index.js", "license": "MIT", "devDependencies": { "@attachments/proxy": "^0.1.15", "@types/fs-extra": "^9.0.12", "@types/node": "^16.4.6", "@types/react": "^17.0.18", "@types/react-dom": "^17.0.9", "@types/react-router": "^5.1.16", "@types/react-router-config": "^5.0.3", "@types/react-router-dom": "^5.1.8", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "autoprefixer": "^10.3.6", "concurrently": "^6.3.0", "cross-env": "^7.0.3", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "react-router-config": "^5.1.1", "rimraf": "^3.0.2", "ts-node": "^10.2.1", "typescript": "^4.3.5", "webpack-merge": "^5.8.0" }, "scripts": { "dev:proxy": "ts-node scripts/proxy.ts", "dev:serve": "cross-env NODE_ENV=development mf-lite serve --port=10000 --app-type=micro-app", "build:dev": "rimraf dist && cross-env NODE_ENV=development mf-lite build --app-type=micro-app", "build:prod": "rimraf dist && cross-env NODE_ENV=production mf-lite build --app-type=micro-app", "lint": "eslint --ext js,jsx,ts,tsx src", "generate": "mf-lite generate", "upgrade": "mf-lite upgrade" }, "dependencies": { "@mf-lite/cli": "^0.1.3", "@mf-lite/core": "^0.1.2", "@attachments/utils": "^0.1.15", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router": "^5.2.1", "react-router-dom": "^5.2.1" } } ================================================ FILE: examples/remote-deploy/micro-app/public/index.html ================================================ micro-app
================================================ FILE: examples/remote-deploy/micro-app/public/mf-expose-types/exposes.d.ts ================================================ ================================================ FILE: examples/remote-deploy/micro-app/scripts/proxy.ts ================================================ import { ProxyServer } from '@attachments/proxy'; const runProxy = async () => { const server = new ProxyServer(); // micro app 代理 server.addRule( 'mf-lite-quick-start-micro-app.vercel.app', { location: '/', proxyPass: 'http://localhost:10000', } ); server.addRule( 'mf-lite-quick-start-base-app.vercel.app', { location: '/', proxyPass: 'http://localhost:8080', } ); await server.initServers(); await server.listen(); }; runProxy().catch(e => { console.log(e); }); ================================================ FILE: examples/remote-deploy/micro-app/src/app.tsx ================================================ import React from 'react'; import ReactDOM from 'react-dom'; import { BrowserRouter } from 'react-router-dom'; import { renderRoutes } from 'react-router-config'; import './init-common'; import { routes } from './routes'; const App: React.FC = () => { return ( {renderRoutes(routes)} ); }; export const render = () => { const el = document.getElementById('micro-app'); if (el) { ReactDOM.render(, el); } }; export const destroy = () => { const el = document.getElementById('micro-app'); if (el) { ReactDOM.unmountComponentAtNode(el); } }; ================================================ FILE: examples/remote-deploy/micro-app/src/index.tsx ================================================ const App = import('./app'); const render = () => { App.then(res => res.render()); }; if (!window.__POWERED_BY_QIANKUN__) { render(); } /** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */ export async function bootstrap() { return 0; } /** * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */ export async function mount() { render(); } /** * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 */ export async function unmount() { App.then(res => res.destroy()); } /** * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 */ export async function update() { render(); } ================================================ FILE: examples/remote-deploy/micro-app/src/init-common.ts ================================================ // @ts-ignore import { injectBaseReactRefresh } from '@mf-lite/core/esm/browser/inject-base-react-refresh'; if (process.env.NODE_ENV === 'development') { injectBaseReactRefresh(); } ================================================ FILE: examples/remote-deploy/micro-app/src/pages/home.less ================================================ .react-app-home { .button-wrapper { margin-bottom: 20px; } .home-content { font-size: 20px; } } .parent-method { color: #409eff; } .feature-image { margin-top: 20px; width: 480px; } ================================================ FILE: examples/remote-deploy/micro-app/src/pages/home.tsx ================================================ import React, { useState } from 'react'; import { renderRoutes } from 'react-router-config'; import { RouteComponentProps } from 'react-router'; import './home.less'; interface HomeProps extends RouteComponentProps { } const Home: React.FC = (props) => { // @ts-ignore const { route, history, location } = props; const [currentPage, setCurrentPage] = useState<'one' | 'two'>( location.pathname === '/page-two' ? 'two' : 'one' ); const handleButtonClick = () => { const nextPage = currentPage === 'one' ? 'two' : 'one'; setCurrentPage(nextPage); history.push(`page-${nextPage}`); }; return (
{renderRoutes(route.routes)}
); }; export default Home; ================================================ FILE: examples/remote-deploy/micro-app/src/pages/page-one.tsx ================================================ import React from 'react'; import add from 'base_app/shared-utils'; import './home.less'; import feature from '../images/feature.png'; interface ProfileProps { } const PageOne: React.FC = () => { return (
Page One
来自父应用的方法:100 + 100 = {add(100, 100)}
feature
); }; export default PageOne; ================================================ FILE: examples/remote-deploy/micro-app/src/pages/page-two.tsx ================================================ import React from 'react'; interface PageTwoProps { } const PageTwo: React.FC = () => { return (
Page Two
); }; export default PageTwo; ================================================ FILE: examples/remote-deploy/micro-app/src/routes.ts ================================================ import { RouteConfig } from 'react-router-config'; import PageOne from './pages/page-one'; import Home from './pages/home'; import PageTwo from './pages/page-two'; export const routes: RouteConfig[] = [ { component: Home, routes: [ { path: '/', exact: true, component: PageOne, }, { path: '/page-one', component: PageOne, }, { path: '/page-two', component: PageTwo, }, ], }, ]; ================================================ FILE: examples/remote-deploy/micro-app/src/types/assets.d.ts ================================================ declare module '*.module.css'; declare module '*.module.sass'; declare module '*.module.scss'; declare module '*.svg' declare module '*.png' declare module '*.jpg' declare module '*.jpeg' declare module '*.gif' declare module '*.bmp' declare module '*.tiff' ================================================ FILE: examples/remote-deploy/micro-app/src/types/global.d.ts ================================================ declare global { interface Window { __POWERED_BY_QIANKUN__: boolean; __REACT_DEVTOOLS_GLOBAL_HOOK__: any; } } export {}; ================================================ FILE: examples/remote-deploy/micro-app/tsconfig.json ================================================ { "compilerOptions": { "target": "ES5", "module": "CommonJS", "baseUrl": "src", "lib": [ "dom", "dom.iterable", "esnext" ], "paths": { "~src/*": [ "./*" ] }, "types": [ "node" ], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": false, "noEmit": true, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true }, "include": [ "src" ] } ================================================ FILE: lerna.json ================================================ { "packages": [ "packages/*" ], "command": { "publish": { "message": "chore(release): publish %s", "registry": "https://registry.npmjs.org" } }, "version": "0.1.9", "npmClient": "pnpm" } ================================================ FILE: package.json ================================================ { "name": "mf-lite", "version": "1.0.0", "private": true, "workspaces": [ "packages/*" ], "main": "index.js", "license": "MIT", "scripts": { "lint": "eslint --ext .ts --max-warnings 0 ./", "test": "jest --no-cache", "build-all": "pnpm -r build", "clean-all": "lerna clean", "publish-all": "pnpm clean-all && pnpm i && pnpm build-all && lerna publish --no-push" }, "devDependencies": { "@types/jest": "^26.0.24", "@types/node": "^16.4.6", "@typescript-eslint/eslint-plugin": "^4.28.5", "@typescript-eslint/parser": "^4.28.5", "concurrently": "^6.3.0", "eslint": "^7.31.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-prettier": "^8.1.0", "eslint-import-resolver-typescript": "^2.4.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-prettier": "^3.3.1", "fs-extra": "^10.0.0", "html-webpack-plugin": "^5.3.2", "jest": "^27.0.6", "lerna": "^4.0.0", "nodemon": "^2.0.12", "npm-run-all": "^4.1.5", "rimraf": "^3.0.2", "stylelint": "^13.13.1", "stylelint-config-standard": "^22.0.0", "ts-jest": "^27.0.4", "ts-node": "^10.1.0", "typescript": "^4.3.5" } } ================================================ FILE: packages/assets/README.md ================================================ This project has moved [here](https://github.com/yuzhanglong/attachments/tree/main/packages/assets) ================================================ FILE: packages/cli/LICENSE ================================================ MIT License Copyright (c) 2021 YuZhanglong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/cli/README.md ================================================ # @mf-lite/cli 一个通过命令行快速创建基座应用或者微前端应用的脚手架(CLI),提供项目初始化依赖及开发、构建脚本。 ## Usage [查看文档](https://ph3xmz5sya.feishu.cn/docs/doccnGEPiy8D3DJTZw6S05QJW4f?from=space_persnoal_filelist) ================================================ FILE: packages/cli/package.json ================================================ { "name": "@mf-lite/cli", "version": "0.1.9", "description": "A scaffold for quickly creating base applications or micro-front-end applications from the command line", "author": "yuzhanglong ", "homepage": "https://github.com/yuzhanglong/mf-lite", "license": "MIT", "main": "lib/index.js", "module": "esm/index.js", "bin": { "mf-lite": "lib/mf-lite.js" }, "files": [ "src", "esm", "lib" ], "scripts": { "dev:start": "tsc -w", "build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib", "build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm", "build": "npm-run-all --parallel build:*", "lint": "eslint --ext .ts --max-warnings 0 ./src", "test": "jest", "micro-app-template": "cd playground && npx mf-lite create" }, "dependencies": { "@attachments/assets": "^0.1.24", "@attachments/utils": "^0.1.8", "@mf-lite/core": "^0.1.9", "chalk": "^4.1.2", "commander": "^8.2.0", "npm-check-updates": "^11.8.5" } } ================================================ FILE: packages/cli/src/index.ts ================================================ console.log('This is @mf-lite/cli project!'); ================================================ FILE: packages/cli/src/mf-lite.ts ================================================ #!/usr/bin/env node import { program } from 'commander'; import { loadTsConfigFile } from '@attachments/utils/lib/node'; import * as path from 'path'; import ncu from 'npm-check-updates'; import inquirer from 'inquirer'; import chalk from 'chalk'; import { generateMfExposeDeclaration, getMicroAppWebpackConfig, MicroAppConfig, MicroAppWebpackConfigOptions, webpackBuild, webpackServe } from '@mf-lite/core'; import { launchPlopByConfig } from '@attachments/assets/lib'; const APP_TYPES: (MicroAppWebpackConfigOptions['type'])[] = ['micro-app', 'base-app']; const parseMicroAppBaseOptions = async (opt: any, isBuildMode: boolean) => { const { appConfigPath, port, appType, analyze } = opt; if (!APP_TYPES.includes(appType)) { throw new Error('[mf-lite] app-type should be one of base-app or micro-app!'); } const appConfig = await loadTsConfigFile(path.resolve(process.cwd(), appConfigPath)); return { appConfig: appConfig, // 在 build 模式下不起作用 port: port || 8080, type: appType, isBuildMode: isBuildMode, isAnalyzeMode: analyze } as MicroAppWebpackConfigOptions; }; // 版本信息 program .version(`@mf-lite/cli ${require('../package.json').version}`); // 创建项目 program .command('create') .description('create mf-lite project template for base-app or micro-app') .action( async () => { await launchPlopByConfig('micro-fe-generator'); } ); // server 服务 program .command('serve') .description('serve project by webpack-dev-server in development mode') .option('--port ', 'dev-server port') .option('--app-config-path ', 'app configuration file path', 'app-config.ts') .option('--analyze ', 'open webpack-bundle-analyzer', false) .requiredOption('--app-type ', 'set the app type, micro-app or base-app') .action( async (opt) => { const config = await parseMicroAppBaseOptions(opt, false); const microAppWebpackConfig = getMicroAppWebpackConfig(config); await webpackServe(microAppWebpackConfig); } ); // 构建服务 program .command('build') .description('build your app') .option('--app-config-path ', 'app configuration file path', 'app-config.ts') .option('--analyze ', 'open webpack-bundle-analyzer', false) .requiredOption('--app-type ', 'set the app type, micro-app or base-app') .action( async (opt) => { const config = await parseMicroAppBaseOptions(opt, true); const microAppWebpackConfig = getMicroAppWebpackConfig(config); await webpackBuild(microAppWebpackConfig); } ); // 类型定义生成器 program .command('generate') .description('generate remote ts declarations for your app') .option('--app-config-path ', 'app configuration file path', 'app-config.ts') .action( async (opt) => { const { appConfigPath } = opt; const appConfig = await loadTsConfigFile(path.resolve(process.cwd(), appConfigPath)); await generateMfExposeDeclaration(appConfig as MicroAppConfig); } ); // 脚手架包升级 program .command('upgrade') .description('upgrade mf-lite to newest version') .action( async () => { console.log('[mf-lite] upgrading your mf-lite toolkits dependencies...'); const DEPS_TO_UPGRADE = [ '@attachments/proxy', '@attachments/utils', '@mf-lite/core', '@mf-lite/cli' ]; const res = await ncu.run({ packageFile: path.resolve(process.cwd(), 'package.json'), upgrade: false, filter: DEPS_TO_UPGRADE }); const entries = Object.entries(res); // 相关依赖没有需要更新的 if (!entries || entries.length === 0) { console.log('\n[mf-lite] mf-lite is already the latest version~'); return; } for (const [k, v] of entries) { console.log(`${k} => ${chalk.blue(v)}`); } const userAnswers = await inquirer.prompt( [ { type: 'confirm', message: '[mf-lite] There is a new version of mf-lite, the dependencies information is listed above, are you sure you want to update it?', name: 'shouldUpgrade', default: false } ] ); if (userAnswers) { await ncu.run({ packageFile: path.resolve(process.cwd(), 'package.json'), upgrade: true, filter: DEPS_TO_UPGRADE }); console.log('[mf-lite] package.json is updated successfully, please run `yarn install` or `npm install` to upgrade your dependencies!'); } } ); program.parse(process.argv); ================================================ FILE: packages/cli/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "baseUrl": "./", "outDir": "lib", "rootDir": "./src", "jsx": "react" }, "include": [ "src/**/*" ] } ================================================ FILE: packages/core/LICENSE ================================================ MIT License Copyright (c) 2021 YuZhanglong Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: packages/core/README.md ================================================ # @mf-lite/core mf-lite 的核心工具库 ## Usage [查看文档](https://ph3xmz5sya.feishu.cn/docs/doccnGEPiy8D3DJTZw6S05QJW4f?from=space_persnoal_filelist) ================================================ FILE: packages/core/__tests__/__snapshots__/ts-bundle.spec.ts.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`test ts-bundle utils test bundleModuleDeclare 1`] = ` "// module name: app1/foo declare module 'app1/foo' { export const add: (a: string, b: string) => string; export class Hello { private name; constructor(name: string); } export default add; export { }; } // module name: app1/bar declare module 'app1/bar' { export const add2: (a: string, b: string) => string; export class Hello2 { private name; constructor(name: string); } export default Hello2; export { }; } " `; exports[`test ts-bundle utils test dts bundle generator wrapper 1`] = ` "// Generated by dts-bundle-generator v5.9.0 export declare const add2: (a: string, b: string) => string; export declare class Hello2 { private name; constructor(name: string); } export default Hello2; export {}; " `; exports[`test ts-bundle utils test dts bundle generator wrapper 2`] = ` "// Generated by dts-bundle-generator v5.9.0 export declare const add: (a: string, b: string) => string; export declare class Hello { private name; constructor(name: string); } export default add; export {}; " `; ================================================ FILE: packages/core/__tests__/assets/app-config.ts ================================================ const a = 1; console.log(a); ================================================ FILE: packages/core/__tests__/assets/bar-tmp.d.ts ================================================ // Generated by dts-bundle-generator v5.9.0 export declare const add2: (a: string, b: string) => string; export declare class Hello2 { private name; constructor(name: string); } export default Hello2; export {}; ================================================ FILE: packages/core/__tests__/assets/bar.ts ================================================ export const add2 = (a: string, b: string) => { return a + b; }; export class Hello2 { private name: string; constructor(name: string) { this.name = name; } } export default Hello2; ================================================ FILE: packages/core/__tests__/assets/foo-tmp.d.ts ================================================ // Generated by dts-bundle-generator v5.9.0 export declare const add: (a: string, b: string) => string; export declare class Hello { private name; constructor(name: string); } export default add; export {}; ================================================ FILE: packages/core/__tests__/assets/foo.ts ================================================ export const add = (a: string, b: string) => { return a + b; }; export class Hello { private name: string; constructor(name: string) { this.name = name; } } export default add; ================================================ FILE: packages/core/__tests__/micro-app-config.spec.ts ================================================ import { getMicroAppConfigManager } from '../src'; describe('test the micro application Config configuration library', () => { test('test getModuleFederationRemotes()', () => { const manager = getMicroAppConfigManager({ name: 'my_app', url: 'www.base.com', remotes: [ { name: 'app1', url: 'www.app1.com', sharedLibraries: [], }, { name: 'app2', url: 'www.app2.com', }, ], exposes: [], }); expect(manager.getModuleFederationRemotes()) .toStrictEqual({ 'app1': 'app1@www.app1.com/module-federation-entry.js', 'app2': 'app2@www.app2.com/module-federation-entry.js', }); }); test('test getNormalModuleReplacementPluginCallBack()', () => { const manager = getMicroAppConfigManager({ name: 'my_app', url: 'www.base.com', remotes: [ { name: 'app1', url: 'www.app1.com', sharedLibraries: [ 'react', ], }, { name: 'app2', url: 'www.app2.com', sharedLibraries: [ 'mobx', { name: 'global-store', type: 'module', }, { name: 'foo', type: 'package', }, ], }, ], exposes: [], }); const cb = manager.getNormalModuleReplacementPluginCallBack(); const runCb = (name: string) => { const item = { request: name, }; cb(item); return item.request; }; expect(runCb('react')).toStrictEqual('app1/react'); expect(runCb('react-dom')).toStrictEqual('react-dom'); expect(runCb('foo')).toStrictEqual('app2/foo'); }); }); ================================================ FILE: packages/core/__tests__/ts-bundle.spec.ts ================================================ /** * File: ts-bundle.spec.ts * Description: typescript 类型定义打包工具封装 * Created: 2021-10-01 14:25:20 * Author: yuzhanglong * Email: yuzl1123@163.com */ import * as path from 'path'; import * as fs from 'fs'; import { bundleModuleDeclare, bundleTsDeclaration } from '../src'; describe('test ts-bundle utils', () => { const p = path.resolve(__dirname, 'assets'); test('test dts bundle generator wrapper', async () => { await bundleTsDeclaration([ { entryPath: path.resolve(p, 'bar.ts'), outputPath: path.resolve(p, 'bar-tmp.d.ts'), }, { entryPath: path.resolve(p, 'foo.ts'), outputPath: path.resolve(p, 'foo-tmp.d.ts'), }, ]); expect(fs.readFileSync(path.resolve(p, 'bar-tmp.d.ts')).toString()).toMatchSnapshot(); expect(fs.readFileSync(path.resolve(p, 'foo-tmp.d.ts')).toString()).toMatchSnapshot(); }, 20000); test('test bundleModuleDeclare', () => { const content = bundleModuleDeclare([ { moduleName: 'app1/foo', path: path.resolve(p, 'foo-tmp.d.ts'), }, { moduleName: 'app1/bar', path: path.resolve(p, 'bar-tmp.d.ts'), }, ]); expect(content).toMatchSnapshot(); }); }); ================================================ FILE: packages/core/package.json ================================================ { "name": "@mf-lite/core", "version": "0.1.9", "description": "core library for mf-lite", "author": "yuzhanglong ", "homepage": "https://github.com/yuzhanglong/mf-lite", "license": "MIT", "main": "lib/index.js", "module": "esm/index.js", "files": [ "src", "esm", "lib", "dist" ], "scripts": { "dev:start": "tsc -w", "build:cjs": "rimraf ./lib && tsc --module commonjs --outDir lib", "build:esm": "rimraf ./esm && tsc --module ESNext --outDir esm", "build": "npm-run-all --parallel build:*", "lint": "eslint --ext .ts --max-warnings 0 ./src", "test": "jest", "mf-scripts:serve": "node lib/node/mf-scripts.js serve --port=8080 --app-config-path=__tests__/assets/app-config.ts --app-type=micro-app", "mf-scripts:upgrade": "node lib/node/mf-scripts.js upgrade" }, "devDependencies": { "@types/fs-extra": "^9.0.13", "@types/mini-css-extract-plugin": "^2.3.0", "@types/react": "^17.0.26", "@types/react-dom": "^17.0.9", "@types/webpack-dev-server": "^4.1.0" }, "dependencies": { "@attachments/utils": "^0.1.8", "@babel/core": "^7.15.5", "@babel/plugin-proposal-decorators": "^7.15.4", "@babel/plugin-transform-runtime": "^7.15.0", "@babel/preset-env": "^7.15.6", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.15.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", "@types/mini-css-extract-plugin": "^2.3.0", "autoprefixer": "^10.3.6", "axios": "^0.22.0", "babel-loader": "^8.2.2", "chalk": "^4.1.2", "commander": "^8.2.0", "css-loader": "^6.3.0", "dts-bundle-generator": "^5.9.0", "enhanced-resolve": "^5.8.3", "fs-extra": "^10.0.0", "html-webpack-plugin": "^5.3.2", "inquirer": "^8.2.0", "less": "^4.1.1", "less-loader": "^10.0.1", "mini-css-extract-plugin": "^2.3.0", "moment": "^2.29.1", "npm-check-updates": "^11.8.5", "postcss": "^8.3.8", "postcss-loader": "^6.1.1", "qiankun": "^2.5.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-refresh": "^0.10.0", "sass": "^1.43.5", "sass-loader": "^12.3.0", "style-loader": "^3.3.0", "terser-webpack-plugin": "^5.2.4", "ts-morph": "^12.0.0", "webpack": "^5.56.0", "webpack-bundle-analyzer": "^4.4.2", "webpack-dev-server": "^4.3.0", "webpack-merge": "^5.8.0" } } ================================================ FILE: packages/core/src/browser/inject-base-react-refresh.ts ================================================ import { injectIntoGlobalHook } from 'react-refresh/cjs/react-refresh-runtime.development'; import ReactDOM from 'react-dom'; declare global { interface Window { __REACT_DEVTOOLS_GLOBAL_HOOK__: any; } } /** * 基于 webpack module Federation 架构下子应用的 react refresh 补丁 * * @author yuzhanglong * @date 2021-09-27 22:19:36 */ export const injectBaseReactRefresh = () => { // Injects the react refresh replacing the one from the base app injectIntoGlobalHook(window); // Injects the react-dom instance again window.__REACT_DEVTOOLS_GLOBAL_HOOK__.inject(ReactDOM); }; ================================================ FILE: packages/core/src/browser/micro-app.tsx ================================================ import React, { forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; import { loadMicroApp, ObjectType } from 'qiankun'; import { LoadableApp, MicroApp as IMicroApp } from 'qiankun/es/interfaces'; interface MicroAppProps { microAppConfig: Omit, 'container'>; } interface MicroAppCmpStore { microApp: IMicroApp | null; containerRef: HTMLElement | null; } export interface MicroAppRef { appStore: MicroAppCmpStore; } export const MicroApp = forwardRef((props, microAppRef) => { const { current } = useRef({ containerRef: null, microApp: null, }); const loadApp = () => { if (current.containerRef) { current.microApp = loadMicroApp({ ...props.microAppConfig, container: current.containerRef, }, { sandbox: { experimentalStyleIsolation: true, }, }); } }; useEffect(() => { loadApp(); return () => { current.microApp?.unmount(); }; }, []); useEffect(() => { if (current.microApp && current.microApp.update) { current.microApp.update({}); } }, []); useImperativeHandle(microAppRef, () => { return { appStore: current, }; }); return (
{ current.containerRef = ref; }} /> ); }); ================================================ FILE: packages/core/src/common/constants.ts ================================================ export const JS_PREFIX = 'static/js'; export const CSS_PREFIX = 'static/css'; export const IMG_PREFIX = 'static/images'; export const FONT_PREFIX = 'static/fonts'; export const FILE_PREFIX = 'static/files'; ================================================ FILE: packages/core/src/common/paths.ts ================================================ import * as path from 'path'; export const sourcePath = path.resolve(process.cwd(), 'src'); export const publicPath = path.resolve(process.cwd(), 'public'); export const appConfigPath = path.resolve(process.cwd(), 'app-config.ts'); ================================================ FILE: packages/core/src/index.ts ================================================ export { MicroAppConfig } from './node/micro-fe-app-config'; export { MicroAppWebpackConfigOptions } from './node/get-micro-app-webpack-config'; export { webpackBuild, webpackPromisify, webpackServe } from './node/webpack-command'; export { getMicroAppConfigManager } from './node/micro-fe-app-config'; export { generateMfExposeDeclaration } from './node/generate-mf-expose-declaration'; export { bundleTsDeclaration } from './node/bundle-ts-declaration'; export { generateDtsBundle } from 'dts-bundle-generator'; export { bundleModuleDeclare } from './node/bundle-module-declare'; export { emitMfExposeDeclaration } from './node/emit-mf-expose-declaration'; export { EmitMfExposeWebpackPlugin } from './node/emit-mf-expose-webpack-plugin'; export { getMicroAppWebpackConfig } from './node/get-micro-app-webpack-config'; export { getModuleFederationExposes } from './node/get-module-federation-exposes'; ================================================ FILE: packages/core/src/node/add-entry-attribute-webpack-plugin.ts ================================================ import webpack from 'webpack'; import { Hooks } from 'html-webpack-plugin'; /** * 向 html-webpack-plugin 导出的 HTML 模板 script 添加属性 * * @author yuzhanglong * @date 2021-10-10 02:31:52 */ export class AddEntryAttributeWebpackPlugin { private readonly entryMatchCallback; constructor(matchCallback: (src: string) => boolean) { this.entryMatchCallback = matchCallback; } apply(compiler: webpack.Compiler) { compiler.hooks.compilation.tap('AddEntryAttributeWebpackPlugin', (compilation) => { // 通过最终的 webpack 配置的 plugins 属性,根据插件的 constructor.name 拿到 html-webpack-plugin 实例 const HtmlWebpackPluginInstance: any = compiler.options.plugins .map(({ constructor }) => constructor) .find(constructor => constructor && constructor.name === 'HtmlWebpackPlugin'); if (HtmlWebpackPluginInstance) { // 获取 html-webpack-plugin 所有的 hooks const hooks = HtmlWebpackPluginInstance.getHooks(compilation) as Hooks; // 在插入标签之前做些什么 hooks.alterAssetTagGroups.tap( 'AddEntryAttributeWebpackPlugin', (data) => { // 拿到所有的标签,如果是 script 标签,并且满足我们的匹配函数,则将其 attributes['entry'] 设为 true data.headTags.forEach(tag => { if (tag.tagName === 'script' && this.entryMatchCallback(tag.attributes?.src)) { // eslint-disable-next-line no-param-reassign tag.attributes.entry = true; } }); return data; }, ); } }); } } ================================================ FILE: packages/core/src/node/bundle-module-declare.ts ================================================ import { ModuleDeclarationKind, Project, SyntaxKind } from 'ts-morph'; export interface FileOptions { // 声明文件路径 path: string; // 声明文件模块名称 moduleName: string; } /** * 打包类型定义文件 * * @author yuzhanglong * @date 2021-10-03 19:28:19 * @param fileOptions 文件相关选项,可参考上面的类型定义 */ export const bundleModuleDeclare = (fileOptions: FileOptions[]) => { const project = new Project(); const content = []; fileOptions.forEach(file => { // 添加源代码 const source = project.addSourceFileAtPath(file.path); // 遍历每一个子节点,如果是 SyntaxKind.DeclareKeyword(即 declare 关键词),进行文本替换 source.forEachDescendant(item => { if (item.getKind() === SyntaxKind.DeclareKeyword) { // 删除即可, 需要判断是不是第一个节点,否则会报异常 item.replaceWithText(item.isFirstNodeOnLine() ? 'export' : ''); } }); // 备份根节点 const baseStatements = source.getStructure().statements; // 移除现存的所有节点 source.getStatements().forEach(res => res.remove()); // 创建一个 module declaration,将上面备份的根节点插入之 source.addModule({ name: `'${file.moduleName}'`, declarationKind: ModuleDeclarationKind.Module, hasDeclareKeyword: true, statements: baseStatements, }); // 格式化代码 source.formatText(); // 补充一些注释 content.push(`// module name: ${file.moduleName}\n\n`); content.push(source.getText()); content.push('\n'); }); return content.join(''); }; ================================================ FILE: packages/core/src/node/bundle-ts-declaration.ts ================================================ import * as os from 'os'; import * as path from 'path'; import { runCommand } from '@attachments/utils/lib/node/run-command'; export interface BundleFileConfig { // 入口文件路径 entryPath: string; // 输出路径 outputPath: string; } /** * 封装 dts-bundle-generator * * @author yuzhanglong * @date 2021-10-01 20:09:23 * @param entries 一个数组,详见 BundleFileConfig * @see BundleFileConfig */ export const bundleTsDeclaration = async ( entries: BundleFileConfig[], ) => { // 最大并行工作数目为 cpu 核心数 - 1 const maxWorkSize = os.cpus().length - 1; while (entries.length > 0) { const runningItems = entries.splice(0, maxWorkSize); await Promise.all(runningItems.map((item) => { const { entryPath, outputPath } = item; return runCommand( path.resolve( require.resolve('dts-bundle-generator'), '../bin/dts-bundle-generator.js', ), [ entryPath, '--out-file', outputPath, '--project', path.resolve(process.cwd(), 'tsconfig.json'), '--no-banner', ]); })); } }; ================================================ FILE: packages/core/src/node/emit-mf-expose-declaration.ts ================================================ import * as path from 'path'; import * as fs from 'fs-extra'; import { MicroAppConfig } from './micro-fe-app-config'; import { BundleFileConfig, bundleTsDeclaration } from './bundle-ts-declaration'; import { bundleModuleDeclare } from './bundle-module-declare'; /** * 供公共组件的提供者使用,用来将相应的类型定义写入某个文件目录下 * 我们可以在 webpack 写入文件之后 hook 到相应的生命周期中追加内容 * * @author yuzhanglong * @date 2021-10-02 14:51:10 * @param appConfig app 配置 * @param baseUrl 写入的根路径 */ export const emitMfExposeDeclaration = async (appConfig: MicroAppConfig, baseUrl: string) => { // 增加临时缓存文件,用来打包每个小 bundle await fs.ensureDir(path.resolve(baseUrl, '.cache')); const entries: (BundleFileConfig & { name: string })[] = []; for (const expose of appConfig.exposes) { // 只处理 module 类型 if (typeof expose === 'object' && expose.type !== 'package') { entries.push({ name: expose.name, entryPath: expose.path, outputPath: path.resolve(baseUrl, '.cache', `${expose.name}.d.ts`), }); } } // 并行打包 await bundleTsDeclaration(entries.slice()); // 合并上面的所有小 bundle const content = bundleModuleDeclare( entries.map(res => { return { path: res.outputPath, moduleName: `${appConfig.name}/${res.name}`, }; }), ); await fs.writeFile(path.resolve(baseUrl, 'exposes.d.ts'), content); await fs.remove(path.resolve(baseUrl, '.cache')); }; ================================================ FILE: packages/core/src/node/emit-mf-expose-webpack-plugin.ts ================================================ import webpack from 'webpack'; import * as path from 'path'; import { MicroAppConfig } from './micro-fe-app-config'; import { emitMfExposeDeclaration } from './emit-mf-expose-declaration'; interface EmitMfExposeWebpackPluginOptions { // app 配置 appConfig: MicroAppConfig; // 输出内容的基础路径,如果没有指定则为 compilation.compiler.outputPath // 由于 serve 模式 build 模式输出位置不同,这个选项是有必要的,降低开发成本 outputBasePath?: string; } /** * 防抖函数 * * @author yuzhanglong * @date 2021-12-01 00:51:19 */ export const debounce = any>(fn: T, timeout: number = 0) => { if (typeof fn !== 'function') { throw new Error('fn should be a function'); } let timer = null; return function(...args: Parameters) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { fn.call(this, ...args); }, timeout); }; }; /** * 向 build 打包产物注入类型定义的 webpack-plugin * * @author yuzhanglong * @date 2021-10-03 19:31:02 */ export class EmitMfExposeWebpackPlugin { private readonly config: EmitMfExposeWebpackPluginOptions; constructor(config: EmitMfExposeWebpackPluginOptions) { this.config = config; } apply(compiler: webpack.Compiler) { const { appConfig, outputBasePath } = this.config; const handler = debounce(async (compilation: webpack.Compilation) => { try { // 用 try catch 包裹一下防止 webpack-dev-server 热更新过程中偶发的强制 exit 现象 if (appConfig) { // 拿到本项目的 outputPath const { outputPath } = compilation.compiler; // 生成相关目录 const target = path.resolve(outputBasePath ?? outputPath, 'mf-expose-types'); console.log('[mf-lite] compiling shared remote module declarations...'); // 基于用户的配置 appConfig 生成类型定义 await emitMfExposeDeclaration(appConfig, target); } } catch (e) { console.log(e); } }, 1500); // afterEmit 生命周期的时机:输出 asset 到 output 目录之后 // 实践证明,它不会阻塞 webpack dev-server 的流程,不会影响开发体验。 compiler.hooks.afterEmit.tap('EmitMfExposeWebpackPlugin', handler); // TODO: 使用文件 hash 进行缓存,避免相同的内容重复打包,可以参考下面的的注释 DEMO // compiler.hooks.thisCompilation.tap('EmitMfExposeWebpackPlugin', (compilation) => { // compilation.hooks.processAssets.tapAsync({ // name: 'EmitMfExposeWebpackPlugin', // stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE, // }, (compilationAssets, callback) => { // // 首先收集所有的文件,进行 content hash 值比对 // // @ts-ignore // const data = [...compilation.modules].filter(res => res.request && res.request.includes('shared-utils.ts'))[0]; // console.log(data.buildInfo); // return callback(); // }); // }); } } ================================================ FILE: packages/core/src/node/generate-mf-expose-declaration.ts ================================================ /** * 生成 module federation expose 声明,一般用于消费者调用 * * @author yuzhanglong * @date 2021-10-02 14:53:24 */ import axios from 'axios'; import * as url from 'url'; import * as fs from 'fs-extra'; import * as path from 'path'; import { MicroAppConfig } from './micro-fe-app-config'; import { sourcePath } from '../common/paths'; export const generateMfExposeDeclaration = async (appConfig: MicroAppConfig) => { const declareTypeRoot = path.resolve(sourcePath, 'types', 'mf-remotes'); await fs.ensureDir(declareTypeRoot); for (const { name, url: remoteUrl } of appConfig.remotes) { const targetFileName = `${name}-exposes.d.ts`; // example: https://base-40kkvlqeq-yzl.vercel.app/mf-expose-types/exposes.d.ts const remote = url.resolve(remoteUrl, 'mf-expose-types/exposes.d.ts'); console.log(`fetching remotes types declarations from ${remote}...`); const declarations = await axios.get(remote); await fs.writeFile(path.resolve(declareTypeRoot, targetFileName), declarations.data); } console.log('Done!'); }; ================================================ FILE: packages/core/src/node/get-micro-app-webpack-config.ts ================================================ import webpack, { NormalModuleReplacementPlugin } from 'webpack'; import HtmlWebpackPlugin from 'html-webpack-plugin'; import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import moment from 'moment'; import * as path from 'path'; import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; import merge from 'webpack-merge'; import { publicPath as assetPublicPath, sourcePath } from '../common/paths'; import { CSS_PREFIX, FILE_PREFIX, JS_PREFIX } from '../common/constants'; import { getMicroAppConfigManager, MicroAppConfig } from './micro-fe-app-config'; import { getModuleFederationExposes } from './get-module-federation-exposes'; import { EmitMfExposeWebpackPlugin } from './emit-mf-expose-webpack-plugin'; import { AddEntryAttributeWebpackPlugin } from './add-entry-attribute-webpack-plugin'; const TerserWebpackPlugin = require('terser-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; export interface MicroAppWebpackConfigOptions { // 项目类型,base: 基座, micro-app 微应用 type: 'base-app' | 'micro-app'; // 项目运行的端口 port: number; // 微应用配置 appConfig: MicroAppConfig; // 是否在构建环境 isBuildMode: boolean; // 开启分析模式 isAnalyzeMode?: boolean; } /** * 基座或者微应用的 webpack 配置封装 * * @author yuzhanglong * @date 2021-10-03 19:32:18 * @param options 相关配置选项,可参考上面的类型定义 * @see MicroAppWebpackConfigOptions */ export const getMicroAppWebpackConfig = (options: MicroAppWebpackConfigOptions) => { const { port, appConfig, isBuildMode, isAnalyzeMode } = options; const websocketPath = `ws://localhost:${port.toString()}/ws`; // 是否为生产环境 const isProductionEnvironment = process.env.NODE_ENV === 'production'; // 微应用工具管理类 const microAppConfigManager = getMicroAppConfigManager(appConfig); // 样式默认配置 const baseStyleConfigRules = [ isProductionEnvironment ? MiniCssExtractPlugin.loader : require.resolve('style-loader'), require.resolve('css-loader'), { loader: require.resolve('postcss-loader'), options: { postcssOptions: { plugins: [ require('autoprefixer'), ], }, }, }, ].filter(Boolean); // noinspection UnnecessaryLocalVariableJS const config = { mode: isProductionEnvironment ? 'production' : 'development', // 打包入口,默认为 index.tsx entry: path.resolve(sourcePath, 'index.tsx'), // 在生产环境下默认不会打包 sourcemap (但有时候可能还是有必要的,比如接入监控平台) devtool: isProductionEnvironment ? false : 'source-map', // 开启打包文件缓存,第二次打开可以节约大量的时间 // 在 build 模式下不要打开,否则会报错 cache: isBuildMode ? false : { type: 'filesystem', }, // 代码优化配置 optimization: { // 这里保证了基座热更新的实现 runtimeChunk: 'single', minimize: isProductionEnvironment, minimizer: [ new TerserWebpackPlugin({ terserOptions: { format: { comments: false, }, }, extractComments: false, }), ], // 一些常见依赖的代码分割 splitChunks: { chunks: 'all', cacheGroups: { thirdVendors: { name: 'initial-third-vendors', test: /moment|lodash|mobx|qiankun/, priority: 20, enforce: true, }, reactVendors: { name: 'initial-react-vendors', test: /react\/|react-dom\/|react-router\/|react-router-dom\/|axios/, priority: 20, enforce: true, }, uiComponents: { name: 'initial-ui-components-vendors', test: /antd/, priority: 20, enforce: true, }, uiIcons: { name: 'initial-ui-icons-vendors', test: /@ant-design\/icons/, priority: 20, enforce: true, }, uiOthers: { name: 'initial-material-ui-others-vendors', test: /@ant-design\/*/, priority: 10, enforce: true, }, }, }, }, // webpack dev-server // @ts-ignore devServer: { client: { webSocketURL: websocketPath, }, static: { directory: assetPublicPath, watch: { ignored: (f: string) => { // 生成的类型定义不要监听,否则会引发全局的 reload 使 HMR 失去意义 return f.endsWith('.d.ts'); }, }, }, allowedHosts: 'all', hot: true, port: port, historyApiFallback: true, headers: { 'Access-Control-Allow-Origin': '*', }, }, // 输出 output: { // library 名称 library: `${microAppConfigManager.config.name}`, // 输出的文件名称 // 如果你要修改此内容,请看一下下面调用 html-webpack-plugin 代码的相关注释 filename: isProductionEnvironment ? `${JS_PREFIX}/[name].[contenthash:8].bundle.js` : `${JS_PREFIX}/[name].bundle.js`, // 输出文件名称,和 fileName 不同,这里的输出文件为非初始(non-initial)文件,例如我们熟悉的路由懒加载 chunkFilename: isProductionEnvironment ? `${JS_PREFIX}/[name].[contenthash:8].chunk.js` : `${JS_PREFIX}/[name].chunk.js`, // asset/resource 模块以 [hash][ext][query] 文件名发送到输出目录 assetModuleFilename: `${FILE_PREFIX}/[name].[hash][ext]`, // 公共路径 publicPath: microAppConfigManager.config.url, // 输出 umd 类型的 bundle libraryTarget: 'umd', }, plugins: [ // NormalModuleReplacementPlugin 需要我们传入一个回调 // 我们可以在这里将默认的公共的 package 级别依赖重定向到 remote(即共享模块) // 这个回调已被封装成公共方法,它会从你的 app-config 目录下读取 remote 字段,从中找到匹配的 sharedLibraries new NormalModuleReplacementPlugin( /(.*)/, microAppConfigManager.getNormalModuleReplacementPluginCallBack(), ), // 输出 html 入口文件 new HtmlWebpackPlugin({ template: path.resolve(assetPublicPath, 'index.html'), }), // qiankun 底层依赖的 import-html-entry 会取所有 scripts 里面排在最后的 script 作为 entry。 // 具体代码可查看:https://github.com/kuitos/import-html-entry/blob/master/src/index.js#L321 // 但是我们通过 html-webpack-plugin 导出的 HTML,一般情况下是 main 在最后,但是在 webpack module federation 中,会生成一个额外的 entry 排在 main 的后面。 // 从而导致拿不到 main 入口的生命周期函数, 我们可以向 script 标签加入 entry 属性解决这个问题 new AddEntryAttributeWebpackPlugin((src => { return !!(src.match(/main\.(.*)\.bundle.js$/) || src.match('main.bundle.js')); })), // webpack module federation 的插件,其配置基于 app-config 封装,一般无需改动 new ModuleFederationPlugin({ name: microAppConfigManager.config.name, filename: 'module-federation-entry.js', remotes: microAppConfigManager.getModuleFederationRemotes(), exposes: getModuleFederationExposes(microAppConfigManager.config.exposes), }), // 非生产环境下启动 react 热更新插件 // 注意,如果将代码打包到测试服务器上(非生产环境),那么也应该开启这个插件以向主应用插入相关胶水代码 // 由于子应用的 react 是由主应用 share 的,子应用如果需要热更新必须依赖这些胶水代码 // 另外这需要和 src/utils 目录下的 init-common 配合使用,开发者无需额外处理 !isProductionEnvironment && new ReactRefreshWebpackPlugin({ overlay: { sockPath: websocketPath, }, }), // css 压缩 isProductionEnvironment && new MiniCssExtractPlugin({ filename: `${CSS_PREFIX}/${isProductionEnvironment ? '[name].[contenthash].css' : '[name].css'}`, chunkFilename: `${CSS_PREFIX}/${isProductionEnvironment ? '[id].[contenthash].css' : '[id].css'}`, ignoreOrder: true, }), // 定义一些全局的变量,例如版本、打包时间、环境信息 new webpack.DefinePlugin({ // eslint-disable-next-line import/no-dynamic-require __APP_VERSION__: JSON.stringify(require(path.resolve(process.cwd(), 'package.json')).version), __MODE__: JSON.stringify(process.env.NODE_ENV?.toUpperCase()), __BUILD_TIME__: JSON.stringify(moment().format('MMMM Do YYYY, h:mm:ss A')), }), // Moment.js 默认情况下它会打包所有的 locale 文件 // 我们需要用户选择导入特定的区域设置 new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/, }), // 写入共享模块(非 package)的类型定义 new EmitMfExposeWebpackPlugin({ appConfig: microAppConfigManager.config, outputBasePath: isBuildMode ? undefined : assetPublicPath, }), isAnalyzeMode && new BundleAnalyzerPlugin(), ].filter(Boolean), // 解析配置 resolve: { // 扩展名省略 extensions: ['.ts', '.tsx', '.js', '.jsx'], // 实用 alias alias: { '~src': sourcePath, }, }, // 模块解析 module: { rules: [ { oneOf: [ // babel 相关 { test: [/\.[jt]sx?$/], include: [sourcePath], use: { loader: require.resolve('babel-loader'), options: { presets: [ [ require.resolve('@babel/preset-env'), ], [ require.resolve('@babel/preset-react'), { runtime: 'automatic', }, ], [ require.resolve('@babel/preset-typescript'), ], ], plugins: [ [ require.resolve('@babel/plugin-proposal-decorators'), { legacy: true, }, ], [require.resolve('@babel/plugin-transform-runtime')], ].filter(Boolean), }, }, }, // css 预处理相关 css 和 less { test: [/\.(le|c)ss$/], use: [ ...baseStyleConfigRules, { loader: require.resolve('less-loader'), options: { lessOptions: { javascriptEnabled: true, }, }, }, ], }, // css 预处理相关 sass { test: [/\.s[ac]ss$/], use: [ ...baseStyleConfigRules, { loader: require.resolve('sass-loader'), }, ], }, // 其它内容全部以 asset/resource 输出 { exclude: [/^$/, /\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/], type: 'asset/resource', }, ], }, ], }, }; // 合并用户自定义的 webpack 配置 const additionalConfig = microAppConfigManager.config.webpackConfig; if (typeof additionalConfig === 'function') { // 用户可以二次修改 additionalConfig(config as webpack.Configuration); return config; } // @ts-ignore return merge(config, additionalConfig || {}); }; ================================================ FILE: packages/core/src/node/get-module-federation-exposes.ts ================================================ /** * File: get-mf-exposes.ts * Description: 根据配置的目录(src/externals 下), 生成 webpack module federation 配置 * Created: 2021-08-29 13:39:40 * Author: yuzhanglong * Email: yuzl1123@163.com */ import { CachedInputFileSystem, ResolverFactory } from 'enhanced-resolve'; import * as fs from 'fs'; type MfExposesModule = string | { name: string; path: string; } const myResolver = ResolverFactory.createResolver({ fileSystem: new CachedInputFileSystem(fs, 4000), conditionNames: ['node'], extensions: ['.js', '.json', '.node'], useSyncFileSystemCalls: true, mainFields: ['esm', 'module', 'main'], }); export function getModuleFederationExposes(modules: MfExposesModule[]) { const exposes: Record = {}; for (const module of modules) { if (typeof module === 'string') { const key = `./${module}`; const resolveResult = myResolver.resolveSync({}, process.cwd(), module); if (typeof resolveResult !== 'string') { throw new Error(`resolve error: ${module}`); } exposes[key] = resolveResult; } else if (typeof module === 'object') { exposes[`./${module.name}`] = module.path; } } return exposes; } ================================================ FILE: packages/core/src/node/micro-fe-app-config.ts ================================================ import webpack from 'webpack'; type SharedLibraryExpose = string | { name: string; path: string; type?: 'package' | 'module' }; type SharedLibrary = string | { name: string; type?: 'package' | 'module' }; export interface MicroAppConfig { name: string; url: string; remotes: { name: string; url: string; sharedLibraries?: SharedLibrary[] }[]; exposes: SharedLibraryExpose[]; webpackConfig?: Partial | ((config: webpack.Configuration) => void); } /** * 基于 micro app config 生成目录 * * @author yuzhanglong * @date 2021-09-28 00:40:39 * @return object 一个对象, key 表示远程应用的名称,value 表示其入口 url */ export const getModuleFederationRemotes = (microAppConfig: MicroAppConfig) => { const remotes: Record = {}; // example: 'base_app': `base_app@https://base-yzl.vercel.app/base_app_entry.js`, for (const remote of microAppConfig.remotes) { remotes[remote.name] = `${remote.name}@${remote.url.endsWith('/') ? remote.url : `${remote.url}/`}module-federation-entry.js`; } return remotes; }; /** * 获取 share library 的导入替换回调函数 * * 基座暴露一些公共库,我们称为 share library,我们通过 NormalModuleReplacementPlugin 将所有的公共依赖导向相应的 app * * @author yuzhanglong * @date 2021-09-28 00:47:33 */ export const getNormalModuleReplacementPluginCallBack = (microAppConfig: MicroAppConfig) => { return (v: { request: string }) => { // 寻找相应的 request,例如我们要重定向 react,那么我们要从所有的 remotes 中找到第一个其 shareLibrary 中有 react 的远程模块 const externalRemoteApp = microAppConfig.remotes .find(res => { if (!res.sharedLibraries) { return false; } return res.sharedLibraries .some(i => { // 如果直接是 string 类型表示是一个 package if (typeof i === 'string') { return i === v.request; } return i.type === 'package' && i.name === v.request; }); }); if (externalRemoteApp) { // eslint-disable-next-line no-param-reassign v.request = `${externalRemoteApp.name}/${v.request}`; } }; }; /** * 初始化全局 manager 方便调用 * * @author yuzhanglong * @date 2021-09-28 00:53:32 */ export const getMicroAppConfigManager = (microAppConfig: MicroAppConfig) => { return { getNormalModuleReplacementPluginCallBack: () => getNormalModuleReplacementPluginCallBack(microAppConfig), getModuleFederationRemotes: () => getModuleFederationRemotes(microAppConfig), config: microAppConfig, }; }; ================================================ FILE: packages/core/src/node/webpack-command.ts ================================================ import webpack from 'webpack'; import WebpackDevServer from 'webpack-dev-server'; export const webpackPromisify = (config: Record) => { return new Promise((resolve, reject) => { webpack(config, (err: any, state) => { if (err) { console.error(err.stack || err); if (err.details) { console.error(err.details); } reject(); } const info = state.toJson(); if (state.hasErrors()) { console.error(info.errors); reject(); } if (state.hasWarnings()) { console.warn(info.warnings); reject(); } resolve(true); }); }); }; export const webpackBuild = async (config: Record) => { await webpackPromisify(config); }; export const webpackServe = async (config: Record) => { const compiler = webpack(config); const devServerOptions = { ...config.devServer, }; // @ts-ignore const server = new WebpackDevServer(devServerOptions, compiler); server.startCallback(() => { console.log('webpack dev server is running...'); }); }; ================================================ FILE: packages/core/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "baseUrl": "./", "outDir": "lib", "rootDir": "./src", "jsx": "react" }, "include": [ "src/**/*" ] } ================================================ FILE: packages/proxy/README.md ================================================ This project has moved [here](https://github.com/yuzhanglong/attachments/tree/main/packages/proxy) ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - 'packages/*' ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ESNext", "module": "CommonJS", "declaration": true, "moduleResolution": "Node", "resolveJsonModule": true, "types": [ "jest", "node" ], "emitDecoratorMetadata": true, "sourceMap": true, "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "isolatedModules": false, "experimentalDecorators": true, "jsx": "preserve", "downlevelIteration": true } }