diff --git a/umijs-react-ts-demo/app1/.editorconfig b/umijs-react-ts-demo/app1/.editorconfig
new file mode 100755
index 00000000000..7e3649acc2c
--- /dev/null
+++ b/umijs-react-ts-demo/app1/.editorconfig
@@ -0,0 +1,16 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
+
+[Makefile]
+indent_style = tab
diff --git a/umijs-react-ts-demo/app1/.gitignore b/umijs-react-ts-demo/app1/.gitignore
new file mode 100644
index 00000000000..bee1cf61ce7
--- /dev/null
+++ b/umijs-react-ts-demo/app1/.gitignore
@@ -0,0 +1,20 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/npm-debug.log*
+/yarn-error.log
+/yarn.lock
+/package-lock.json
+
+# production
+/dist
+
+# misc
+.DS_Store
+
+# umi
+/src/.umi
+/src/.umi-production
+/src/.umi-test
+/.env.local
diff --git a/umijs-react-ts-demo/app1/.prettierignore b/umijs-react-ts-demo/app1/.prettierignore
new file mode 100644
index 00000000000..0d4222f5443
--- /dev/null
+++ b/umijs-react-ts-demo/app1/.prettierignore
@@ -0,0 +1,8 @@
+**/*.md
+**/*.svg
+**/*.ejs
+**/*.html
+package.json
+.umi
+.umi-production
+.umi-test
diff --git a/umijs-react-ts-demo/app1/.umirc.ts b/umijs-react-ts-demo/app1/.umirc.ts
new file mode 100644
index 00000000000..f99dcb1c16a
--- /dev/null
+++ b/umijs-react-ts-demo/app1/.umirc.ts
@@ -0,0 +1,64 @@
+import { defineConfig } from 'umi';
+import { join } from 'path';
+
+const pkg = require('../package.json');
+const deps = pkg.dependencies;
+
+function webpackDeepPathImportWorkaround() {
+ const mod = require('module');
+ const resolveFilename = mod._resolveFilename;
+
+ mod._resolveFilename = function (request: string, parent: any, isMain: boolean, options: any) {
+ let newRequest = request;
+
+ if (/\/@umijs\/deps\/compiled\/(lib|schemas)\//.test(request)) {
+ newRequest = request.replace(
+ /.*@umijs\/deps\/compiled\/(lib|schemas)\//,
+ join(__dirname, './node_modules/webpack/$1', './'),
+ );
+ }
+
+ return resolveFilename.call(mod, newRequest, parent, isMain, options);
+ };
+}
+
+export default defineConfig({
+ nodeModulesTransform: {
+ type: 'none',
+ },
+ routes: [
+ { path: '/', component: '@/pages/index' },
+ ],
+ fastRefresh: {},
+ webpack5: {},
+ chainWebpack: (config, { webpack }: any) => {
+ // reference https://github.com/umijs/umi/issues/10583
+ webpackDeepPathImportWorkaround();
+
+ // error: Not found module './src/pages/index'
+ // const { ModuleFederationPlugin } = require('@module-federation/enhanced');
+
+ config.plugin('mf').use(webpack.container.ModuleFederationPlugin, [{
+ name: 'app1',
+ filename: 'remoteEntry.js',
+ exposes: {
+ './Header': './src/pages/index',
+ },
+ shared: {
+ ...deps,
+ react: {
+ eager: true,
+ singleton: true,
+ requiredVersion: '^17.0.0',
+ version: '0',
+ },
+ 'react-dom': {
+ eager: true,
+ singleton: true,
+ requiredVersion: '^17.0.0',
+ version: '0',
+ },
+ },
+ }])
+ }
+});
diff --git a/umijs-react-ts-demo/app1/README.md b/umijs-react-ts-demo/app1/README.md
new file mode 100644
index 00000000000..07afeb7fd6d
--- /dev/null
+++ b/umijs-react-ts-demo/app1/README.md
@@ -0,0 +1,15 @@
+# umi project
+
+## Getting Started
+
+Install dependencies,
+
+```bash
+$ yarn
+```
+
+Start the dev server,
+
+```bash
+$ yarn start
+```
diff --git a/umijs-react-ts-demo/app1/mock/.gitkeep b/umijs-react-ts-demo/app1/mock/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/umijs-react-ts-demo/app1/package.json b/umijs-react-ts-demo/app1/package.json
new file mode 100644
index 00000000000..f586e45dec0
--- /dev/null
+++ b/umijs-react-ts-demo/app1/package.json
@@ -0,0 +1,41 @@
+{
+ "private": true,
+ "scripts": {
+ "start": "umi dev",
+ "build": "umi build",
+ "postinstall": "umi generate tmp",
+ "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'",
+ "test": "umi-test",
+ "test:coverage": "umi-test --coverage"
+ },
+ "gitHooks": {
+ "pre-commit": "lint-staged"
+ },
+ "lint-staged": {
+ "*.{js,jsx,less,md,json}": [
+ "prettier --write"
+ ],
+ "*.ts?(x)": [
+ "prettier --parser=typescript --write"
+ ]
+ },
+ "dependencies": {
+ "@ant-design/pro-layout": "^6.5.0",
+ "react": "17.x",
+ "react-dom": "17.x",
+ "umi": "^3.5.41"
+ },
+ "devDependencies": {
+ "@module-federation/enhanced": "^0.0.10",
+ "@module-federation/runtime": "^0.0.10",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "@umijs/preset-react": "1.x",
+ "@umijs/test": "^3.5.41",
+ "lint-staged": "^10.0.7",
+ "prettier": "^2.2.0",
+ "typescript": "^4.1.2",
+ "webpack": "5.89.0",
+ "yorkie": "^2.0.0"
+ }
+}
diff --git a/umijs-react-ts-demo/app1/src/pages/index.less b/umijs-react-ts-demo/app1/src/pages/index.less
new file mode 100644
index 00000000000..586302bfc88
--- /dev/null
+++ b/umijs-react-ts-demo/app1/src/pages/index.less
@@ -0,0 +1,3 @@
+.title {
+ background: rgb(121, 242, 157);
+}
diff --git a/umijs-react-ts-demo/app1/src/pages/index.tsx b/umijs-react-ts-demo/app1/src/pages/index.tsx
new file mode 100644
index 00000000000..ebdd169f7de
--- /dev/null
+++ b/umijs-react-ts-demo/app1/src/pages/index.tsx
@@ -0,0 +1,9 @@
+import styles from './index.less';
+
+export default function IndexPage() {
+ return (
+
+
Page index
+
+ );
+}
diff --git a/umijs-react-ts-demo/app1/tsconfig.json b/umijs-react-ts-demo/app1/tsconfig.json
new file mode 100644
index 00000000000..6d42f8cb4b2
--- /dev/null
+++ b/umijs-react-ts-demo/app1/tsconfig.json
@@ -0,0 +1,37 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "importHelpers": true,
+ "jsx": "react-jsx",
+ "esModuleInterop": true,
+ "sourceMap": true,
+ "baseUrl": "./",
+ "strict": true,
+ "paths": {
+ "@/*": ["src/*"],
+ "@@/*": ["src/.umi/*"]
+ },
+ "allowSyntheticDefaultImports": true
+ },
+ "include": [
+ "mock/**/*",
+ "src/**/*",
+ "config/**/*",
+ ".umirc.ts",
+ "typings.d.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "lib",
+ "es",
+ "dist",
+ "typings",
+ "**/__test__",
+ "test",
+ "docs",
+ "tests"
+ ]
+}
diff --git a/umijs-react-ts-demo/app1/typings.d.ts b/umijs-react-ts-demo/app1/typings.d.ts
new file mode 100644
index 00000000000..06c8a5b8ca6
--- /dev/null
+++ b/umijs-react-ts-demo/app1/typings.d.ts
@@ -0,0 +1,10 @@
+declare module '*.css';
+declare module '*.less';
+declare module '*.png';
+declare module '*.svg' {
+ export function ReactComponent(
+ props: React.SVGProps,
+ ): React.ReactElement;
+ const url: string;
+ export default url;
+}
diff --git a/umijs-react-ts-demo/app2/.gitignore b/umijs-react-ts-demo/app2/.gitignore
new file mode 100644
index 00000000000..0dc2a3f9357
--- /dev/null
+++ b/umijs-react-ts-demo/app2/.gitignore
@@ -0,0 +1,9 @@
+/node_modules
+/.env.local
+/.umirc.local.ts
+/config/config.local.ts
+/src/.umi
+/src/.umi-production
+/src/.umi-test
+/dist
+.swc
diff --git a/umijs-react-ts-demo/app2/.umirc.ts b/umijs-react-ts-demo/app2/.umirc.ts
new file mode 100644
index 00000000000..a1a648af828
--- /dev/null
+++ b/umijs-react-ts-demo/app2/.umirc.ts
@@ -0,0 +1,64 @@
+import { defineConfig } from "umi";
+import { join } from 'path';
+
+const externals = {
+ react: "window.React",
+ "react-dom": "window.ReactDOM",
+}
+
+function webpackDeepPathImportWorkaround() {
+ const mod = require('module');
+ const resolveFilename = mod._resolveFilename;
+
+ mod._resolveFilename = function (request: string, parent: any, isMain: boolean, options: any) {
+ let newRequest = request;
+
+ if (/@umijs\/bundler-webpack\/compiled\/(lib|schemas)\//.test(request)) {
+ newRequest = request.replace(
+ /.*\/@umijs\/bundler-webpack\/compiled\/(lib|schemas)\//,
+ join(__dirname, './node_modules/webpack/$1', './'),
+ );
+ }
+
+ return resolveFilename.call(mod, newRequest, parent, isMain, options);
+ };
+}
+
+export default defineConfig({
+ routes: [
+ { path: "/", component: "index" },
+ { path: "/docs", component: "docs" },
+ ],
+ npmClient: 'pnpm',
+ chainWebpack: (config: any) => {
+ // reference https://github.com/umijs/umi/issues/10583
+ webpackDeepPathImportWorkaround();
+
+ const { ModuleFederationPlugin } = require('@module-federation/enhanced');
+
+ config.plugin('mf').use(ModuleFederationPlugin, [{
+ remotes: {
+ app1: 'app1@http://localhost:3001/remoteEntry.js'
+ },
+ shared: {
+ react: {
+ import: false,
+ singleton: true,
+ version: '18.2.0',
+ },
+ 'react-dom': {
+ import: false,
+ singleton: true,
+ version: "16.14.0",
+ },
+ },
+ runtimePlugins: [require.resolve('./runtime.js')],
+ }])
+ },
+ headScripts: [
+ "https://cdn.bootcdn.net/ajax/libs/react/18.2.0/umd/react.production.min.js",
+ "https://cdn.bootcdn.net/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js",
+ ],
+ externals,
+ mfsu: false
+});
diff --git a/umijs-react-ts-demo/app2/package.json b/umijs-react-ts-demo/app2/package.json
new file mode 100644
index 00000000000..f520cadad50
--- /dev/null
+++ b/umijs-react-ts-demo/app2/package.json
@@ -0,0 +1,22 @@
+{
+ "private": true,
+ "author": "jie.q ",
+ "scripts": {
+ "dev": "umi dev",
+ "build": "umi build",
+ "postinstall": "umi setup",
+ "setup": "umi setup",
+ "start": "npm run dev"
+ },
+ "dependencies": {
+ "umi": "^4.1.1"
+ },
+ "devDependencies": {
+ "@module-federation/enhanced": "^0.0.10",
+ "@module-federation/runtime": "^0.0.10",
+ "@types/react": "18.2.48",
+ "@types/react-dom": "18.2.18",
+ "typescript": "^5.3.3",
+ "webpack": "5.89.0"
+ }
+}
diff --git a/umijs-react-ts-demo/app2/plugin.ts b/umijs-react-ts-demo/app2/plugin.ts
new file mode 100644
index 00000000000..f8a69ffa1cd
--- /dev/null
+++ b/umijs-react-ts-demo/app2/plugin.ts
@@ -0,0 +1,32 @@
+// See https://umijs.org/docs/guides/plugins
+import type { IApi } from 'umi';
+import { resolve } from 'path';
+import { readFileSync } from 'fs';
+// import { ModuleFederationPlugin } from '@module-federation/enhanced';
+
+export default (api: IApi) => {
+ // 实现 umi-plugin-mf-bootstrap, umi-plugin-mf-bootstrap-r 在 umi@4 下不兼容,会在 tmp 目录下生成 plugin-mfBootstrapR
+ api.onGenerateFiles(() => {
+ const path = api.env === 'production' ? './src/.umi-production/umi.ts' : './src/.umi/umi.ts';
+ const buffer = readFileSync(resolve(path));
+ const c = String(buffer);
+ // 防止热更新重复覆盖
+ if (c.includes('const { bootstrap, mount, unmount, update } = await import("./index")')) {
+ return;
+ }
+
+ api.writeTmpFile({
+ path: 'index.ts',
+ content: c,
+ noPluginDir: true
+ });
+ api.writeTmpFile({
+ path: 'umi.ts',
+ content: `
+const { bootstrap, mount, unmount, update } = await import("./index")
+export { bootstrap, mount, unmount, update }
+ `,
+ noPluginDir: true
+ });
+ });
+};
diff --git a/umijs-react-ts-demo/app2/runtime.js b/umijs-react-ts-demo/app2/runtime.js
new file mode 100644
index 00000000000..09c43820400
--- /dev/null
+++ b/umijs-react-ts-demo/app2/runtime.js
@@ -0,0 +1,21 @@
+// import { FederationRuntimePlugin } from '@module-federation/runtime/types';
+
+export default function () {
+ return {
+ name: 'umd-library-shared-plugin',
+ resolveShare(args) {
+ const { shareScopeMap, scope, pkgName, version } = args;
+
+ if (!['react', 'react-dom'].includes(pkgName)) {
+ return args;
+ }
+
+ args.resolver = function () {
+ shareScopeMap[scope][pkgName][version] = pkgName === 'react' ? window.React : window.ReactDOM; // replace local share scope manually with desired module
+ return shareScopeMap[scope][pkgName][version];
+ };
+
+ return args;
+ },
+ };
+}
diff --git a/umijs-react-ts-demo/app2/src/assets/yay.jpg b/umijs-react-ts-demo/app2/src/assets/yay.jpg
new file mode 100644
index 00000000000..e72bd8ffaec
Binary files /dev/null and b/umijs-react-ts-demo/app2/src/assets/yay.jpg differ
diff --git a/umijs-react-ts-demo/app2/src/layouts/index.less b/umijs-react-ts-demo/app2/src/layouts/index.less
new file mode 100644
index 00000000000..2e1d3f80e21
--- /dev/null
+++ b/umijs-react-ts-demo/app2/src/layouts/index.less
@@ -0,0 +1,10 @@
+.navs {
+ ul {
+ padding: 0;
+ list-style: none;
+ display: flex;
+ }
+ li {
+ margin-right: 1em;
+ }
+}
diff --git a/umijs-react-ts-demo/app2/src/layouts/index.tsx b/umijs-react-ts-demo/app2/src/layouts/index.tsx
new file mode 100644
index 00000000000..f4e26ef35fc
--- /dev/null
+++ b/umijs-react-ts-demo/app2/src/layouts/index.tsx
@@ -0,0 +1,21 @@
+import { Link, Outlet } from 'umi';
+import styles from './index.less';
+
+export default function Layout() {
+ return (
+
+
+ -
+ Home
+
+ -
+ Docs
+
+ -
+ Github
+
+
+
+
+ );
+}
diff --git a/umijs-react-ts-demo/app2/src/pages/docs.tsx b/umijs-react-ts-demo/app2/src/pages/docs.tsx
new file mode 100644
index 00000000000..a9b00701449
--- /dev/null
+++ b/umijs-react-ts-demo/app2/src/pages/docs.tsx
@@ -0,0 +1,9 @@
+const DocsPage = () => {
+ return (
+
+ );
+};
+
+export default DocsPage;
diff --git a/umijs-react-ts-demo/app2/src/pages/index.tsx b/umijs-react-ts-demo/app2/src/pages/index.tsx
new file mode 100644
index 00000000000..de76ceb184b
--- /dev/null
+++ b/umijs-react-ts-demo/app2/src/pages/index.tsx
@@ -0,0 +1,19 @@
+import yayJpg from '../assets/yay.jpg';
+import Header from 'app1/Header';
+
+export default function HomePage() {
+ return (
+ <>
+
+
+
Yay! Welcome to umi!
+
+
+
+
+ To get started, edit pages/index.tsx
and save to reload.
+
+
+ >
+ );
+}
diff --git a/umijs-react-ts-demo/app2/tsconfig.json b/umijs-react-ts-demo/app2/tsconfig.json
new file mode 100644
index 00000000000..133cfd82a23
--- /dev/null
+++ b/umijs-react-ts-demo/app2/tsconfig.json
@@ -0,0 +1,3 @@
+{
+ "extends": "./src/.umi/tsconfig.json"
+}
diff --git a/umijs-react-ts-demo/app2/typings.d.ts b/umijs-react-ts-demo/app2/typings.d.ts
new file mode 100644
index 00000000000..ba96b3b44e2
--- /dev/null
+++ b/umijs-react-ts-demo/app2/typings.d.ts
@@ -0,0 +1,6 @@
+import 'umi/typings';
+
+declare module "app1/Header" {
+ const Header: ComponentType;
+ export default Header;
+}
\ No newline at end of file
diff --git a/umijs-react-ts-demo/package.json b/umijs-react-ts-demo/package.json
new file mode 100644
index 00000000000..aa5981e94ca
--- /dev/null
+++ b/umijs-react-ts-demo/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "umijs-react-ts-demo",
+ "version": "1.0.0",
+ "description": "Two apps, one using UmiJs 3 and the other using UmiJs 4",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}