├── src ├── hooks │ ├── .keep │ └── useDownload.tsx ├── utils │ └── .keep ├── components │ └── .keep ├── assets │ ├── yay.jpg │ ├── Icon.icns │ └── playstore.png ├── app.ts ├── services │ └── api.ts ├── layouts │ ├── index.tsx │ └── BasicLayout.tsx ├── pages │ ├── index.tsx │ ├── umi │ │ └── index.tsx │ └── pro │ │ └── index.tsx └── global.less ├── platforms └── node │ ├── templates │ ├── .keep │ ├── config.tpl │ └── pro │ │ └── config.ts.tpl │ ├── fixtures │ ├── helpers-getconfig │ │ └── config │ │ │ └── config.js │ ├── helpers-getconfig-ts │ │ └── config │ │ │ └── config.ts │ └── pro │ │ └── config │ │ ├── config.dev.ts │ │ ├── defaultSettings.ts │ │ ├── proxy.ts │ │ ├── config.ts │ │ └── routes.ts │ ├── routes │ ├── index.ts │ ├── hello.ts │ ├── download.ts │ └── generate.ts │ ├── constant │ └── index.ts │ ├── tsconfig.json │ ├── helpers │ ├── errors.ts │ ├── getUmiConfig.test.ts │ ├── writeConfig.ts │ └── getUmiConfig.ts │ ├── tools │ ├── run.ts │ └── Generator.ts │ ├── app.ts │ └── serve ├── mock └── app.ts ├── config └── config.ts ├── typings.d.ts ├── rollup.config.js ├── .gitignore ├── tsconfig.json ├── CONTRIBUTING.md ├── README.md └── package.json /src/hooks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /platforms/node/templates/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/yay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/start/HEAD/src/assets/yay.jpg -------------------------------------------------------------------------------- /src/assets/Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/start/HEAD/src/assets/Icon.icns -------------------------------------------------------------------------------- /src/assets/playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umijs/start/HEAD/src/assets/playstore.png -------------------------------------------------------------------------------- /mock/app.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | 3 | export default { 4 | "POST /api/hello2": { 5 | text: "Alita", 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /platforms/node/fixtures/helpers-getconfig/config/config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "umi"; 2 | 3 | export default defineConfig({ 4 | foo: "bar", 5 | }); 6 | -------------------------------------------------------------------------------- /platforms/node/fixtures/helpers-getconfig-ts/config/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "umi"; 2 | 3 | export default defineConfig({ 4 | foo: "bar", 5 | }); 6 | -------------------------------------------------------------------------------- /platforms/node/templates/config.tpl: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'umi'; 2 | 3 | export default defineConfig({ 4 | {{ ^config }} 5 | // something 6 | {{ /config }} 7 | }); 8 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import { ResponseError } from 'umi-request'; 2 | 3 | export const request = { 4 | prefix: '', 5 | method: 'post', 6 | errorHandler: (error: ResponseError) => { 7 | // 集中处理错误 8 | console.log(error); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /platforms/node/routes/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get("/", function (req, res, next) { 6 | res.render("index", { title: "Umi Start" }); 7 | }); 8 | 9 | export default router; 10 | -------------------------------------------------------------------------------- /src/services/api.ts: -------------------------------------------------------------------------------- 1 | import { request } from "alita"; 2 | 3 | export async function query(): Promise { 4 | return request("/api/hello"); 5 | } 6 | 7 | export async function generate(data: any): Promise { 8 | return request("/api/generate", { data }); 9 | } 10 | -------------------------------------------------------------------------------- /platforms/node/routes/hello.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | 4 | /* GET */ 5 | router.post("/", function (req, res, next) { 6 | res.send({ 7 | data: { 8 | text: "Alita", 9 | }, 10 | message: "success", 11 | }); 12 | }); 13 | 14 | export default router; 15 | -------------------------------------------------------------------------------- /src/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import type { FC } from 'react'; 3 | import { IRouteComponentProps } from 'alita'; 4 | import BasicLayout from './BasicLayout'; 5 | 6 | const MenuLayout: FC = (props) => { 7 | return ; 8 | }; 9 | 10 | export default MenuLayout; 11 | -------------------------------------------------------------------------------- /config/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "alita"; 2 | 3 | export default defineConfig({ 4 | appType: "pc", 5 | noBuiltInPlugins: true, 6 | outputPath: "platforms/node/www/", 7 | publicPath: "./", 8 | proxy: { 9 | "/api": { 10 | target: "http://localhost:8000/", 11 | changeOrigin: true, 12 | }, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.css"; 2 | declare module "*.less"; 3 | declare module "*.scss"; 4 | declare module "*.sass"; 5 | declare module "*.svg"; 6 | declare module "*.png"; 7 | declare module "*.jpg"; 8 | declare module "*.jpeg"; 9 | declare module "*.gif"; 10 | declare module "*.bmp"; 11 | declare module "*.tiff"; 12 | declare module "*.json"; 13 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript2 from "rollup-plugin-typescript2"; 2 | 3 | export default { 4 | input: "platforms/node/app.ts", 5 | output: { 6 | file: "platforms/node/lib/app.js", 7 | format: "cjs", 8 | }, 9 | plugins: [ 10 | typescript2({ 11 | tsconfig: "platforms/node/tsconfig.json", 12 | clean: true, 13 | module: "esnext", 14 | }), 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { Result } from "antd"; 2 | 3 | export default () => { 4 | return ( 5 | 12 | } 13 | /> 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | .umi 18 | .umi-production 19 | platforms/node/www 20 | platforms/node/lib/** 21 | /out 22 | .temp-cache 23 | .pkg-cache -------------------------------------------------------------------------------- /platforms/node/constant/index.ts: -------------------------------------------------------------------------------- 1 | export const tempPath = ".temp-cache"; 2 | export const pkgPath = ".pkg-cache"; 3 | 4 | export const tempsList = [ 5 | { 6 | type: "umi", 7 | name: "umi", 8 | url: "https://github.com/umijs/umi", 9 | path: "packages/create-umi-app/templates/AppGenerator", 10 | }, 11 | { 12 | type: "pro", 13 | name: "pro", 14 | url: "https://github.com/ant-design/ant-design-pro", 15 | path: "", 16 | }, 17 | ]; 18 | -------------------------------------------------------------------------------- /platforms/node/fixtures/pro/config/config.dev.ts: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from "umi"; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | // https://github.com/zthxxx/react-dev-inspector 7 | "react-dev-inspector/plugins/umi/react-inspector", 8 | ], 9 | // https://github.com/zthxxx/react-dev-inspector#inspector-loader-props 10 | inspectorConfig: { 11 | exclude: [], 12 | babelPlugins: [], 13 | babelOptions: {}, 14 | }, 15 | webpack5: {}, 16 | }); 17 | -------------------------------------------------------------------------------- /platforms/node/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "moduleResolution": "node", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "baseUrl": ".", 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "noImplicitReturns": true, 13 | "suppressImplicitAnyIndexErrors": true, 14 | "skipLibCheck": true, 15 | "declaration": false // 是否自动创建类型声明文件 16 | }, 17 | "exclude": ["**/www/**"] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "moduleResolution": "node", 5 | "jsx": "preserve", 6 | "allowJs": true, 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noImplicitReturns": true, 12 | "suppressImplicitAnyIndexErrors": true, 13 | "declaration": true, 14 | "skipLibCheck": true, 15 | "paths": { 16 | "@/*": ["./src/*"], 17 | "@@/*": ["./src/.umi/*"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /platforms/node/helpers/errors.ts: -------------------------------------------------------------------------------- 1 | export abstract class BaseException extends Error { 2 | constructor(readonly message: string, readonly code: T) { 3 | super(message); 4 | } 5 | } 6 | 7 | export class FatalException extends BaseException<"FATAL"> { 8 | constructor(readonly message: string, readonly exitCode = 1) { 9 | super(message, "FATAL"); 10 | } 11 | } 12 | 13 | export function fatal(message: string): never { 14 | throw new FatalException(message); 15 | } 16 | 17 | export function isFatal(e: any): e is FatalException { 18 | return e && e instanceof FatalException; 19 | } 20 | -------------------------------------------------------------------------------- /platforms/node/fixtures/pro/config/defaultSettings.ts: -------------------------------------------------------------------------------- 1 | import { Settings as ProSettings } from "@ant-design/pro-layout"; 2 | 3 | type DefaultSettings = Partial & { 4 | pwa: boolean; 5 | }; 6 | 7 | const proSettings: DefaultSettings = { 8 | navTheme: "dark", 9 | // 拂晓蓝 10 | primaryColor: "#1890ff", 11 | layout: "side", 12 | contentWidth: "Fluid", 13 | fixedHeader: false, 14 | fixSiderbar: true, 15 | colorWeak: false, 16 | title: "Ant Design Pro", 17 | pwa: false, 18 | iconfontUrl: "", 19 | }; 20 | 21 | export type { DefaultSettings }; 22 | 23 | export default proSettings; 24 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to umi start 2 | 3 | ## Set up 4 | 5 | 安装需要的依赖 6 | 7 | ```bash 8 | $ yarn 9 | ``` 10 | 11 | ## develop 12 | 13 | 启用开发服务,这会同时执行三个命令,分别是启动 web 端的开发服务(umi dev)、启动 node 端的编译服务(rollup)、启动 node 端的 express 服务(nodemon)。 14 | 15 | ```bash 16 | $ yarn dev 17 | ``` 18 | 19 | ## Build 20 | 21 | 1. 编译会将 web 端编译到 node 端的静态资源文件目录 22 | 2. 编译 node 端到 lib 目录 23 | 24 | ```bash 25 | $ yarn build 26 | ``` 27 | 28 | ## Deploy 29 | 30 | 只需要 `platforms/node/lib`、`platforms/node/www`、`platforms/node/serve`。启动 `platforms/node/serve` 服务即可。默认使用 `3033` 端口号,可以通过环境变量修改。 31 | 32 | ## Tip 33 | 34 | node 端默认使用的是 3033 端口,如果有修改,开发环境下需要修改 web 端的 proxy 配置。 35 | -------------------------------------------------------------------------------- /platforms/node/fixtures/pro/config/proxy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置 3 | * ------------------------------- 4 | * The agent cannot take effect in the production environment 5 | * so there is no configuration of the production environment 6 | * For details, please see 7 | * https://pro.ant.design/docs/deploy 8 | */ 9 | export default { 10 | dev: { 11 | "/api/": { 12 | target: "https://preview.pro.ant.design", 13 | changeOrigin: true, 14 | pathRewrite: { "^": "" }, 15 | }, 16 | }, 17 | test: { 18 | "/api/": { 19 | target: "https://preview.pro.ant.design", 20 | changeOrigin: true, 21 | pathRewrite: { "^": "" }, 22 | }, 23 | }, 24 | pre: { 25 | "/api/": { 26 | target: "your pre url", 27 | changeOrigin: true, 28 | pathRewrite: { "^": "" }, 29 | }, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/global.less: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | #root { 4 | height: 100%; 5 | } 6 | 7 | .colorWeak { 8 | filter: invert(80%); 9 | } 10 | 11 | .ant-layout { 12 | min-height: 100vh; 13 | } 14 | 15 | canvas { 16 | display: block; 17 | } 18 | 19 | body { 20 | text-rendering: optimizeLegibility; 21 | -webkit-font-smoothing: antialiased; 22 | -moz-osx-font-smoothing: grayscale; 23 | } 24 | 25 | ul, 26 | ol { 27 | list-style: none; 28 | } 29 | 30 | @media (max-width: 600px) { 31 | .ant-table { 32 | width: 100%; 33 | overflow-x: auto; 34 | &-thead > tr, 35 | &-tbody > tr { 36 | > th, 37 | > td { 38 | white-space: pre; 39 | > span { 40 | display: block; 41 | } 42 | } 43 | } 44 | } 45 | } 46 | 47 | // Compatible with IE11 48 | @media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) { 49 | body .ant-design-pro > .ant-layout { 50 | min-height: 100vh; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /platforms/node/helpers/getUmiConfig.test.ts: -------------------------------------------------------------------------------- 1 | import { join } from "path"; 2 | import getUmiConfig from "./getUmiConfig"; 3 | 4 | const fixtures = join(__dirname, "..", "fixtures"); 5 | 6 | test("config-config", async () => { 7 | const cwd = join(fixtures, "helpers-getconfig"); 8 | const config = await getUmiConfig(cwd); 9 | const defaultConfig = config?.extConfig?.default ?? config?.extConfig; 10 | expect(defaultConfig).toEqual({ foo: "bar" }); 11 | }); 12 | test("config-config-ts", async () => { 13 | const cwd = join(fixtures, "helpers-getconfig-ts"); 14 | const config = await getUmiConfig(cwd); 15 | const defaultConfig = config?.extConfig?.default ?? config?.extConfig; 16 | expect(defaultConfig).toEqual({ foo: "bar" }); 17 | }); 18 | test("config-config-pro", async () => { 19 | const cwd = join(fixtures, "pro"); 20 | const config = await getUmiConfig(cwd); 21 | const defaultConfig = config?.extConfig?.default ?? config?.extConfig; 22 | expect(defaultConfig?.antd).toEqual({}); 23 | }); 24 | -------------------------------------------------------------------------------- /platforms/node/helpers/writeConfig.ts: -------------------------------------------------------------------------------- 1 | import { writeFile, writeJSON } from "fs-extra"; 2 | import { extname } from "path"; 3 | import * as util from "util"; 4 | 5 | export function formatJSObject(o: { [key: string]: any }): string { 6 | try { 7 | o = JSON.parse(JSON.stringify(o)); 8 | } catch (e) { 9 | throw new Error(`Cannot parse object as JSON: ${e.stack ? e.stack : e}`); 10 | } 11 | 12 | // @ts-ignore 13 | return util.inspect(o, { 14 | compact: false, 15 | breakLength: Infinity, 16 | depth: Infinity, 17 | maxArrayLength: Infinity, 18 | maxStringLength: Infinity, 19 | }); 20 | } 21 | 22 | function formatConfigTS(extConfig: any): string { 23 | // TODO: tags 24 | return `import { defineConfig } from 'umi'; 25 | export default defineConfig(${formatJSObject(extConfig)});\n`; 26 | } 27 | export default async function writeConfig( 28 | extConfig: any, 29 | extConfigFilePath: string 30 | ): Promise { 31 | if (extname(extConfigFilePath) === ".json") { 32 | await writeJSON(extConfigFilePath, extConfig, { spaces: 2 }); 33 | } 34 | 35 | await writeFile(extConfigFilePath, formatConfigTS(extConfig)); 36 | } 37 | -------------------------------------------------------------------------------- /src/hooks/useDownload.tsx: -------------------------------------------------------------------------------- 1 | import { useRequest } from "alita"; 2 | import { notification, Button, message } from "antd"; 3 | import { generate } from "@/services/api"; 4 | 5 | const useDownload = () => { 6 | const { loading, run } = useRequest(generate, { 7 | manual: true, 8 | onSuccess: (result, params) => { 9 | if (!result.success) { 10 | message.error(result.message); 11 | } 12 | if (result && result.fileName) { 13 | const key = `open${Date.now()}`; 14 | const btn = ( 15 | 22 | ); 23 | notification.open({ 24 | message: "将在新页面中下载,请关闭弹窗拦截", 25 | description: "如果没有正确下载,请联系开发人员", 26 | btn, 27 | key, 28 | duration: null, 29 | }); 30 | window.open( 31 | `http://${window.location.hostname}:8000/api/download/?fileName=${result.fileName}` 32 | ); 33 | } 34 | }, 35 | }); 36 | return { loading, run }; 37 | }; 38 | export default useDownload; 39 | -------------------------------------------------------------------------------- /platforms/node/fixtures/pro/config/config.ts: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from "umi"; 3 | import defaultSettings from "./defaultSettings"; 4 | import proxy from "./proxy"; 5 | import routes from "./routes"; 6 | 7 | const { REACT_APP_ENV } = process.env; 8 | 9 | export default defineConfig({ 10 | hash: true, 11 | antd: {}, 12 | dva: { 13 | hmr: true, 14 | }, 15 | history: { 16 | type: "browser", 17 | }, 18 | locale: { 19 | // default zh-CN 20 | default: "zh-CN", 21 | antd: true, 22 | // default true, when it is true, will use `navigator.language` overwrite default 23 | baseNavigator: true, 24 | }, 25 | dynamicImport: { 26 | loading: "@/components/PageLoading/index", 27 | }, 28 | targets: { 29 | ie: 11, 30 | }, 31 | // umi routes: https://umijs.org/docs/routing 32 | routes, 33 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn 34 | theme: { 35 | "primary-color": defaultSettings.primaryColor, 36 | }, 37 | title: false, 38 | ignoreMomentLocale: true, 39 | proxy: proxy[REACT_APP_ENV || "dev"], 40 | manifest: { 41 | basePath: "/", 42 | }, 43 | // 快速刷新功能 https://umijs.org/config#fastrefresh 44 | fastRefresh: {}, 45 | esbuild: {}, 46 | }); 47 | -------------------------------------------------------------------------------- /platforms/node/tools/run.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import { writeFileSync } from "fs-extra"; 3 | import { mkdirp } from "@umijs/utils"; 4 | import download from "load-examples"; 5 | import { tempPath, tempsList } from "../constant"; 6 | 7 | export default async () => { 8 | let tempsListData = [] as any[]; 9 | const root = path.join(process.cwd(), tempPath); 10 | // await downloadList() 11 | await Promise.all( 12 | tempsList.map(async (temp) => { 13 | return new Promise(async (resolve, reject) => { 14 | try { 15 | const pkg = await download(root, temp); 16 | if (pkg) { 17 | tempsListData.push(pkg); 18 | resolve("success"); 19 | } 20 | } catch (error) { 21 | reject(error); 22 | } 23 | }); 24 | }) 25 | ); 26 | // 1. 下载模版或者检测更新 27 | 28 | // 2.获取清单数据 放到上面的循环中了,可以少执行读取 29 | // 3.将 tempsListData 保存为文件?或者有什么方式存为全局数据,现不错动态表单,这些逻辑没用处,先注释了 30 | // const targetPath = path.join(root, "tempsListData.json"); 31 | // mkdirp.sync(path.dirname(targetPath)); 32 | // writeFileSync( 33 | // targetPath, 34 | // // 删除一个包之后 json会多了一些空行。sortPackage 可以删除掉并且排序 35 | // // prettier 会容忍一个空行 36 | // JSON.stringify({ data: tempsListData }), 37 | // "utf-8" 38 | // ); 39 | // console.log(`Write file ${targetPath} Success!`); 40 | }; 41 | -------------------------------------------------------------------------------- /platforms/node/routes/download.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import archiver from "archiver"; 3 | import { join } from "path"; 4 | import rimraf from "rimraf"; 5 | import { tempPath, pkgPath } from "../constant"; 6 | 7 | const router = express.Router(); 8 | /* GET users listing. */ 9 | router.get("/", function (req, res, next) { 10 | const fileName = req.query.fileName as string; 11 | console.log(fileName); 12 | if (!fileName) { 13 | res.status(500).send({ error: "文件名错误啦" }); 14 | } 15 | const name = fileName.split("-")[0]; 16 | const target = join(process.cwd(), pkgPath, fileName || ""); 17 | 18 | var archive = archiver("zip"); 19 | archive.on("error", function (err) { 20 | res.status(500).send({ error: err.message }); 21 | }); 22 | 23 | //on stream closed we can end the request 24 | archive.on("end", function () { 25 | console.log("Archive wrote %d bytes", archive.pointer()); 26 | // 结束的时候删除服务器上的数据 27 | rimraf.sync(target); 28 | }); 29 | 30 | //set the archive name 31 | res.attachment(`${name}.zip`); 32 | 33 | //this is the streaming magic 34 | archive.pipe(res); 35 | 36 | const directories = [target]; 37 | 38 | for (let i in directories) { 39 | archive.directory(directories[i], directories[i].replace(target, name)); 40 | } 41 | 42 | archive.finalize(); 43 | }); 44 | 45 | export default router; 46 | -------------------------------------------------------------------------------- /platforms/node/templates/pro/config.ts.tpl: -------------------------------------------------------------------------------- 1 | // https://umijs.org/config/ 2 | import { defineConfig } from 'umi'; 3 | import defaultSettings from './defaultSettings'; 4 | import proxy from './proxy'; 5 | import routes from './routes'; 6 | 7 | const { REACT_APP_ENV } = process.env; 8 | 9 | export default defineConfig({ 10 | hash: {{{ hash }}}, 11 | antd: {}, 12 | dva: { 13 | hmr: true, 14 | }, 15 | history: { 16 | type: '{{{ history }}}', 17 | }, 18 | locale: { 19 | // default zh-CN 20 | default: 'zh-CN', 21 | antd: true, 22 | // default true, when it is true, will use `navigator.language` overwrite default 23 | baseNavigator: true, 24 | }, 25 | dynamicImport: { 26 | loading: '@/components/PageLoading/index', 27 | }, 28 | targets: { 29 | ie: 11, 30 | }, 31 | // umi routes: https://umijs.org/docs/routing 32 | routes, 33 | // Theme for antd: https://ant.design/docs/react/customize-theme-cn 34 | theme: { 35 | 'primary-color': defaultSettings.primaryColor, 36 | }, 37 | title: false, 38 | ignoreMomentLocale: true, 39 | proxy: proxy[REACT_APP_ENV || 'dev'], 40 | manifest: { 41 | basePath: '/', 42 | }, 43 | {{ #fastRefresh }} 44 | // 快速刷新功能 https://umijs.org/config#fastrefresh 45 | fastRefresh: {}, 46 | {{ /fastRefresh }} 47 | {{ #ssr }} 48 | ssr: {}, 49 | {{ /ssr }} 50 | {{ #webpack5 }} 51 | webpack5: {}, 52 | {{ /webpack5 }} 53 | esbuild: {}, 54 | }); 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # start.umijs.org 2 | 3 | 在网页上自定义项目配置,生成 Umi 项目脚手架。 4 | 5 | 现有的 umi create 功能简单,只能选择很少的选项不能满足很多用户的需求。 6 | 7 | ![image](https://cdn.nlark.com/yuque/0/2021/png/84868/1619361125913-3b6f9e4a-c534-49b0-ab1f-ad9e4fcb099a.png?x-oss-process=image%2Fresize%2Cw_1434) 8 | 9 | 随着 umi 和 pro 的功能越来越多也需要一个更好的配置工具。类似智能想到提供更丰富的选择和预设。 10 | 11 | ## 调研 12 | 13 | umi 现在已经提供了丰富的功能,有组件库研发,插件研发,pro 脚手架,还有海量的配置可以自定义,spring 也提供了类似的工具,[spring initializr](https://start.spring.io/) 可以帮助 java 用户快速的新建项目,并且提供各项的功能的配置。 14 | 15 | ![image](https://cdn.nlark.com/yuque/0/2021/png/84868/1619363396863-b8b8be85-e50c-4c53-89f2-a10562e21230.png?x-oss-process=image%2Fresize%2Cw_1436) 16 | 17 | 同样的在 umi 如果可以有这样的工具,将会更好的推广 umi 的功能, create-umi 有一周 1.4k 的下载量, create-react-app 有 140k,用户需求量还是蛮大的。 18 | 19 | ## Why? 20 | 21 | 1. 只做项目创建,功能需求更加聚焦。 22 | 2. Umi 用户技术可选,项目适用性更广。 23 | 3. 对于 Umi 关键性的配置能够提供良好 playground。 24 | 4. 能减轻 Umi 和 Pro 开源项目的维护成本。 25 | 26 | ## 基础功能 27 | 28 | umi 提供了多种类型的引用开发, umi initializr 应该提供以下的功能: 29 | 30 | - 一个极简的 umi 项目 31 | - Pro 模板 32 | - dumi 模板 33 | - h5 模板 34 | - plugin 模板 35 | 36 | ## 共有配置 37 | 38 | | 配置名称 | 值类型 | 说明 | 默认值 | 39 | | :---------- | :--------: | :------------: | :----: | 40 | | name | string | 项目名称 | umi | 41 | | version | 版本号 | 版本号 | 1.0.0 | 42 | | description | string | 项目描述 | - | 43 | | keywords | 标签选择器 | 项目的关键字 | - | 44 | | author | string | 项目的作者 | - | 45 | | license | 选择 | 项目的 license | ISC | 46 | 47 | 待补充 48 | -------------------------------------------------------------------------------- /platforms/node/app.ts: -------------------------------------------------------------------------------- 1 | import compression from "compression"; 2 | import createError from "http-errors"; 3 | import express from "express"; 4 | import type { ErrorRequestHandler } from "express"; 5 | import path from "path"; 6 | import cookieParser from "cookie-parser"; 7 | import logger from "morgan"; 8 | import indexRouter from "./routes/index"; 9 | import helloRouter from "./routes/hello"; 10 | import downloadRouter from "./routes/download"; 11 | import generateRouter from "./routes/generate"; 12 | import run from "./tools/run"; 13 | 14 | //TODO: 这个执行时机还要在考虑下 15 | run(); 16 | const app = express(); 17 | app.use(compression()); 18 | 19 | // view engine setup 20 | app.use(logger("dev")); 21 | app.use(express.json()); 22 | app.use(express.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, "..", "www"))); 25 | app.use("/", indexRouter); 26 | app.use("/api/hello", helloRouter); 27 | app.use("/api/download", downloadRouter); 28 | app.use("/api/generate", generateRouter); 29 | 30 | // catch 404 and forward to error handler 31 | app.use((req, res, next) => { 32 | next(createError(404)); 33 | }); 34 | 35 | const errHandler: ErrorRequestHandler = (err, req, res, next) => { 36 | // set locals, only providing error in development 37 | res.locals.message = err.message; 38 | res.locals.error = req.app.get("env") === "development" ? err : {}; 39 | 40 | // render the error page 41 | res.status(err.status || 500); 42 | res.render("error"); 43 | }; 44 | // error handler 45 | app.use(errHandler); 46 | console.log( 47 | "首次执行之后,请注释 platforms/node/app.ts#L15 这行代码,这个执行时机还没考虑清楚,首次执行之后,请注释 platforms/node/app.ts#L15 这行代码,这个执行时机还没考虑清楚,首次执行之后,请注释 platforms/node/app.ts#L15 这行代码,这个执行时机还没考虑清楚" 48 | ); 49 | 50 | module.exports = app; 51 | -------------------------------------------------------------------------------- /platforms/node/serve: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('./lib/app'); 8 | var debug = require('debug')('start:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '8000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | console.log(`Start Server running in port:${port}`); 30 | server.on('error', onError); 31 | server.on('listening', onListening); 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | 37 | function normalizePort(val) { 38 | var port = parseInt(val, 10); 39 | 40 | if (isNaN(port)) { 41 | // named pipe 42 | return val; 43 | } 44 | 45 | if (port >= 0) { 46 | // port number 47 | return port; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | /** 54 | * Event listener for HTTP server "error" event. 55 | */ 56 | 57 | function onError(error) { 58 | if (error.syscall !== 'listen') { 59 | throw error; 60 | } 61 | 62 | var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port; 63 | 64 | // handle specific listen errors with friendly messages 65 | switch (error.code) { 66 | case 'EACCES': 67 | console.error(bind + ' requires elevated privileges'); 68 | process.exit(1); 69 | break; 70 | case 'EADDRINUSE': 71 | console.error(bind + ' is already in use'); 72 | process.exit(1); 73 | break; 74 | default: 75 | throw error; 76 | } 77 | } 78 | 79 | /** 80 | * Event listener for HTTP server "listening" event. 81 | */ 82 | 83 | function onListening() { 84 | var addr = server.address(); 85 | var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; 86 | debug('Listening on ' + bind); 87 | } 88 | -------------------------------------------------------------------------------- /platforms/node/fixtures/pro/config/routes.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | path: "/", 4 | component: "../layouts/BlankLayout", 5 | routes: [ 6 | { 7 | path: "/user", 8 | component: "../layouts/UserLayout", 9 | routes: [ 10 | { 11 | name: "login", 12 | path: "/user/login", 13 | component: "./User/login", 14 | }, 15 | ], 16 | }, 17 | { 18 | path: "/", 19 | component: "../layouts/SecurityLayout", 20 | routes: [ 21 | { 22 | path: "/", 23 | component: "../layouts/BasicLayout", 24 | authority: ["admin", "user"], 25 | routes: [ 26 | { 27 | path: "/", 28 | redirect: "/welcome", 29 | }, 30 | { 31 | path: "/welcome", 32 | name: "welcome", 33 | icon: "smile", 34 | component: "./Welcome", 35 | }, 36 | { 37 | path: "/admin", 38 | name: "admin", 39 | icon: "crown", 40 | component: "./Admin", 41 | authority: ["admin"], 42 | routes: [ 43 | { 44 | path: "/admin/sub-page", 45 | name: "sub-page", 46 | icon: "smile", 47 | component: "./Welcome", 48 | authority: ["admin"], 49 | }, 50 | ], 51 | }, 52 | { 53 | name: "list.table-list", 54 | icon: "table", 55 | path: "/list", 56 | component: "./TableList", 57 | }, 58 | { 59 | component: "./404", 60 | }, 61 | ], 62 | }, 63 | { 64 | component: "./404", 65 | }, 66 | ], 67 | }, 68 | ], 69 | }, 70 | { 71 | component: "./404", 72 | }, 73 | ]; 74 | -------------------------------------------------------------------------------- /platforms/node/routes/generate.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { join } from "path"; 3 | import { tempPath, pkgPath } from "../constant"; 4 | import Generator from "../tools/Generator"; 5 | 6 | const router = express.Router(); 7 | 8 | // { 9 | // "type": "umi", 10 | // "project": { 11 | // "name": "umi", 12 | // "version": "1.0.0", 13 | // "description": "新的独角兽在这里启航", 14 | // "keywords": ["umi"], 15 | // "license": "MIT" 16 | // }, 17 | // "umi": { 18 | // "history": "browser", 19 | // "hash": true, 20 | // "webpack5": false, 21 | // "fastRefresh": false, 22 | // "ssr": false 23 | // } 24 | // } 25 | 26 | router.post("/", async (req, res, next) => { 27 | try { 28 | const { type, project: p, umi: u } = req.body; 29 | const { name } = p; 30 | // 第三方模版需要的配置,理论上应该和前端传过来的一致 31 | let project = p; 32 | let umi = u; 33 | switch (type) { 34 | case "umi": 35 | project = { 36 | ...project, 37 | version: "3.4.14", 38 | }; 39 | umi = { 40 | ...umi, 41 | conventionRoutes: true, 42 | }; 43 | break; 44 | default: 45 | break; 46 | } 47 | if (!type) { 48 | res.send({ 49 | data: { 50 | message: `type:${type} is no support`, 51 | success: false, 52 | }, 53 | message: "success", 54 | }); 55 | return; 56 | } 57 | // config, path, target 58 | const path = join(process.cwd(), tempPath, type); 59 | const filePath = `${name}-${new Date().getTime()}`; 60 | const target = join(process.cwd(), pkgPath, filePath); 61 | const g = new Generator({ 62 | cwd: "", 63 | args: { _: [], $0: "", project, path, target, umi, type }, 64 | }); 65 | await g.run(); 66 | res.send({ 67 | data: { 68 | fileName: filePath, 69 | success: true, 70 | }, 71 | message: "success", 72 | }); 73 | } catch (error) { 74 | console.log(error); 75 | res.json({ 76 | code: 500, 77 | data: { 78 | success: false, 79 | }, 80 | message: "服务端出错了!", 81 | }); 82 | } 83 | }); 84 | 85 | export default router; 86 | -------------------------------------------------------------------------------- /platforms/node/tools/Generator.ts: -------------------------------------------------------------------------------- 1 | import { Generator } from "@umijs/utils"; 2 | import { join, resolve } from "path"; 3 | import { existsSync, writeFileSync, remove } from "fs-extra"; 4 | import prettier from "prettier"; 5 | import sortPackage from "sort-package-json"; 6 | import getUmiConfig from "../helpers/getUmiConfig"; 7 | import writeConfig from "../helpers/writeConfig"; 8 | 9 | type Arguments = T & { 10 | project: any; 11 | umi: any; 12 | /** 模版所在的文件路径 */ 13 | path: string; 14 | type?: string; 15 | /** 项目新建路径,这两个路径由外面传入是为了适应不同的平台调用,node 端需要写到缓存路径中,其他需要写到本地路径*/ 16 | target: string; 17 | /** Non-option arguments */ 18 | _: Array; 19 | /** The script name or node command */ 20 | $0: string; 21 | /** All remaining options */ 22 | [argName: string]: unknown; 23 | }; 24 | 25 | export default class StartGenerator extends Generator { 26 | async writing() { 27 | const { project, umi, path, target, type } = this.args as Arguments; 28 | this.copyDirectory({ 29 | context: { ...project, ...umi }, 30 | path, 31 | target, 32 | }); 33 | 34 | // error 35 | const packageJsonPath = resolve(target, "package.json"); 36 | if (!existsSync(packageJsonPath)) { 37 | console.log(`${packageJsonPath} is no find`); 38 | return; 39 | } 40 | const pkg = require(packageJsonPath); 41 | 42 | // 修改项目的 package.json 43 | const projectPkg = { 44 | ...pkg, 45 | ...project, 46 | }; 47 | // remove create-umi config 48 | delete projectPkg["create-umi"]; 49 | 50 | writeFileSync( 51 | packageJsonPath, 52 | // 删除一个包之后 json会多了一些空行。sortPackage 可以删除掉并且排序 53 | // prettier 会容忍一个空行 54 | prettier.format(JSON.stringify(sortPackage(projectPkg)), { 55 | parser: "json", 56 | }) 57 | ); 58 | 59 | // 修改 config 文件 60 | // 分3种情况 61 | // 情况1 pro 项目,存在文件引用的情况。 62 | //TODO: 应该智能处理的,现在使用 情况3获取到的 config 是解析后的。会消除 import,还要再研究下。 63 | console.log(type); 64 | if (type === "pro") { 65 | const proTplPath = join( 66 | __dirname, 67 | "..", 68 | "templates", 69 | "pro", 70 | "config.ts.tpl" 71 | ); 72 | const proConfigPath = join(target, "config", "config.ts"); 73 | this.copyTpl({ 74 | context: umi, 75 | target: proConfigPath, 76 | templatePath: proTplPath, 77 | }); 78 | return; 79 | } 80 | // 情况2 模版项目中 存在 config/config.ts.tpl 文件,即表示配置修改由模版提供者自己管理,在上面的 copyDirectory 中已经处理了这个逻辑 81 | // 情况3 没有 tpl 文件,手动修改 82 | // const { extConfig, extConfigFilePath, extConfigName, extConfigType } = await getUmiConfig(target); 83 | // if (!extConfigFilePath) { 84 | // // 没有找到配置文件 85 | // return; 86 | // } 87 | // const userConfig = extConfig?.default ?? extConfig; 88 | // const newConfig = { ...userConfig, ...umi }; 89 | 90 | // await writeConfig(newConfig, extConfigFilePath); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "start", 4 | "version": "0.0.1", 5 | "description": "User defined project configuration on the web page to generate UMI project scaffolding.", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "nodemon platforms/node/serve", 9 | "dev": "concurrently \"npm run dev:renderer\" \"npm run dev:main\" \"npm run start\"", 10 | "build": "concurrently \"npm run build:renderer\" \"npm run build:main\"", 11 | "dev:main": "rollup -c -w", 12 | "dev:renderer": "alita dev", 13 | "build:main": "rollup -c", 14 | "build:renderer": "alita build", 15 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 16 | "lint": "npm run lint:js && npm run lint:style && npm run lint:prettier", 17 | "lint-staged": "lint-staged", 18 | "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ", 19 | "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style", 20 | "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src", 21 | "lint:prettier": "prettier --check \"**/*\" --end-of-line auto", 22 | "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less", 23 | "test": "umi-test" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/umijs/start.git" 28 | }, 29 | "keywords": [], 30 | "author": "", 31 | "license": "ISC", 32 | "bugs": { 33 | "url": "https://github.com/umijs/start/issues" 34 | }, 35 | "authors": [ 36 | "xiaohuoni <448627663@qq.com> (https://github.com/xiaohuoni)", 37 | "chenshuai2144 (https://github.com/chenshuai2144)" 38 | ], 39 | "homepage": "https://github.com/umijs/start#readme", 40 | "gitHooks": { 41 | "pre-commit": "lint-staged" 42 | }, 43 | "lint-staged": { 44 | "*.{js,jsx,less,md,json}": [ 45 | "prettier --write" 46 | ], 47 | "*.ts?(x)": [ 48 | "prettier --parser=typescript --write" 49 | ] 50 | }, 51 | "dependencies": { 52 | "@ant-design/pro-card": "^1.11.12", 53 | "@ant-design/pro-form": "^1.22.0", 54 | "@ant-design/pro-layout": "^6.17.1", 55 | "@ant-design/pro-provider": "^1.4.12", 56 | "alita": "2.8.2-beta.1", 57 | "util": "^0.12.4" 58 | }, 59 | "devDependencies": { 60 | "@types/archiver": "^5.1.0", 61 | "@types/async-retry": "1.4.2", 62 | "@types/compression": "^1.7.0", 63 | "@types/cookie-parser": "^1.4.2", 64 | "@types/cross-spawn": "^6.0.2", 65 | "@types/express": "^4.17.11", 66 | "@types/fs-extra": "^9.0.11", 67 | "@types/http-errors": "^1.8.0", 68 | "@types/jest": "^26.0.23", 69 | "@types/mkdirp": "^1.0.1", 70 | "@types/morgan": "^1.9.2", 71 | "@types/mustache": "^4.1.1", 72 | "@types/node": "^12.6.8", 73 | "@types/prettier": "^2.2.3", 74 | "@types/prompts": "2.0.1", 75 | "@types/rimraf": "3.0.0", 76 | "@types/tar": "4.0.3", 77 | "@types/validate-npm-package-name": "3.0.0", 78 | "@types/yargs": "^16.0.1", 79 | "@types/yeoman-generator": "^4.11.3", 80 | "@umijs/fabric": "2.5.7", 81 | "@umijs/test": "3.4.4", 82 | "@umijs/utils": "3.4.4", 83 | "@vercel/ncc": "0.25.1", 84 | "archiver": "^5.3.0", 85 | "async-retry": "1.3.1", 86 | "chalk": "^4.1.0", 87 | "commander": "2.20.0", 88 | "concurrently": "^6.0.1", 89 | "cookie-parser": "^1.4.5", 90 | "cpy": "7.3.0", 91 | "cross-env": "^7.0.3", 92 | "cross-spawn": "6.0.5", 93 | "eslint": "^7.23.0", 94 | "express": "^4.17.1", 95 | "fs-extra": "^9.1.0", 96 | "got": "10.7.0", 97 | "iconutil": "^1.0.2", 98 | "lint-staged": "^10.5.4", 99 | "load-examples": "^1.0.1", 100 | "morgan": "^1.10.0", 101 | "mustache": "^4.2.0", 102 | "nodemon": "^2.0.7", 103 | "prettier": "^2.2.1", 104 | "prompts": "2.1.0", 105 | "rimraf": "3.0.0", 106 | "rollup": "^2.44.0", 107 | "rollup-plugin-typescript2": "^0.30.0", 108 | "sort-package-json": "^1.49.0", 109 | "stylelint": "^13.12.0", 110 | "tar": "4.4.10", 111 | "tslib": "^2.2.0", 112 | "typescript": "3.8.3", 113 | "update-check": "1.5.4", 114 | "validate-npm-package-name": "3.0.0", 115 | "yargs": "^16.2.0", 116 | "yorkie": "^2.0.0" 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /platforms/node/helpers/getUmiConfig.ts: -------------------------------------------------------------------------------- 1 | import { join, resolve } from "path"; 2 | import { existsSync, readFileSync, access, constants } from "fs-extra"; 3 | import { winPath } from "@umijs/utils"; 4 | import type typescript from "typescript"; 5 | import { fatal, isFatal } from "./errors"; 6 | 7 | const DEFAULT_CONFIG_FILES = [ 8 | ".umirc.ts", 9 | ".umirc.js", 10 | "config/config.ts", 11 | "config/config.js", 12 | ]; 13 | 14 | const getConfigFile = (base: string): string | null => { 15 | const configFile = DEFAULT_CONFIG_FILES.find((f) => 16 | existsSync(join(base, f)) 17 | ); 18 | return configFile ? winPath(configFile) : null; 19 | }; 20 | interface NodeModuleWithCompile extends NodeJS.Module { 21 | _compile?(code: string, filename: string): any; 22 | } 23 | 24 | /** 25 | * @see https://github.com/ionic-team/stencil/blob/HEAD/src/compiler/sys/node-require.ts 26 | */ 27 | export const requireTS = (ts: typeof typescript, p: string): unknown => { 28 | const id = resolve(p); 29 | 30 | delete require.cache[id]; 31 | 32 | require.extensions[".ts"] = ( 33 | module: NodeModuleWithCompile, 34 | fileName: string 35 | ) => { 36 | let sourceText = readFileSync(fileName, "utf8"); 37 | 38 | if (fileName.endsWith(".ts")) { 39 | const tsResults = ts.transpileModule(sourceText, { 40 | fileName, 41 | compilerOptions: { 42 | module: ts.ModuleKind.CommonJS, 43 | moduleResolution: ts.ModuleResolutionKind.NodeJs, 44 | esModuleInterop: true, 45 | strict: true, 46 | target: ts.ScriptTarget.ES2017, 47 | }, 48 | reportDiagnostics: true, 49 | }); 50 | sourceText = tsResults.outputText; 51 | } else { 52 | // quick hack to turn a modern es module 53 | // into and old school commonjs module 54 | sourceText = sourceText.replace(/export\s+\w+\s+(\w+)/gm, "exports.$1"); 55 | } 56 | module._compile?.(sourceText, fileName); 57 | }; 58 | 59 | const m = require(id); // eslint-disable-line @typescript-eslint/no-var-requires 60 | 61 | delete require.extensions[".ts"]; 62 | 63 | return m; 64 | }; 65 | 66 | export function resolveNode( 67 | root: string, 68 | ...pathSegments: string[] 69 | ): string | null { 70 | try { 71 | return require.resolve(pathSegments.join("/"), { paths: [root] }); 72 | } catch (e) { 73 | return null; 74 | } 75 | } 76 | 77 | async function loadExtConfigTS( 78 | rootDir: string, 79 | extConfigName: string, 80 | extConfigFilePath: string 81 | ): Promise { 82 | try { 83 | const tsPath = resolveNode(rootDir, "typescript"); 84 | 85 | if (!tsPath) { 86 | fatal( 87 | "Could not find installation of TypeScript.\n" + 88 | `To use ${extConfigName} files, you must install TypeScript in your project, e.g. w/ ${"npm install -D typescript"}` 89 | ); 90 | } 91 | 92 | const ts = require(tsPath); // eslint-disable-line @typescript-eslint/no-var-requires 93 | const extConfigObject = requireTS(ts, extConfigFilePath) as any; 94 | const extConfig = extConfigObject.default ?? extConfigObject; 95 | 96 | return { 97 | extConfigType: "ts", 98 | extConfigName, 99 | extConfigFilePath: extConfigFilePath, 100 | extConfig, 101 | }; 102 | } catch (e) { 103 | if (!isFatal(e)) { 104 | fatal(`Parsing ${extConfigName} failed.\n\n${e.stack ?? e}`); 105 | } 106 | 107 | throw e; 108 | } 109 | } 110 | 111 | async function loadExtConfigJS( 112 | rootDir: string, 113 | extConfigName: string, 114 | extConfigFilePath: string 115 | ): Promise { 116 | try { 117 | return { 118 | extConfigType: "js", 119 | extConfigName, 120 | extConfigFilePath: extConfigFilePath, 121 | extConfig: require(extConfigFilePath), 122 | }; 123 | } catch (e) { 124 | fatal(`Parsing ${extConfigName} failed.\n\n${e.stack ?? e}`); 125 | } 126 | } 127 | export async function pathAccessible( 128 | filePath: string, 129 | mode: number 130 | ): Promise { 131 | try { 132 | await access(filePath, mode); 133 | } catch (e) { 134 | return false; 135 | } 136 | 137 | return true; 138 | } 139 | 140 | export async function pathExists(filePath: string): Promise { 141 | return pathAccessible(filePath, constants.F_OK); 142 | } 143 | const getUmiConfig = async (rootDir: string): Promise => { 144 | const configFileName = getConfigFile(rootDir); 145 | console.log(configFileName); 146 | if (!configFileName) return {}; 147 | const extConfigFilePath = resolve(rootDir, configFileName); 148 | if (configFileName?.endsWith(".ts")) { 149 | return loadExtConfigTS(rootDir, configFileName, extConfigFilePath); 150 | } 151 | return loadExtConfigJS(rootDir, configFileName, extConfigFilePath); 152 | }; 153 | 154 | export default getUmiConfig; 155 | -------------------------------------------------------------------------------- /src/layouts/BasicLayout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useContext, useState } from "react"; 2 | import type { FC } from "react"; 3 | import ProLayout, { 4 | DefaultFooter, 5 | PageContainer, 6 | } from "@ant-design/pro-layout"; 7 | import { Link, history, IRouteComponentProps } from "alita"; 8 | import { Input, Space, Tag } from "antd"; 9 | import { Menu } from "antd"; 10 | import ProCard from "@ant-design/pro-card"; 11 | import ProProvider from "@ant-design/pro-provider"; 12 | 13 | const TagList: React.FC<{ 14 | value?: string[]; 15 | onChange?: (value: string[]) => void; 16 | }> = ({ value, onChange }) => { 17 | const ref = useRef(null); 18 | const [newTags, setNewTags] = useState([]); 19 | const [inputValue, setInputValue] = useState(""); 20 | 21 | const handleInputChange = (e: React.ChangeEvent) => { 22 | setInputValue(e.target.value); 23 | }; 24 | 25 | const handleInputConfirm = () => { 26 | let tempsTags = [...(value || [])]; 27 | if ( 28 | inputValue && 29 | tempsTags.filter((tag) => tag === inputValue).length === 0 30 | ) { 31 | tempsTags = [...tempsTags, inputValue]; 32 | } 33 | onChange?.(tempsTags); 34 | setNewTags([]); 35 | setInputValue(""); 36 | }; 37 | 38 | return ( 39 | 40 | {(value || []).concat(newTags).map((item) => ( 41 | {item} 42 | ))} 43 | 53 | 54 | ); 55 | }; 56 | 57 | const BasicLayout: FC = ({ children, ...rest }) => { 58 | const values = useContext(ProProvider); 59 | return ( 60 | } 68 | {...rest} 69 | onMenuHeaderClick={() => history.push("/")} 70 | menuItemRender={(menuItemProps, defaultDom) => { 71 | if ( 72 | menuItemProps.isUrl || 73 | !menuItemProps.path || 74 | location.pathname === menuItemProps.path 75 | ) { 76 | return defaultDom; 77 | } 78 | return {defaultDom}; 79 | }} 80 | > 81 | 82 | 83 | 90 | history.push(e.key as string)} 92 | selectedKeys={[rest.location.pathname]} 93 | style={{ 94 | width: "100%", 95 | height: "100%", 96 | }} 97 | > 98 | 99 | 109 | 114 | Umi 115 | 116 | 117 | 118 | 128 | 133 | Ant Design Pro 134 | 135 | 136 | 137 | 138 | 139 | { 145 | return ( 146 | <> 147 | {[text].flat(1).map((item) => ( 148 | {item} 149 | ))} 150 | 151 | ); 152 | }, 153 | renderFormItem: (text, props) => ( 154 | 155 | ), 156 | }, 157 | }, 158 | }} 159 | > 160 | {children} 161 | 162 | 163 | 164 | 165 | 166 | ); 167 | }; 168 | 169 | export default BasicLayout; 170 | -------------------------------------------------------------------------------- /src/pages/umi/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type { FC } from "react"; 3 | import { StepsForm, BetaSchemaForm } from "@ant-design/pro-form"; 4 | import useDownload from "@/hooks/useDownload"; 5 | 6 | interface PageProps {} 7 | 8 | const IndexPage: FC = (props) => { 9 | const { loading, run } = useDownload(); 10 | return ( 11 | { 13 | run({ type: "umi", ...values }); 14 | return true; 15 | }} 16 | > 17 | 117 | 182 | `, 183 | columns: [ 184 | { 185 | valueType: "group", 186 | columns: [ 187 | { 188 | title: "名称", 189 | dataIndex: "name", 190 | width: "xs", 191 | formItemProps: { 192 | rules: [ 193 | { 194 | required: true, 195 | message: "此项为必填项", 196 | }, 197 | ], 198 | }, 199 | }, 200 | { 201 | title: "内容", 202 | dataIndex: "content", 203 | formItemProps: { 204 | rules: [ 205 | { 206 | required: true, 207 | message: "此项为必填项", 208 | }, 209 | ], 210 | }, 211 | width: "m", 212 | }, 213 | ], 214 | }, 215 | ], 216 | }, 217 | ]} 218 | /> 219 | 220 | ); 221 | }; 222 | 223 | export default IndexPage; 224 | -------------------------------------------------------------------------------- /src/pages/pro/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import type { FC } from "react"; 3 | import { StepsForm, BetaSchemaForm } from "@ant-design/pro-form"; 4 | import useDownload from "@/hooks/useDownload"; 5 | 6 | interface PageProps {} 7 | 8 | const IndexPage: FC = ({}) => { 9 | const { loading, run } = useDownload(); 10 | 11 | return ( 12 | { 14 | run({ type: "pro", ...values }); 15 | return true; 16 | }} 17 | > 18 | 118 | 183 | `, 184 | columns: [ 185 | { 186 | valueType: "group", 187 | columns: [ 188 | { 189 | title: "名称", 190 | dataIndex: "name", 191 | width: "xs", 192 | formItemProps: { 193 | rules: [ 194 | { 195 | required: true, 196 | message: "此项为必填项", 197 | }, 198 | ], 199 | }, 200 | }, 201 | { 202 | title: "内容", 203 | dataIndex: "content", 204 | formItemProps: { 205 | rules: [ 206 | { 207 | required: true, 208 | message: "此项为必填项", 209 | }, 210 | ], 211 | }, 212 | width: "m", 213 | }, 214 | ], 215 | }, 216 | ], 217 | }, 218 | ]} 219 | /> 220 | 285 | `, 286 | columns: [ 287 | { 288 | valueType: "group", 289 | columns: [ 290 | { 291 | title: "名称", 292 | dataIndex: "name", 293 | width: "xs", 294 | formItemProps: { 295 | rules: [ 296 | { 297 | required: true, 298 | message: "此项为必填项", 299 | }, 300 | ], 301 | }, 302 | }, 303 | { 304 | title: "内容", 305 | dataIndex: "content", 306 | formItemProps: { 307 | rules: [ 308 | { 309 | required: true, 310 | message: "此项为必填项", 311 | }, 312 | ], 313 | }, 314 | width: "m", 315 | }, 316 | ], 317 | }, 318 | ], 319 | }, 320 | ]} 321 | /> 322 | 323 | ); 324 | }; 325 | 326 | export default IndexPage; 327 | --------------------------------------------------------------------------------