From 14ea12174534f48dd6a25167d0daafc776177df0 Mon Sep 17 00:00:00 2001 From: dmyang Date: Tue, 23 Feb 2021 15:32:02 +0800 Subject: [PATCH] feat: support custom config files (#6142) * feat: support custom config files * test: add test cases * docs: add custom config files document --- docs/api/README.md | 81 +++++++++++-------- packages/core/src/Config/Config.test.ts | 18 +++++ packages/core/src/Config/Config.ts | 17 +++- .../fixtures/custom-config-file-ts/.foorc.ts | 4 + .../fixtures/custom-config-file-ts/.umirc.ts | 4 + .../fixtures/custom-config-file/.foorc.js | 4 + .../fixtures/custom-config-file/.umirc.js | 4 + packages/core/src/Service/Service.ts | 6 ++ 8 files changed, 101 insertions(+), 37 deletions(-) create mode 100644 packages/core/src/Config/fixtures/custom-config-file-ts/.foorc.ts create mode 100644 packages/core/src/Config/fixtures/custom-config-file-ts/.umirc.ts create mode 100644 packages/core/src/Config/fixtures/custom-config-file/.foorc.js create mode 100644 packages/core/src/Config/fixtures/custom-config-file/.umirc.js diff --git a/docs/api/README.md b/docs/api/README.md index 417130d1e0bc..890f2a353416 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -6,7 +6,6 @@ toc: menu # API - ## 基本 API ### dynamic @@ -19,16 +18,17 @@ Load component dynamically on demand. Usually work with [dynamic import syntax](https://github.com/tc39/proposal-dynamic-import). - **Create dynamic component** ```js import { dynamic } from 'umi'; export default dynamic({ - loader: async function() { + loader: async function () { // webpackChunkName tells webpack create separate bundle for HugeA - const { default: HugeA } = await import(/* webpackChunkName: "external_A" */ './HugeA'); + const { default: HugeA } = await import( + /* webpackChunkName: "external_A" */ './HugeA' + ); return HugeA; }, }); @@ -47,7 +47,7 @@ import AsyncHugeA from './AsyncHugeA'; // 3. display HugeA whenever component downloaded export default () => { return ; -} +}; ``` ### history @@ -135,11 +135,11 @@ plugin.applyPlugins({ 参数属性包含: -* **key**,坑位的 key -* **type**,执行方式类型,详见 [ApplyPluginsType](#ApplyPluginsType) -* **initialValue**,初始值 -* **args**,参数 -* **async**,是否异步执行且返回 Promise +- **key**,坑位的 key +- **type**,执行方式类型,详见 [ApplyPluginsType](#ApplyPluginsType) +- **initialValue**,初始值 +- **args**,参数 +- **async**,是否异步执行且返回 Promise ### ApplyPluginsType @@ -147,9 +147,9 @@ plugin.applyPlugins({ 运行时插件执行类型,enum 类型,包含三个属性: -* **compose**,用于合并执行多个函数,函数可决定前序函数的执行时机 -* **modify**,用于修改值 -* **event**,用于执行事件,前面没有依赖关系 +- **compose**,用于合并执行多个函数,函数可决定前序函数的执行时机 +- **modify**,用于修改值 +- **event**,用于执行事件,前面没有依赖关系 ## 路由 @@ -175,9 +175,9 @@ export default () => { {/* An object representation of the Link location */} @@ -190,8 +190,8 @@ export default () => { or as an object */} { - return { ...location, pathname: "/profile" }; + to={(location) => { + return { ...location, pathname: '/profile' }; }} /> @@ -206,7 +206,7 @@ export default () => { */} { + innerRef={(node) => { // `node` refers to the mounted DOM element // or null when unmounted }} @@ -241,8 +241,8 @@ export default () => { FAQs @@ -276,7 +276,7 @@ export default () => { if (!match) { return false; } - return location.search.includes("name"); + return location.search.includes('name'); }} > Profile @@ -307,8 +307,10 @@ export default () => { transition */} { - return location.pathname !== "/" ? true : `Are are sure you want to back to home page?`; + message={(location) => { + return location.pathname !== '/' + ? true + : `Are are sure you want to back to home page?`; }} /> @@ -327,7 +329,7 @@ export default () => { You can get access to the `history`, `location`, `match` objects via the `withRouter` higher-order component. `withRouter` will pass updated `match`, `location`, and `history` props to the wrapped component whenever it renders ```tsx -import { withRouter } from "umi"; +import { withRouter } from 'umi'; export default withRouter(({ history, location, match }) => { return ( @@ -347,10 +349,10 @@ export default withRouter(({ history, location, match }) => { The `useHistory` hook gives you access to the `history` instance that you may use to navigate. ```tsx -import { useHistory } from "umi"; +import { useHistory } from 'umi'; export default () => { - const history = useHistory() + const history = useHistory(); return (
    @@ -366,10 +368,10 @@ export default () => { The `useLocation` hook returns the `location` object that represents the current URL. You can think about it like a `useState` that returns a new location whenever the URL changes. ```tsx -import { useLocation } from "umi"; +import { useLocation } from 'umi'; export default () => { - const location = useLocation() + const location = useLocation(); return (
      @@ -385,10 +387,10 @@ export default () => { `useParams` returns an object of key/value pairs of URL parameters. Use it to access `match.params` of the current route. ```tsx -import { useParams } from "umi"; +import { useParams } from 'umi'; export default () => { - const params = useParams() + const params = useParams(); return (
        @@ -404,10 +406,10 @@ export default () => { The `useRouteMatch` hook attempts to match the current URL in the same way that a Route would. It’s mostly useful for getting access to the match data without actually rendering a `` ```tsx -import { useRouteMatch } from "umi"; +import { useRouteMatch } from 'umi'; export default () => { - const match = useRouteMatch() + const match = useRouteMatch(); return (
          @@ -426,6 +428,19 @@ export default () => { Umi 内核的 Service 方法,用于测试,或调用 Umi 底层命令。 +#### 自定义配置文件 + +umi 默认会依次(相对应用根目录)读取`.umirc.ts`、`.umirc.js`、`config/config.ts`、`config/config.js`作为用户配置文件,也可以自定义用户配置文件(优先级高于默认的配置文件): + +```tsx +new Service({ + configFiles: ['.mycustomrc.ts', 'config/mycustom.ts'], + // ... other options +}); +``` + +自定义用户配置文件通常用于基于 umi 或 umi-core 深度定制开发框架的场景。 + ### utils utils 方法,给插件使用,和插件里的 api.utils 是同一个底层库。 diff --git a/packages/core/src/Config/Config.test.ts b/packages/core/src/Config/Config.test.ts index f8c3d7ea5b10..0fb6813088b5 100644 --- a/packages/core/src/Config/Config.test.ts +++ b/packages/core/src/Config/Config.test.ts @@ -26,6 +26,24 @@ test('umirc-typescript', async () => { expect(service.userConfig).toEqual({ foo: 'bar' }); }); +test('custom-config-file', async () => { + const cwd = join(fixtures, 'custom-config-file'); + const service = new Service({ + cwd, + configFiles: ['.foorc.js'], + }); + expect(service.userConfig).toEqual({ foo: 'bar' }); +}); + +test('custom-config-file-ts', async () => { + const cwd = join(fixtures, 'custom-config-file-ts'); + const service = new Service({ + cwd, + configFiles: ['.foorc.ts'], + }); + expect(service.userConfig).toEqual({ foo: 'bar' }); +}); + test('config-config', async () => { const cwd = join(fixtures, 'config-config'); const service = new Service({ diff --git a/packages/core/src/Config/Config.ts b/packages/core/src/Config/Config.ts index 121fb75bbece..e223843c421f 100644 --- a/packages/core/src/Config/Config.ts +++ b/packages/core/src/Config/Config.ts @@ -34,9 +34,10 @@ interface IOpts { cwd: string; service: Service; localConfig?: boolean; + configFiles?: string[]; } -const CONFIG_FILES = [ +const DEFAULT_CONFIG_FILES = [ '.umirc.ts', '.umirc.js', 'config/config.ts', @@ -51,11 +52,17 @@ export default class Config { config?: object; localConfig?: boolean; configFile?: string | null; + configFiles = DEFAULT_CONFIG_FILES; constructor(opts: IOpts) { this.cwd = opts.cwd || process.cwd(); this.service = opts.service; this.localConfig = opts.localConfig; + + if (Array.isArray(opts.configFiles)) { + // 配置的优先读取 + this.configFiles = lodash.uniq(opts.configFiles.concat(this.configFiles)); + } } async getDefaultConfig() { @@ -212,14 +219,16 @@ export default class Config { getConfigFile(): string | null { // TODO: support custom config file - const configFile = CONFIG_FILES.find((f) => existsSync(join(this.cwd, f))); + const configFile = this.configFiles.find((f) => + existsSync(join(this.cwd, f)), + ); return configFile ? winPath(configFile) : null; } getWatchFilesAndDirectories() { const umiEnv = process.env.UMI_ENV; - const configFiles = lodash.clone(CONFIG_FILES); - CONFIG_FILES.forEach((f) => { + const configFiles = lodash.clone(this.configFiles); + this.configFiles.forEach((f) => { if (this.localConfig) configFiles.push(this.addAffix(f, 'local')); if (umiEnv) configFiles.push(this.addAffix(f, umiEnv)); }); diff --git a/packages/core/src/Config/fixtures/custom-config-file-ts/.foorc.ts b/packages/core/src/Config/fixtures/custom-config-file-ts/.foorc.ts new file mode 100644 index 000000000000..f868fa3e7185 --- /dev/null +++ b/packages/core/src/Config/fixtures/custom-config-file-ts/.foorc.ts @@ -0,0 +1,4 @@ + +export default { + foo: 'bar', +}; diff --git a/packages/core/src/Config/fixtures/custom-config-file-ts/.umirc.ts b/packages/core/src/Config/fixtures/custom-config-file-ts/.umirc.ts new file mode 100644 index 000000000000..292bd9bf4e40 --- /dev/null +++ b/packages/core/src/Config/fixtures/custom-config-file-ts/.umirc.ts @@ -0,0 +1,4 @@ + +export default { + foo: 'biz', +}; diff --git a/packages/core/src/Config/fixtures/custom-config-file/.foorc.js b/packages/core/src/Config/fixtures/custom-config-file/.foorc.js new file mode 100644 index 000000000000..f868fa3e7185 --- /dev/null +++ b/packages/core/src/Config/fixtures/custom-config-file/.foorc.js @@ -0,0 +1,4 @@ + +export default { + foo: 'bar', +}; diff --git a/packages/core/src/Config/fixtures/custom-config-file/.umirc.js b/packages/core/src/Config/fixtures/custom-config-file/.umirc.js new file mode 100644 index 000000000000..292bd9bf4e40 --- /dev/null +++ b/packages/core/src/Config/fixtures/custom-config-file/.umirc.js @@ -0,0 +1,4 @@ + +export default { + foo: 'biz', +}; diff --git a/packages/core/src/Service/Service.ts b/packages/core/src/Service/Service.ts index 772330492cc6..d48e79aee68a 100644 --- a/packages/core/src/Service/Service.ts +++ b/packages/core/src/Service/Service.ts @@ -28,6 +28,7 @@ export interface IServiceOpts { pkg?: IPackage; presets?: string[]; plugins?: string[]; + configFiles?: string[]; env?: NodeEnv; } @@ -113,10 +114,15 @@ export default class Service extends EventEmitter { // get user config without validation logger.debug('get user config'); + const configFiles = opts.configFiles; this.configInstance = new Config({ cwd: this.cwd, service: this, localConfig: this.env === 'development', + configFiles: + Array.isArray(configFiles) && !!configFiles[0] + ? configFiles + : undefined, }); this.userConfig = this.configInstance.getUserConfig(); logger.debug('userConfig:');