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 ( +
+

This is umi docs.

+
+ ); +}; + +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" +}