diff --git a/.changeset/config.json b/.changeset/config.json index 67a93fc6d..f2a2a8b3e 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -18,6 +18,7 @@ "@examples/aclass", "@examples/alita2", "@examples/boilerplate", + "@examples/extends-app", "@examples/helmet", "@examples/state", "@examples/legacy", diff --git a/.changeset/smart-apes-cheat.md b/.changeset/smart-apes-cheat.md new file mode 100644 index 000000000..e74ae3276 --- /dev/null +++ b/.changeset/smart-apes-cheat.md @@ -0,0 +1,7 @@ +--- +'@alita/plugin-extends-app': patch +'@alita/plugins': patch +'alita': patch +--- + +feat: add plugin extends app diff --git a/examples/extends-app/.gitignore b/examples/extends-app/.gitignore new file mode 100644 index 000000000..9e8a6f26e --- /dev/null +++ b/examples/extends-app/.gitignore @@ -0,0 +1,7 @@ +/.env.local +/.umirc.local.ts +/.umirc.local.js +/config/config.local.ts +/config/config.local.js +/src/.umi +/.umi diff --git a/examples/extends-app/config/config.ts b/examples/extends-app/config/config.ts new file mode 100644 index 000000000..f348f58c0 --- /dev/null +++ b/examples/extends-app/config/config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'alita'; + +export default defineConfig({ + appType: 'h5', + keepalive: [/./], + extendsApp: { + root: 'root', + }, + plugins: ['@alita/plugin-extends-app'], + mobileLayout: true, + legacyBuild: false, + // mainPath:'users', + mfsu: {}, + hash: false, +}); diff --git a/examples/extends-app/mock/app.ts b/examples/extends-app/mock/app.ts new file mode 100644 index 000000000..d0c39305d --- /dev/null +++ b/examples/extends-app/mock/app.ts @@ -0,0 +1,5 @@ +export default { + '/api/hello': { + text: 'Alita', + }, +}; diff --git a/examples/extends-app/package.json b/examples/extends-app/package.json new file mode 100644 index 000000000..b1e5e7b91 --- /dev/null +++ b/examples/extends-app/package.json @@ -0,0 +1,17 @@ +{ + "name": "@examples/extends-app", + "private": true, + "scripts": { + "dev": "alita dev", + "build": "alita build", + "plugin": "alita plugin list", + "start": "npm run dev" + }, + "dependencies": { + "@alita/flow": "workspace:*", + "@alita/plugin-extends-app": "workspace:*", + "ahooks": "^3.0.8", + "alita": "workspace:*", + "antd-mobile": "^5.10.0" + } +} diff --git a/examples/extends-app/root/pages/index/index.less b/examples/extends-app/root/pages/index/index.less new file mode 100644 index 000000000..41929ad6d --- /dev/null +++ b/examples/extends-app/root/pages/index/index.less @@ -0,0 +1,6 @@ +.title { + font-size: 30px; +} +.adm-button{ + font-size: 30px +} \ No newline at end of file diff --git a/examples/extends-app/root/pages/index/index.tsx b/examples/extends-app/root/pages/index/index.tsx new file mode 100644 index 000000000..308ed053a --- /dev/null +++ b/examples/extends-app/root/pages/index/index.tsx @@ -0,0 +1,21 @@ +import { Button } from 'antd-mobile'; +import React, { useState } from 'react'; +import styles from './index.less'; +export default () => { + const [count, setCount] = useState(0); + return ( +
+ exclude routes hello + +
+ ); +}; diff --git a/examples/extends-app/src/app.tsx b/examples/extends-app/src/app.tsx new file mode 100644 index 000000000..c69c55bae --- /dev/null +++ b/examples/extends-app/src/app.tsx @@ -0,0 +1,88 @@ +import HomeGary from '@/assets/demoIcon/home.png'; +import HomeBlue from '@/assets/demoIcon/home1.png'; +import ListGary from '@/assets/demoIcon/list.png'; +import ListBlue from '@/assets/demoIcon/list1.png'; +import type { + NavBarListItem, + NavBarProps, + TabBarListItem, + TabBarProps, + TitleListItem, +} from 'alita'; + +export const request = { + prefix: '/api', + method: 'get', + errorHandler: (error) => { + // 集中处理错误 + console.log(11111111); + console.log(error); + }, +}; + +const titleList: TitleListItem[] = [ + { + pagePath: '/', + title: '首页', + }, + { + pagePath: '/hello', + title: 'Hi', + }, +]; +const navList: NavBarListItem[] = [ + { + pagePath: '/', + navBar: { + pageBackground: '#fff', + }, + }, + { + pagePath: '/hello', + navBar: { + pageBackground: '#e5e5e5', + }, + }, +]; +const navBar: NavBarProps = { + navList, + fixed: false, + onLeftClick: () => { + // router.goBack(); + }, +}; +const tabList: TabBarListItem[] = [ + { + pagePath: '/', + text: '首页', + iconPath: HomeGary, + selectedIconPath: HomeBlue, + title: '首页', + iconSize: '', + badge: '', + }, + { + pagePath: '/hello', + text: 'Hi', + iconPath: ListGary, + selectedIconPath: ListBlue, + title: 'Hi', + iconSize: '', + badge: '', + }, +]; + +const tabBar: TabBarProps = { + color: `#999999`, + selectedColor: '#00A0FF', + borderStyle: 'white', + position: 'bottom', + list: tabList, +}; + +export const mobileLayout = { + documentTitle: '默认标题', + navBar, + tabBar, + titleList, +}; diff --git a/examples/extends-app/src/assets/demoIcon/home.png b/examples/extends-app/src/assets/demoIcon/home.png new file mode 100644 index 000000000..28d67bf89 Binary files /dev/null and b/examples/extends-app/src/assets/demoIcon/home.png differ diff --git a/examples/extends-app/src/assets/demoIcon/home1.png b/examples/extends-app/src/assets/demoIcon/home1.png new file mode 100644 index 000000000..2181ee520 Binary files /dev/null and b/examples/extends-app/src/assets/demoIcon/home1.png differ diff --git a/examples/extends-app/src/assets/demoIcon/list.png b/examples/extends-app/src/assets/demoIcon/list.png new file mode 100644 index 000000000..54ae90e12 Binary files /dev/null and b/examples/extends-app/src/assets/demoIcon/list.png differ diff --git a/examples/extends-app/src/assets/demoIcon/list1.png b/examples/extends-app/src/assets/demoIcon/list1.png new file mode 100644 index 000000000..3563aecba Binary files /dev/null and b/examples/extends-app/src/assets/demoIcon/list1.png differ diff --git a/examples/extends-app/src/models/index.ts b/examples/extends-app/src/models/index.ts new file mode 100644 index 000000000..ac4e990e7 --- /dev/null +++ b/examples/extends-app/src/models/index.ts @@ -0,0 +1,34 @@ +import { query } from '@/services/api'; +import type { DvaModel } from 'alita'; + +export interface IndexModelState { + name: string; +} + +const IndexModel: DvaModel = { + namespace: 'index', + + state: { + name: 'Hello Alita', + }, + + effects: { + *query({ payload }, { call, put }) { + const data = yield call(query, payload); + yield put({ + type: 'save', + payload: { name: data.text }, + }); + }, + }, + reducers: { + save(state, action) { + return { + ...state, + ...action.payload, + }; + }, + }, +}; + +export default IndexModel; diff --git a/examples/extends-app/src/models/todo.ts b/examples/extends-app/src/models/todo.ts new file mode 100644 index 000000000..186f7bc7e --- /dev/null +++ b/examples/extends-app/src/models/todo.ts @@ -0,0 +1,9 @@ +import { useState } from 'react'; + +export default function () { + const [todos, setTodos] = useState(['foo', 'bar']); + return { + todos, + setTodos, + }; +} diff --git a/examples/extends-app/src/pages/global.less b/examples/extends-app/src/pages/global.less new file mode 100644 index 000000000..03597d36d --- /dev/null +++ b/examples/extends-app/src/pages/global.less @@ -0,0 +1,3 @@ +a { + color: green; +} diff --git a/examples/extends-app/src/pages/index/index.less b/examples/extends-app/src/pages/index/index.less new file mode 100644 index 000000000..41929ad6d --- /dev/null +++ b/examples/extends-app/src/pages/index/index.less @@ -0,0 +1,6 @@ +.title { + font-size: 30px; +} +.adm-button{ + font-size: 30px +} \ No newline at end of file diff --git a/examples/extends-app/src/pages/index/index.tsx b/examples/extends-app/src/pages/index/index.tsx new file mode 100644 index 000000000..29983f8fa --- /dev/null +++ b/examples/extends-app/src/pages/index/index.tsx @@ -0,0 +1,21 @@ +import { Button } from 'antd-mobile'; +import React, { useState } from 'react'; +import styles from './index.less'; +export default () => { + const [count, setCount] = useState(0); + return ( +
+ Hello Alita + +
+ ); +}; diff --git a/examples/extends-app/src/services/api.ts b/examples/extends-app/src/services/api.ts new file mode 100644 index 000000000..0da74439a --- /dev/null +++ b/examples/extends-app/src/services/api.ts @@ -0,0 +1,5 @@ +import { request } from '@@/plugin-request'; + +export async function query(): Promise { + return request('/hello'); +} diff --git a/examples/extends-app/tsconfig.json b/examples/extends-app/tsconfig.json new file mode 100644 index 000000000..7bb06b898 --- /dev/null +++ b/examples/extends-app/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "dist", + "sourceMap": true, + "jsx": "react", + "declaration": false, + "module": "esnext", + "moduleResolution": "node", + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "importHelpers": true, + "target": "es2017", + "typeRoots": ["node_modules/@types"], + "lib": ["es2018", "dom"], + "allowSyntheticDefaultImports": true, + "rootDirs": ["/src", "/root", "/test", "/mock", "./typings"], + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "allowJs": true, + "strict": true, + "paths": { + "@/*": ["./src/*"], + "@@/*": ["./src/.umi/*"], + "alita": ["./src/.umi/*"] + } + } +} diff --git a/examples/extends-app/typings.d.ts b/examples/extends-app/typings.d.ts new file mode 100644 index 000000000..2feef17a1 --- /dev/null +++ b/examples/extends-app/typings.d.ts @@ -0,0 +1,13 @@ +declare module '*.css'; +declare module '*.less'; +declare module '*.scss'; +declare module '*.sass'; +declare module '*.svg'; +declare module '*.png'; +declare module '*.jpg'; +declare module '*.jpeg'; +declare module '*.gif'; +declare module '*.bmp'; +declare module '*.tiff'; +declare module '*.json'; + diff --git a/packages/alita/src/features/compatibleMako.ts b/packages/alita/src/features/compatibleMako.ts index 64381c913..64203c0c5 100644 --- a/packages/alita/src/features/compatibleMako.ts +++ b/packages/alita/src/features/compatibleMako.ts @@ -1,5 +1,7 @@ import { IApi } from 'umi'; -import { dirname } from 'path'; +import { dirname, join, resolve } from 'path'; +import { fsExtra, glob } from '@umijs/utils'; +import { copyFileSync, statSync } from 'fs'; export default (api: IApi) => { // 强制关闭 @@ -17,6 +19,43 @@ export default (api: IApi) => { ); } } + if (api.userConfig.copy) { + const copy = [ + { + from: join( + dirname(require.resolve('pdfjs-dist/package.json')), + 'cmaps', + ), + to: 'build/static/cmaps/', + }, + ]; + const copyDirectory = (opts: any) => { + const files = glob.sync('**/*', { + cwd: opts.path, + dot: true, + }); + files.forEach((file: any) => { + const absFile = join(opts.path, file); + if (statSync(absFile).isDirectory()) return; + const absTarget = join(opts.target, file); + fsExtra.mkdirpSync(dirname(absTarget)); + copyFileSync(absFile, absTarget); + }); + }; + + copy.forEach((file) => { + const sourcePath = resolve(file.from); + const destinationPath = resolve( + api.userConfig.outputPath || api.paths.absOutputPath, + file.to, + ); + copyDirectory({ + path: sourcePath, + target: destinationPath, + }); + }); + } + memo.copy = []; return memo; }); } diff --git a/packages/plugin-extends-app/README.md b/packages/plugin-extends-app/README.md new file mode 100644 index 000000000..31e0083e7 --- /dev/null +++ b/packages/plugin-extends-app/README.md @@ -0,0 +1,3 @@ +# @alita/plugin-exclude-pages + +See our website [alitajs](https://alitajs.com) for more information. diff --git a/packages/plugin-extends-app/package.json b/packages/plugin-extends-app/package.json new file mode 100644 index 000000000..1d3852d31 --- /dev/null +++ b/packages/plugin-extends-app/package.json @@ -0,0 +1,33 @@ +{ + "name": "@alita/plugin-extends-app", + "version": "3.0.1", + "description": "@alita/plugin-extends-app", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "pnpm tsc", + "build:deps": "umi-scripts bundleDeps", + "dev": "pnpm build --watch" + }, + "repository": { + "type": "git", + "url": "https://github.com/alitajs/alita" + }, + "authors": [ + "xiaohuoni (https://github.com/xiaohuoni)" + ], + "license": "MIT", + "bugs": "https://github.com/alitajs/alita/issues", + "homepage": "https://github.com/alitajs/alita/tree/master/packages/plugin-extends-app#readme", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@umijs/core": "4.3.11", + "@umijs/preset-umi": "4.3.11", + "@umijs/utils": "4.3.11" + } +} diff --git a/packages/plugin-extends-app/src/index.ts b/packages/plugin-extends-app/src/index.ts new file mode 100644 index 000000000..d931ace52 --- /dev/null +++ b/packages/plugin-extends-app/src/index.ts @@ -0,0 +1,114 @@ +import { IApi } from 'umi'; +import { getConventionRoutes } from '@umijs/core'; +import { getModuleExports } from '@umijs/preset-umi/dist/features/tmpFiles/getModuleExports'; +import { resolve, winPath } from '@umijs/utils'; +import { isAbsolute, join } from 'path'; +import { existsSync, readFileSync } from 'fs'; + +export default (api: IApi) => { + api.describe({ + key: 'extendsApp', + config: { + schema({ zod }) { + return zod + .object({ + root: zod.string(), + }) + .partial(); + }, + default: { + root: 'root', + }, + }, + }); + + const { extendsApp = {} } = api.userConfig; + const { root = 'root' } = extendsApp; + const rootCwd = winPath(join(api.paths.cwd, root)); + if (existsSync(rootCwd)) { + const otherPagesPath = winPath(join(rootCwd, 'pages')); + api.addTmpGenerateWatcherPaths(() => otherPagesPath); + api.modifyRoutes(async (memo) => { + // 配置式不支持 + if (api.userConfig.routes) return memo; + const prefix = 'pages/'; + const routes = getConventionRoutes({ + base: otherPagesPath, + exclude: api.config.conventionRoutes?.exclude, + prefix, + }); + function localPath(path: string) { + if (path.charAt(0) !== '.') { + return `./${path}`; + } + { + return path; + } + } + // 取 memo 第一个路由的 parentId,这在 alita 的场景是可以保证准确性的 + let parentId; + for (const id of Object.keys(memo)) { + if (!memo[id].isLayout) { + parentId = memo[id].parentId; + break; + } + } + for (const id of Object.keys(routes)) { + if (routes[id].file) { + let file = routes[id].file; + const basedir = rootCwd; + + if (!isAbsolute(file)) { + if (file.startsWith('@/')) { + file = file.replace('@/', '../'); + } + file = resolve.sync(localPath(file), { + basedir, + extensions: ['.js', '.jsx', '.tsx', '.ts', '.vue'], + }); + } + + const isJSFile = /.[jt]sx?$/.test(file); + // layout route 这里不需要这些属性 + if (!routes[id].isLayout) { + routes[id].__content = readFileSync(file, 'utf-8'); + routes[id].__isJSFile = isJSFile; + routes[id].parentId = parentId; + } + routes[id].__absFile = winPath(file); + // 给绝对路径不受 prefix 影响 + // packages/preset-umi/src/features/tmpFiles/tmpFiles.ts#436 + routes[id].file = routes[id].__absFile; + + const enableSSR = api.config.ssr; + const enableClientLoader = api.config.clientLoader; + const enableRouteProps = !api.userConfig.routes; + const needCollectExports = + enableSSR || enableClientLoader || enableRouteProps; + if (needCollectExports) { + const exports = + isJSFile && existsSync(file) + ? await getModuleExports({ + file, + }) + : []; + if (enableSSR) { + routes[id].hasServerLoader = exports.includes('serverLoader'); + routes[id].hasMetadataLoader = exports.includes('metadataLoader'); + } + if (enableClientLoader && exports.includes('clientLoader')) { + routes[id].clientLoader = `clientLoaders['${id}']`; + } + if (enableRouteProps && exports.includes('routeProps')) { + routes[id].routeProps = `routeProps['${id}']`; + } + } + // 同路由,则扩展覆盖原有的数据 + const key = id.substring(prefix.length); + memo[key] = routes[id]; + } + } + return memo; + }); + } +}; diff --git a/packages/plugin-extends-app/tsconfig.json b/packages/plugin-extends-app/tsconfig.json new file mode 100644 index 000000000..792172fb8 --- /dev/null +++ b/packages/plugin-extends-app/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b50bfb43b..a536a7cd9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -219,6 +219,24 @@ importers: specifier: ^5.10.0 version: 5.31.1(react-dom@17.0.2)(react@17.0.2) + examples/extends-app: + dependencies: + '@alita/flow': + specifier: workspace:* + version: link:../../packages/flow + '@alita/plugin-extends-app': + specifier: workspace:* + version: link:../../packages/plugin-extends-app + ahooks: + specifier: ^3.0.8 + version: 3.7.8(react@17.0.2) + alita: + specifier: workspace:* + version: link:../../packages/alita + antd-mobile: + specifier: ^5.10.0 + version: 5.31.1(react-dom@17.0.2)(react@17.0.2) + examples/helmet: dependencies: alita: @@ -578,6 +596,18 @@ importers: specifier: ^4.19.1 version: 4.19.1 + packages/plugin-extends-app: + dependencies: + '@umijs/core': + specifier: 4.3.11 + version: 4.3.11 + '@umijs/preset-umi': + specifier: 4.3.11 + version: 4.3.11(@types/node@17.0.45)(@types/react@18.2.14)(typescript@4.9.5)(webpack@5.90.3) + '@umijs/utils': + specifier: 4.3.11 + version: 4.3.11 + packages/plugin-lowcode: dependencies: '@alita/types': @@ -1326,14 +1356,14 @@ packages: semver: 6.3.1 dev: false - /@babel/eslint-parser@7.22.15(@babel/core@7.23.2)(eslint@7.32.0): + /@babel/eslint-parser@7.22.15(@babel/core@7.23.6)(eslint@7.32.0): resolution: {integrity: sha512-yc8OOBIQk1EcRrpizuARSQS0TWAcOMpEJ1aafhNznaeYkeL+OhqnDObGFylB8ka8VFF/sZc+S4RzHyO+3LjQxg==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} peerDependencies: '@babel/core': ^7.11.0 eslint: ^7.5.0 || ^8.0.0 dependencies: - '@babel/core': 7.23.2 + '@babel/core': 7.23.6 '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 eslint: 7.32.0 eslint-visitor-keys: 2.1.0 @@ -4511,7 +4541,7 @@ packages: resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} dependencies: '@esbuild-kit/core-utils': 3.1.0 - get-tsconfig: 4.6.2 + get-tsconfig: 4.7.5 /@esbuild-kit/core-utils@3.1.0: resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==} @@ -4523,7 +4553,7 @@ packages: resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} dependencies: '@esbuild-kit/core-utils': 3.1.0 - get-tsconfig: 4.6.2 + get-tsconfig: 4.7.5 /@esbuild/aix-ppc64@0.23.0: resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} @@ -10219,7 +10249,7 @@ packages: /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 get-intrinsic: 1.2.1 /call-me-maybe@1.0.2: @@ -12265,8 +12295,8 @@ packages: peerDependencies: eslint: '>=7.0.0' dependencies: - '@babel/core': 7.23.2 - '@babel/eslint-parser': 7.22.15(@babel/core@7.23.2)(eslint@7.32.0) + '@babel/core': 7.23.6 + '@babel/eslint-parser': 7.22.15(@babel/core@7.23.6)(eslint@7.32.0) eslint: 7.32.0 eslint-visitor-keys: 2.1.0 esquery: 1.5.0 @@ -13271,11 +13301,6 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.1 - /get-tsconfig@4.6.2: - resolution: {integrity: sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==} - dependencies: - resolve-pkg-maps: 1.0.0 - /get-tsconfig@4.7.5: resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} dependencies: