首頁 > 軟體

umi外掛開發仿dumi專案實現頁面佈局詳解

2023-01-28 18:02:40

實現思路

上一章我們已經完成/docs目錄下檔案自動生路由功能,本章我們將在此基礎上,實現自動生成頁面導航的功能。

  • 使用預設模板提供的layout展示路由切換
  • 使用自定義主題外掛

使用預設專案提供的layout檔案

在我們建立預設umi專案後,會在/src/layouts下生成一個佈局檔案:

同時在上一章節我們列印modifyRouteshook的入參,可以看到umi會將該檔案轉成一個layout router物件,如圖所示:

因此我們可以直接將這個layout的id屬性,賦值到自動生成的路由的parentId屬性上,並新增該物件到返回值中:

// /src/features/routes.ts
import path from 'path';
import type { IApi } from 'umi';
import type { IRoute } from '@umijs/core/dist/types';
import { getConventionRoutes } from '@umijs/core';
export default (api: IApi) => {
  api.describe({ key: 'domi:routes' });
  api.modifyRoutes((oRoutes: Record<string, IRoute>) => {
    const routes: Record<string, IRoute> = {}
    const docDir = 'docs'
    // 獲取某個目錄下所有可以設定成umi約定路由的檔案
    const dirRoutes: Record<string, IRoute> = getConventionRoutes({
      base: path.join(api.cwd, docDir),
    });
    // 預設提供的佈局layout的Id
    let docLayoutId : undefined | string = '@@/global-layout';
    // 從舊路由物件中獲取放入返回值中
    routes[docLayoutId] = oRoutes[docLayoutId]
    Object.entries(dirRoutes).forEach(([key, route]) => {
      // 這裡將檔案的路徑改為絕對路徑,否則umi會預設找/src/pages下元件
      route.file = path.resolve(docDir, route.file);
      // 給頁面物件賦值佈局Id
      route.parentId = docLayoutId
      routes[route.id] = route;
    });
    return routes;
  });
};

同時我們修改佈局檔案,將導航改成我們的測試頁面路由:

// /src/layouts/index.tsx
import { Link, Outlet } from 'umi';
import styles from './index.less';
export default function Layout() {
  return (
    <div className={styles.navs}>
      <ul>
        <li><Link to="/">Home</Link></li>
        <li><Link to="/button">Button</Link></li>
      </ul>
      <Outlet />
    </div>
  );
}

執行專案可以看到佈局檔案已新增到頁面中,並可以切換路由:

自定義主題

上面我們通過最簡單的方式使用了預設提供的佈局檔案,這種方式對頁面侷限性比較大,由於各個專案對頁面的展示要求不一樣,dumi提供了主題外掛來靈活擴充套件使用者自定義佈局。同時提供了預設主題,使用者可以選擇性覆蓋預設樣式。

本節我們將實現其中的預設主題載入

準備工作

建立主題外掛,並註冊到外掛設定中

mkdir /src/features/theme.ts
import { defineConfig } from "umi";
export default defineConfig({
    plugins: [
        './src/features/routes.ts',
        './src/features/theme.ts',
    ],
});

建立預設主題目錄,將/src/layouts/index.tsx檔案複製到這裡

mkdir /src/client/theme-default/layouts/DocLayout
cp /src/layouts/index.tsx /src/client/theme-default/layouts/DocLayout/index.tsx
cp /src/layouts/index.less /src/client/theme-default/layouts/DocLayout/index.less

然後/src/layouts就沒用了,可以刪掉

主題外掛功能

dumi主題外掛主要提供的功能有:

  • 載入預設主題佈局檔案
  • 載入國際化語言套件
  • 合併預設主題及自定義主題
  • ...等其他與頁面相關功能

本章我們將實現預設佈局的載入,並設定到路由中。

modifyAppData

umi提供modifyAppData勾點,用於初始收集應用資料,在dumi中使用這個勾點來初始化主題資料。

我們在主題外掛中提供的主題資料後續會被用在修改路由中,即在modifyRoutes階段使用。因為modifyRoutes是在appData外掛的modifyAppData階段中執行,所以通過before: 'appData讓主題外掛的modifyAppDataappData外掛的modifyAppData之前先初始化完成,這樣在modifyRoutes中就可以使用到主題資料。

外掛程式碼

// /src/features/theme.ts
import path from 'path';
import type { IApi } from 'umi';
import { glob, winPath } from 'umi/plugin-utils';
const DEFAULT_THEME_PATH = path.join(__dirname, '../../src/client/theme-default');
export default async(api: IApi) => {
    api.describe({ key: 'domi:theme' });
    api.modifyAppData({
      before: 'appData',
      async fn(memo: any) {
        const defaultThemeData = loadTheme(DEFAULT_THEME_PATH);
        // @ts-ignore
        api.service.themeData = defaultThemeData
        return memo;
      },
    });
}
/**
 * 載入主題資訊 
 */
function loadTheme(dir: string) {
    return {
      name: path.basename(dir),
      path: dir,
      layouts: getComponentMapFromDir(
        'layouts/{GlobalLayout,DocLayout,DemoLayout}{.,/index.}{js,jsx,ts,tsx}',
        dir,
      ),
    };
};
/**
 * 提取dir目錄下符合條件的元件資訊
 */
function getComponentMapFromDir(globExp: string, dir: string) {
    return glob
      .sync(globExp, { cwd: dir })
      .reduce<any>((ret, file) => {
        const specifier = path.basename(
          winPath(file).replace(/(/index)?.[a-z]+$/, ''),
        );
        // ignore non-component files
        if (/^[A-Zd]/.test(specifier)) {
          ret[specifier] = {
            specifier,
            source: winPath(path.join(dir, file)),
          };
        }
        return ret;
    }, {});
}

另一個比較不優雅的地方是這裡使用了api.service來儲存生成的主題資料,同樣因為上面提到的階段問題,modifyRoutes是在modifyAppData中執行,所以這裡只能用全域性變數來儲存,否則在修改路由階段拿不到這裡的資料。

執行完成後,api.service.themeData就得到了主題相關的資料:

{
  name: 'theme-default',
  path: 'D:\project\domi\src\client\theme-default',
  layouts: {
    DocLayout: {
      specifier: 'DocLayout',
      source: 'D:/project/domi/src/client/theme-default/layouts/DocLayout/index.tsx'
    }
  }
}

生成layout路由物件

前面我們使用了模板自帶的物件@@/global-layout作為佈局模板,現在我們可以將它改成動態新增主題佈局。

我們直接在路由外掛中使用主題外掛中生成的佈局資料,程式碼很簡單,根據前面的layout來生成一個佈局路由,並新增到返回值中即可,這樣所有parentIdDocLayout.specifier的頁面就能使用該佈局了。

// /src/features/routes.ts
...
let docLayoutId : undefined | string = undefined;
// @ts-ignore
const { DocLayout } = api.service.themeData.layouts;
// 從舊路由物件中獲取放入返回值中
if (DocLayout) {
  docLayoutId = DocLayout.specifier;
  routes[DocLayout.specifier] = {
    id: DocLayout.specifier,
    path: '/',
    file: DocLayout.source,
    parentId: undefined,
    absPath: '/',
    isLayout: true,
  };
}
...

使用同步虛擬碼來描述上面流程

// /umi/packages/core/src/service/service.ts 中程式碼
service.collectAppData() {
    // /src/features/theme.ts中程式碼
    // 設定before: 'appData' 使其先於appData.modifyAppData執行
    themePlugin.modifyAppData() {
        // 這裡載入主題資料
        api.service.themeData = loadTheme(DEFAULT_THEME_PATH);
    }
    // /umi/packages/preset-umi/src/features/appData/appData.ts 中程式碼
    appDataPlugin.modifyAppData() {
        // /src/features/routes.ts中程式碼
        routesPlugin.modifyRoutes() {
            // 這裡使用主題資料生成佈局路由
            const { DocLayout } = api.service.themeData.layouts;
            routes[DocLayout.specifier] = {
                ...
                isLayout: true,
            };
        }
    }
}

執行檢查

至此我們已經完成生成預設佈局,實現了簡易的主題外掛,執行程式碼可以看到和上節執行結果一樣:

路由和頁面問題基本解決了,接下來就要開始正式解析markdown檔案。

以上就是umi外掛開發仿dumi專案實現頁面佈局詳解的詳細內容,更多關於umi外掛仿dumi頁面佈局的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com