├── .vscode ├── settings.json └── launch.json ├── src ├── renderer │ ├── public │ │ └── renderer.js │ ├── models │ │ └── global.js │ ├── assets │ │ └── yay.jpg │ ├── .umi-production │ │ ├── core │ │ │ ├── polyfill.ts │ │ │ ├── plugin.ts │ │ │ ├── umiExports.ts │ │ │ ├── history.ts │ │ │ ├── routes.ts │ │ │ ├── pluginRegister.ts │ │ │ └── pluginConfig.d.ts │ │ ├── plugin-initial-state │ │ │ ├── models │ │ │ │ └── initialState.ts │ │ │ ├── exports.ts │ │ │ ├── runtime.tsx │ │ │ └── Provider.tsx │ │ ├── plugin-model │ │ │ ├── helpers │ │ │ │ ├── constant.tsx │ │ │ │ ├── dispatcher.tsx │ │ │ │ └── executor.tsx │ │ │ ├── runtime.tsx │ │ │ ├── Provider.tsx │ │ │ └── useModel.tsx │ │ ├── plugin-dva │ │ │ ├── exports.ts │ │ │ ├── runtime.tsx │ │ │ ├── connect.ts │ │ │ └── dva.ts │ │ ├── plugin-helmet │ │ │ └── exports.ts │ │ ├── umi.ts │ │ └── plugin-request │ │ │ └── request.ts │ ├── pages │ │ ├── document.ejs │ │ └── index.js │ └── config │ │ └── config.js └── main │ └── main.js ├── .prettierrc ├── .prettierignore ├── .gitignore ├── .buildrc.js ├── package.json └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /src/renderer/public/renderer.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | window.electron = require('electron') 3 | -------------------------------------------------------------------------------- /src/renderer/models/global.js: -------------------------------------------------------------------------------- 1 | export default { 2 | state: { 3 | msg: 'Hello World', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } -------------------------------------------------------------------------------- /src/renderer/assets/yay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/williamnie/umi3.0-electron/HEAD/src/renderer/assets/yay.jpg -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | release/ 3 | .DS_Store 4 | .idea/ 5 | yarn-error.log 6 | npm-debug.log 7 | src/renderer/pages/.umi/ -------------------------------------------------------------------------------- /src/renderer/.umi-production/core/polyfill.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import 'core-js'; 3 | import 'regenerator-runtime/runtime'; 4 | export {}; 5 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-initial-state/models/initialState.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export default () => ({ loading: false, refresh: () => {} }) -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-model/helpers/constant.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React from 'react'; 3 | 4 | export const UmiContext = React.createContext({}); 5 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-dva/exports.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | export { connect, useDispatch, useStore, useSelector } from 'dva'; 4 | export { getApp as getDvaApp } from './dva'; 5 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-helmet/exports.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // @ts-ignore 3 | export { Helmet } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/react-helmet'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | src/renderer/pages/.umi 4 | src/renderer/pages/.umi-production 5 | release 6 | yarn-error.log 7 | yarn.lock 8 | .history 9 | .vscode 10 | .umi 11 | src/renderer/.umi/ -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-dva/runtime.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React from 'react'; 3 | import { _DvaContainer, getApp, _onCreate } from './dva'; 4 | 5 | export function rootContainer(container, opts) { 6 | return React.createElement(_DvaContainer, opts, container); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-initial-state/exports.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | // @ts-ignore 4 | import { InitialState as InitialStateType } from '../plugin-initial-state/models/initialState'; 5 | 6 | export type InitialState = InitialStateType; 7 | export const __PLUGIN_INITIAL_STATE = 1; 8 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-model/runtime.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /* eslint-disable import/no-dynamic-require */ 3 | import React from 'react'; 4 | import Provider from './Provider'; 5 | 6 | export function rootContainer(container: React.ReactNode) { 7 | return React.createElement( 8 | Provider, 9 | null, 10 | container, 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/core/plugin.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Plugin } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/umi/node_modules/@umijs/runtime'; 3 | 4 | const plugin = new Plugin({ 5 | validKeys: ['modifyClientRenderOpts','patchRoutes','rootContainer','render','onRouteChange','__mfsu','dva','getInitialState','initialStateConfig','request',], 6 | }); 7 | 8 | export { plugin }; 9 | -------------------------------------------------------------------------------- /src/renderer/pages/document.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | netPassClient 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/core/umiExports.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export { history } from './history'; 3 | export { plugin } from './plugin'; 4 | export * from '../plugin-dva/exports'; 5 | export * from '../plugin-dva/connect'; 6 | export * from '../plugin-initial-state/exports'; 7 | export * from '../plugin-model/useModel'; 8 | export * from '../plugin-request/request'; 9 | export * from '../plugin-helmet/exports'; 10 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-initial-state/runtime.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React from 'react'; 3 | import Provider from './Provider'; 4 | 5 | export function rootContainer(container: React.ReactNode) { 6 | return React.createElement( 7 | // 这里的 plugin-initial-state 不能从 constant 里取,里面有 path 依赖 8 | // 但 webpack-5 没有 node 补丁(包括 path) 9 | Provider, 10 | null, 11 | container, 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/pages/index.js: -------------------------------------------------------------------------------- 1 | 2 | import React, { Component } from 'react'; 3 | import { connect } from 'dva'; 4 | class Index extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = {}; 8 | } 9 | render() { 10 | return ( 11 |
12 |

{this.props.global.msg}

13 |
14 | ); 15 | } 16 | } 17 | 18 | export default connect(({ global }) => ({ global }))(Index) -------------------------------------------------------------------------------- /src/renderer/config/config.js: -------------------------------------------------------------------------------- 1 | const buildrc = require("../../../.buildrc.js"); 2 | 3 | export default { 4 | history: { type: 'hash' }, 5 | outputPath: `../../dist/renderer`, 6 | publicPath: './', 7 | title: 'Electron', 8 | nodeModulesTransform: { type: 'none' }, 9 | webpack5: {}, 10 | mfsu: {}, 11 | 12 | alias: buildrc.webpack.alias, 13 | ignoreMomentLocale: true, 14 | routes: [ 15 | { 16 | path: '/', 17 | component: './index', 18 | }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-model/helpers/dispatcher.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export default class Dispatcher { 3 | callbacks = {}; 4 | 5 | data = {}; 6 | 7 | update = (namespace: string) => { 8 | (this.callbacks[namespace] || []).forEach( 9 | (callback: (val: any) => void) => { 10 | try { 11 | const data = this.data[namespace]; 12 | callback(data); 13 | } catch (e) { 14 | callback(undefined); 15 | } 16 | }, 17 | ); 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /.buildrc.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require("path"); 3 | 4 | module.exports = { 5 | webpack: { 6 | alias: { 7 | "@": path.resolve(__dirname, "src/renderer"), 8 | components: path.resolve(__dirname, "src/renderer/components/"), 9 | pages: `${__dirname}/src/renderer/pages`, 10 | models: `${__dirname}/src/renderer/models`, 11 | services: `${__dirname}/src/renderer/services`, 12 | plugin: `${__dirname}/src/renderer/plugin`, 13 | utils: `${__dirname}/src/renderer/utils`, 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/core/history.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { createHashHistory, History } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/umi/node_modules/@umijs/runtime'; 3 | 4 | let options = { 5 | "basename": "/" 6 | }; 7 | if ((window).routerBase) { 8 | options.basename = (window).routerBase; 9 | } 10 | 11 | // remove initial history because of ssr 12 | let history: History = process.env.__IS_SERVER ? null : createHashHistory(options); 13 | export const createHistory = (hotReload = false) => { 14 | if (!hotReload) { 15 | history = createHashHistory(options); 16 | } 17 | 18 | return history; 19 | }; 20 | 21 | export { history }; 22 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/core/routes.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React from 'react'; 3 | import { ApplyPluginsType } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/umi/node_modules/@umijs/runtime'; 4 | import * as umiExports from './umiExports'; 5 | import { plugin } from './plugin'; 6 | 7 | export function getRoutes() { 8 | const routes = [ 9 | { 10 | "path": "/", 11 | "component": require('/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/src/renderer/pages/index').default, 12 | "exact": true 13 | } 14 | ]; 15 | 16 | // allow user to extend routes 17 | plugin.applyPlugins({ 18 | key: 'patchRoutes', 19 | type: ApplyPluginsType.event, 20 | args: { routes }, 21 | }); 22 | 23 | return routes; 24 | } 25 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/core/pluginRegister.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { plugin } from './plugin'; 3 | import * as Plugin_0 from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/src/renderer/.umi-production/plugin-dva/runtime.tsx'; 4 | import * as Plugin_1 from '../plugin-initial-state/runtime'; 5 | import * as Plugin_2 from '../plugin-model/runtime'; 6 | 7 | plugin.register({ 8 | apply: Plugin_0, 9 | path: '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/src/renderer/.umi-production/plugin-dva/runtime.tsx', 10 | }); 11 | plugin.register({ 12 | apply: Plugin_1, 13 | path: '../plugin-initial-state/runtime', 14 | }); 15 | plugin.register({ 16 | apply: Plugin_2, 17 | path: '../plugin-model/runtime', 18 | }); 19 | 20 | export const __mfsu = 1; 21 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "program": "${workspaceFolder}/dist/main/main.js", 12 | "preLaunchTask": "tsc: build - tsconfig.json", 13 | "outFiles": [ 14 | "${workspaceFolder}/dist/**/*.js" 15 | ] 16 | }, 17 | { 18 | "name": "Debug Main Process", 19 | "type": "node", 20 | "request": "launch", 21 | "cwd": "${workspaceFolder}", 22 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 23 | "windows": { 24 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 25 | }, 26 | "args" : ["."] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /src/main/main.js: -------------------------------------------------------------------------------- 1 | import { app, BrowserWindow } from 'electron'; 2 | import * as path from 'path'; 3 | 4 | let mainWindow = null; 5 | 6 | function createWindow() { 7 | mainWindow = new BrowserWindow({ 8 | height: 800, 9 | width: 1200, 10 | webPreferences: { 11 | nodeIntegration: true, 12 | contextIsolation: false, 13 | // nodeIntegrationInWorker: true, 14 | // preload: path.join(__dirname, './public/renderer.js') 这里必须用绝对地址 15 | } 16 | }); 17 | 18 | if (process.env.NODE_ENV === 'development') { 19 | mainWindow.loadURL('http://localhost:8090/#/'); 20 | mainWindow.webContents.openDevTools(); 21 | } else { 22 | mainWindow.loadURL(`file:${path.join(__dirname, './dist/renderer/index.html')}`) 23 | } 24 | 25 | mainWindow.on('closed', () => { 26 | mainWindow = null; 27 | }); 28 | } 29 | 30 | app.on('ready', createWindow); 31 | 32 | app.on('window-all-closed', () => { 33 | if (process.platform !== 'darwin') { 34 | app.quit(); 35 | } 36 | }); 37 | 38 | app.on('activate', () => { 39 | if (mainWindow === null) { 40 | createWindow(); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-initial-state/Provider.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | import React, { useRef, useEffect } from "react"; 4 | import { plugin } from "../core/umiExports"; 5 | import { ApplyPluginsType } from 'umi'; 6 | import { useModel } from "../plugin-model/useModel"; 7 | if (typeof useModel !== "function") { 8 | throw new Error( 9 | "[plugin-initial-state]: useModel is not a function, @umijs/plugin-model is required." 10 | ); 11 | } 12 | 13 | interface Props { 14 | children: React.ReactNode; 15 | } 16 | export default (props: Props) => { 17 | const { children } = props; 18 | const appLoaded = useRef(false); 19 | // 获取用户的配置,暂时只支持 loading 20 | const useRuntimeConfig = 21 | plugin.applyPlugins({ 22 | key: "initialStateConfig", 23 | type: ApplyPluginsType.modify, 24 | initialValue: {}, 25 | }) || {}; 26 | const { loading = false } = useModel("@@initialState") || {}; 27 | useEffect(() => { 28 | if (!loading) { 29 | appLoaded.current = true; 30 | } 31 | }, [loading]); 32 | // initial state loading 时,阻塞渲染 33 | if (loading && !appLoaded.current) { 34 | return useRuntimeConfig.loading || null; 35 | } 36 | return children; 37 | }; 38 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-model/Provider.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React from 'react'; 3 | import initialState from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/src/renderer/.umi-production/plugin-initial-state/models/initialState'; 4 | 5 | // @ts-ignore 6 | import Dispatcher from './helpers/dispatcher'; 7 | // @ts-ignore 8 | import Executor from './helpers/executor'; 9 | // @ts-ignore 10 | import { UmiContext } from './helpers/constant'; 11 | 12 | export const models = { '@@initialState': initialState, }; 13 | 14 | export type Model = { 15 | [key in keyof typeof models]: ReturnType; 16 | }; 17 | 18 | export type Models = Model[T] 19 | 20 | const dispatcher = new Dispatcher!(); 21 | const Exe = Executor!; 22 | 23 | export default ({ children }: { children: React.ReactNode }) => { 24 | 25 | return ( 26 | 27 | { 28 | Object.entries(models).map(pair => ( 29 | { 30 | const [ns] = pair as [keyof typeof models, any]; 31 | dispatcher.data[ns] = val; 32 | dispatcher.update(ns); 33 | }} /> 34 | )) 35 | } 36 | {children} 37 | 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/umi.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import './core/polyfill'; 3 | 4 | import { plugin } from './core/plugin'; 5 | import './core/pluginRegister'; 6 | import { createHistory } from './core/history'; 7 | import { ApplyPluginsType } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/umi/node_modules/@umijs/runtime'; 8 | import { renderClient } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/@umijs/renderer-react'; 9 | import { getRoutes } from './core/routes'; 10 | 11 | 12 | 13 | 14 | const getClientRender = (args: { hot?: boolean; routes?: any[] } = {}) => plugin.applyPlugins({ 15 | key: 'render', 16 | type: ApplyPluginsType.compose, 17 | initialValue: () => { 18 | const opts = plugin.applyPlugins({ 19 | key: 'modifyClientRenderOpts', 20 | type: ApplyPluginsType.modify, 21 | initialValue: { 22 | routes: args.routes || getRoutes(), 23 | plugin, 24 | history: createHistory(args.hot), 25 | isServer: process.env.__IS_SERVER, 26 | rootElement: 'root', 27 | defaultTitle: `umi 3.0`, 28 | }, 29 | }); 30 | return renderClient(opts); 31 | }, 32 | args, 33 | }); 34 | 35 | const clientRender = getClientRender(); 36 | export default clientRender(); 37 | 38 | 39 | window.g_umi = { 40 | version: '3.5.17', 41 | }; 42 | 43 | 44 | // hot module replacement 45 | // @ts-ignore 46 | if (module.hot) { 47 | // @ts-ignore 48 | module.hot.accept('./core/routes', () => { 49 | const ret = require('./core/routes'); 50 | if (ret.then) { 51 | ret.then(({ getRoutes }) => { 52 | getClientRender({ hot: true, routes: getRoutes() })(); 53 | }); 54 | } else { 55 | getClientRender({ hot: true, routes: ret.getRoutes() })(); 56 | } 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-dva/connect.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { IRoute } from '@umijs/core'; 3 | import { AnyAction } from 'redux'; 4 | import React from 'react'; 5 | import { EffectsCommandMap, SubscriptionAPI } from 'dva'; 6 | import { match } from 'react-router-dom'; 7 | import { Location, LocationState, History } from 'history'; 8 | 9 | export * from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/src/renderer/models/global'; 10 | 11 | export interface Action { 12 | type: T 13 | } 14 | 15 | export type Reducer = ( 16 | state: S | undefined, 17 | action: A 18 | ) => S; 19 | 20 | export type ImmerReducer = ( 21 | state: S, 22 | action: A 23 | ) => void; 24 | 25 | export type Effect = ( 26 | action: AnyAction, 27 | effects: EffectsCommandMap, 28 | ) => void; 29 | 30 | /** 31 | * @type P: Type of payload 32 | * @type C: Type of callback 33 | */ 34 | export type Dispatch

void> = (action: { 35 | type: string; 36 | payload?: P; 37 | callback?: C; 38 | [key: string]: any; 39 | }) => any; 40 | 41 | export type Subscription = (api: SubscriptionAPI, done: Function) => void | Function; 42 | 43 | export interface Loading { 44 | global: boolean; 45 | effects: { [key: string]: boolean | undefined }; 46 | models: { 47 | [key: string]: any; 48 | }; 49 | } 50 | 51 | /** 52 | * @type P: Params matched in dynamic routing 53 | */ 54 | export interface ConnectProps< 55 | P extends { [K in keyof P]?: string } = {}, 56 | S = LocationState, 57 | T = {} 58 | > { 59 | dispatch?: Dispatch; 60 | // https://github.com/umijs/umi/pull/2194 61 | match?: match

; 62 | location: Location & { query: T }; 63 | history: History; 64 | route: IRoute; 65 | } 66 | 67 | export type RequiredConnectProps< 68 | P extends { [K in keyof P]?: string } = {}, 69 | S = LocationState, 70 | T = {} 71 | > = Required> 72 | 73 | /** 74 | * @type T: React props 75 | * @type U: match props types 76 | */ 77 | export type ConnectRC< 78 | T = {}, 79 | U = {}, 80 | S = {}, 81 | Q = {} 82 | > = React.ForwardRefRenderFunction>; 83 | 84 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-model/helpers/executor.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import React, { useEffect, useRef, useMemo } from 'react'; 3 | 4 | interface ExecutorProps { 5 | hook: () => any; 6 | onUpdate: (val: any) => void; 7 | namespace: string; 8 | } 9 | 10 | export default (props: ExecutorProps) => { 11 | const { hook, onUpdate, namespace } = props; 12 | 13 | const updateRef = useRef(onUpdate); 14 | updateRef.current = onUpdate; 15 | const initialLoad = useRef(false); 16 | 17 | let data: any; 18 | try { 19 | data = hook(); 20 | if ( 21 | process.env.NODE_ENV === 'development' && 22 | typeof document !== 'undefined' 23 | ) { 24 | try { 25 | let count = Object.keys( 26 | ((window as any)._umi_useModel_dev_tool_log || {})[namespace] || {}, 27 | ).length; 28 | (window as any)._umi_useModel_dev_tool = Object.assign( 29 | (window as any)._umi_useModel_dev_tool || {}, 30 | { 31 | [namespace]: data, 32 | }, 33 | ); 34 | (window as any)._umi_useModel_dev_tool_log = Object.assign( 35 | (window as any)._umi_useModel_dev_tool_log || {}, 36 | { 37 | [namespace]: Object.assign( 38 | ((window as any)._umi_useModel_dev_tool_log || {})[namespace] || 39 | {}, 40 | { 41 | [count]: data, 42 | }, 43 | ), 44 | }, 45 | ); 46 | window.dispatchEvent( 47 | new CustomEvent('_umi_useModel_update', { 48 | detail: { 49 | namespace, 50 | time: Date.now(), 51 | data, 52 | index: count, 53 | }, 54 | }), 55 | ); 56 | } catch (e) { 57 | // dev tool 记录失败、可能是低版本浏览器,忽略 58 | } 59 | } 60 | } catch (e) { 61 | console.error( 62 | `plugin-model: Invoking '${namespace || 'unknown'}' model failed:`, 63 | e, 64 | ); 65 | } 66 | 67 | // 首次执行时立刻返回初始值 68 | useMemo(() => { 69 | updateRef.current(data); 70 | initialLoad.current = false; 71 | }, []); 72 | 73 | // React 16.13 后 update 函数用 useEffect 包裹 74 | useEffect(() => { 75 | if (initialLoad.current) { 76 | updateRef.current(data); 77 | } else { 78 | initialLoad.current = true; 79 | } 80 | }); 81 | 82 | return <>; 83 | }; 84 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-dva/dva.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { Component } from 'react'; 3 | import { ApplyPluginsType } from 'umi'; 4 | import dva from 'dva'; 5 | // @ts-ignore 6 | import createLoading from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/dva-loading/dist/index.esm.js'; 7 | import { plugin, history } from '../core/umiExports'; 8 | import ModelGlobal0 from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/src/renderer/models/global.js'; 9 | import dvaImmer, { enableES5, enableAllPlugins } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/dva-immer/dist/index.js'; 10 | 11 | let app:any = null; 12 | 13 | export function _onCreate(options = {}) { 14 | const runtimeDva = plugin.applyPlugins({ 15 | key: 'dva', 16 | type: ApplyPluginsType.modify, 17 | initialValue: {}, 18 | }); 19 | app = dva({ 20 | history, 21 | 22 | ...(runtimeDva.config || {}), 23 | // @ts-ignore 24 | ...(typeof window !== 'undefined' && window.g_useSSR ? { initialState: window.g_initialProps } : {}), 25 | ...(options || {}), 26 | }); 27 | 28 | app.use(createLoading()); 29 | app.use(dvaImmer()); 30 | (runtimeDva.plugins || []).forEach((plugin:any) => { 31 | app.use(plugin); 32 | }); 33 | app.model({ namespace: 'global', ...ModelGlobal0 }); 34 | return app; 35 | } 36 | 37 | export function getApp() { 38 | return app; 39 | } 40 | 41 | /** 42 | * whether browser env 43 | * 44 | * @returns boolean 45 | */ 46 | function isBrowser(): boolean { 47 | return typeof window !== 'undefined' && 48 | typeof window.document !== 'undefined' && 49 | typeof window.document.createElement !== 'undefined' 50 | } 51 | 52 | export class _DvaContainer extends Component { 53 | constructor(props: any) { 54 | super(props); 55 | // run only in client, avoid override server _onCreate() 56 | if (isBrowser()) { 57 | _onCreate() 58 | } 59 | } 60 | 61 | componentWillUnmount() { 62 | let app = getApp(); 63 | app._models.forEach((model:any) => { 64 | app.unmodel(model.namespace); 65 | }); 66 | app._models = []; 67 | try { 68 | // 释放 app,for gc 69 | // immer 场景 app 是 read-only 的,这里 try catch 一下 70 | app = null; 71 | } catch(e) { 72 | console.error(e); 73 | } 74 | } 75 | 76 | render() { 77 | let app = getApp(); 78 | app.router(() => this.props.children); 79 | return app.start()(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-model/useModel.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { useState, useEffect, useContext, useRef } from 'react'; 3 | // @ts-ignore 4 | import isEqual from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/@umijs/plugin-model/node_modules/fast-deep-equal/index.js'; 5 | // @ts-ignore 6 | import { UmiContext } from './helpers/constant'; 7 | import { Model, models } from './Provider'; 8 | 9 | export type Models = Model[T] 10 | 11 | export function useModel>(model: T): Model[T] 12 | export function useModel, U>(model: T, selector: (model: Model[T]) => U): U 13 | 14 | export function useModel, U>( 15 | namespace: T, 16 | updater?: (model: Model[T]) => U 17 | ) : typeof updater extends undefined ? Model[T] : ReturnType>{ 18 | 19 | type RetState = typeof updater extends undefined ? Model[T] : ReturnType> 20 | const dispatcher = useContext(UmiContext); 21 | const updaterRef = useRef(updater); 22 | updaterRef.current = updater; 23 | const [state, setState] = useState( 24 | () => updaterRef.current ? updaterRef.current(dispatcher.data![namespace]) : dispatcher.data![namespace] 25 | ); 26 | const stateRef = useRef(state); 27 | stateRef.current = state; 28 | 29 | const isMount = useRef(false); 30 | useEffect(() => { 31 | isMount.current = true; 32 | return () => { 33 | isMount.current = false; 34 | } 35 | }, []) 36 | 37 | useEffect(() => { 38 | const handler = (e: any) => { 39 | if(!isMount.current) { 40 | // 如果 handler 执行过程中,组件被卸载了,则强制更新全局 data 41 | setTimeout(() => { 42 | dispatcher.data![namespace] = e; 43 | dispatcher.update(namespace); 44 | }); 45 | } else { 46 | if(updater && updaterRef.current){ 47 | const currentState = updaterRef.current(e); 48 | const previousState = stateRef.current 49 | if(!isEqual(currentState, previousState)){ 50 | setState(currentState); 51 | } 52 | } else { 53 | setState(e); 54 | } 55 | } 56 | } 57 | try { 58 | dispatcher.callbacks![namespace]!.add(handler); 59 | dispatcher.update(namespace); 60 | } catch (e) { 61 | dispatcher.callbacks![namespace] = new Set(); 62 | dispatcher.callbacks![namespace]!.add(handler); 63 | dispatcher.update(namespace); 64 | } 65 | return () => { 66 | dispatcher.callbacks![namespace]!.delete(handler); 67 | } 68 | }, [namespace]); 69 | 70 | return state; 71 | }; 72 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umi-electron", 3 | "version": "1.0.0", 4 | "description": "an example based on umijs + electron", 5 | "main": "./dist/main/main.js", 6 | "private": true, 7 | "build": { 8 | "productName": "electron", 9 | "files": [ 10 | "dist/", 11 | "node_modules/", 12 | "package.json" 13 | ], 14 | "mac": { 15 | "category": "your.app.category.type" 16 | }, 17 | "win": { 18 | "target": [ 19 | "nsis" 20 | ] 21 | }, 22 | "nsis": { 23 | "oneClick": false, 24 | "perMachine": true, 25 | "allowToChangeInstallationDirectory": true 26 | }, 27 | "directories": { 28 | "output": "release" 29 | }, 30 | "appId": "com.cn.littlestrong.demo", 31 | "asar": false 32 | }, 33 | "authors": [ 34 | "Williamnie " 35 | ], 36 | "scripts": { 37 | "start": "npm run build-main-dev && run-electron ./dist/main/main.js", 38 | "start:main": "electron-webpack dev", 39 | "start:renderer": "cross-env APP_ROOT=src/renderer PORT=8090 umi dev", 40 | "build-main-prod": "NODE_ENV=production webpack --config ./build/webpack.main.prod.config.js", 41 | "build-main-dev": "NODE_ENV=development webpack --config ./build/webpack.main.config.js", 42 | "build:renderer": "cross-env APP_ROOT=src/renderer umi build", 43 | "watch": "tsc -w", 44 | "lint": "tslint -c tslint.json -p tsconfig.json", 45 | "debug:main": "run-electron --inspect=5858 -w ./dist/main/main.js", 46 | "dist": "electron-builder", 47 | "---- 自动根据操作系统平台构建包 ----": "---- 自动根据操作系统平台构建包 ----", 48 | "pack": "npm run build:renderer && npm run build-main-prod && electron-builder --dir", 49 | "---- 构建三个操作系统平台包 ----": "---- 构建三个操作系统平台包 ----", 50 | "pack-all": "npm run build:renderer && npm run build-main-prod && electron-builder -mwl", 51 | "---- 构建 mac 操作系统平台包 ----": "---- 构建 mac 操作系统平台包 ----", 52 | "pack-mac": "npm run build:renderer && npm run build-main-prod && electron-builder -m", 53 | "---- 构建 windows 操作系统平台包 ----": "---- 构建 windows 操作系统平台包 ----", 54 | "pack-exe": "npm run build:renderer && npm run build-main-prod && electron-builder --win", 55 | "prettier": "prettier --list-different \"./**/*.{ts,tsx,js,jsx,less}\"" 56 | }, 57 | "keywords": [ 58 | "Electron", 59 | "umi", 60 | "quick", 61 | "start", 62 | "tutorial", 63 | "demo" 64 | ], 65 | "devDependencies": { 66 | "classnames": "^2.3.1", 67 | "cross-env": "^7.0.3", 68 | "electron": "^14.0.0", 69 | "electron-builder": "^22.11.7", 70 | "electron-debug": "^3.2.0", 71 | "electron-webpack": "^2.8.2", 72 | "prettier": "^2.3.2", 73 | "react": "^17.0.2", 74 | "react-dom": "^17.0.2", 75 | "run-electron": "^1.0.0", 76 | "umi": "^3.5.17", 77 | "webpack": "5.94.0", 78 | "webpack-merge": "^4.2.2", 79 | "@umijs/preset-react": "^1.8.22" 80 | }, 81 | "dependencies": {}, 82 | "electronWebpack": { 83 | "main": { 84 | "sourceDirectory": "src/main" 85 | }, 86 | "renderer": { 87 | "sourceDirectory": null 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # umi-electron-javascript 2 | 3 | ### 一个基于umijs + electron + javascript的模板 4 | 5 | ## 主要特性 6 | - 支持整个应用的热重载 7 | - 依赖升级至umi3.2.1, 8 | - 默认安装的是@umijs/preset-react 插件集,可根据自身需要安装 9 | 10 | ## 项目结构 11 | 12 | ```ssh 13 | . 14 | |-- build 15 | | |-- icon.icns // 打包后程序图标 MacOS 16 | | |-- icon.ico // 打包后程序图标 Windows 17 | | |-- webpack.base.config.js // electron-webpack 基础配置 18 | | |-- webpack.main.config.js // electron-webpack 开发配置 19 | | `-- webpack.main.prod.config.js // electron-webpack 正式配置 20 | |-- dist // 项目编译输出目录 21 | | |-- main // 主程序编译目录 22 | | `-- renderer // 页面编译目录 23 | |-- release // 打包输出目录 24 | |-- src // 开发目录 25 | | |-- main // 主程序目录 26 | | | `-- main.js // 主程序入口 27 | | `-- renderer // React项目页面 28 | | |-- assets 29 | | | `-- yay.jpg 30 | | |-- config 31 | | | |-- config.js // umijs配置 32 | | | `-- webpack.config.js // umijs-webpack配置 33 | | |-- models 34 | | | `-- global.js 35 | | |-- pages 36 | | `-- index.js 37 | | |-- public 38 | | `-- renderer.js // 如果需要引用node的api,需要在这个js里面提前引入 39 | | `-- global.js 40 | |-- package.json // 项目依赖以及打包配置 41 | `-- README.md // 项目说明文档 42 | ``` 43 | 44 | ## 环境搭建 45 | ### 安装 46 | 47 | 然后通过yarn下载依赖 48 | 49 | ```javascript 50 | $ yarn 51 | ``` 52 | 53 | ### 开发 54 | 55 | 首先通过以下命令启动渲染进程(默认端口:8000) 56 | 57 | ```javascript 58 | $ yarn start:renderer 59 | ``` 60 | 61 | 然后启动主进程 62 | 63 | ```javascript 64 | $ yarn start:main 65 | ``` 66 | 67 | ### 如何使用node的api 68 | 69 | 需要在 src/renderer/public/renderer.js中引入相关的api才可以 70 | 71 | ### 打包 72 | 73 | ```javascript 74 | $ npm run pack // 打包macOS 75 | $ npm run exe // 打包windows 76 | ``` 77 | 78 | 如果想把代码打包成一个dmg文件或者zip文件,可以执行以下命令 79 | 80 | ```javascript 81 | $ npm run dist 82 | ``` 83 | 84 | ### 打包配置说明 [`package.json`](./package.json) 85 | 86 | [electron-builder-参数参考](https://www.electron.build/configuration/configuration) 87 | 88 | [category-Mac分类参考](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8) 89 | 90 | ```js 91 | { 92 | ... 93 | "build": { 94 | "productName": "LittleStrong",// 程序名称 95 | "files": [ // 需要打包的文件 96 | "dist/", 97 | "node_modules/", 98 | "package.json" 99 | ], 100 | "mac": { // 打包mac版本 101 | "category": "your.app.category.type", // mac app分类 102 | "target": [ // 打包类型 103 | "dmg", 104 | "zip" 105 | ] 106 | }, 107 | "win": { // 打包windows版本 108 | "target": [ // 打包类型 109 | "nsis" 110 | ] 111 | }, 112 | "nsis": { 113 | "oneClick": false, 114 | "perMachine": true, 115 | "allowToChangeInstallationDirectory": true 116 | }, 117 | "directories": { // 打包后输出目录 118 | "output": "release" 119 | }, 120 | "appId": "com.cn.littlestrong.demo", // appstore包名 121 | "asar": false // 是否加密处理 122 | }, 123 | } 124 | ``` 125 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/core/pluginConfig.d.ts: -------------------------------------------------------------------------------- 1 | // Created by Umi Plugin 2 | 3 | export interface IConfigFromPlugins { 4 | "404"?: boolean 5 | routes?: { 6 | /** 7 | * Any valid URL path 8 | */ 9 | path?: string 10 | /** 11 | * A React component to render only when the location matches. 12 | */ 13 | component?: (string | (() => any)) 14 | wrappers?: string[] 15 | /** 16 | * navigate to a new location 17 | */ 18 | redirect?: string 19 | /** 20 | * When true, the active class/style will only be applied if the location is matched exactly. 21 | */ 22 | exact?: boolean 23 | routes?: any[] 24 | [k: string]: any 25 | }[] 26 | history?: { 27 | type?: ("browser" | "hash" | "memory") 28 | options?: { 29 | 30 | } 31 | } 32 | polyfill?: { 33 | imports?: string[] 34 | } 35 | alias?: { 36 | 37 | } 38 | analyze?: { 39 | analyzerMode?: ("server" | "static" | "disabled") 40 | analyzerHost?: string 41 | analyzerPort?: any 42 | openAnalyzer?: boolean 43 | generateStatsFile?: boolean 44 | statsFilename?: string 45 | logLevel?: ("info" | "warn" | "error" | "silent") 46 | defaultSizes?: ("stat" | "parsed" | "gzip") 47 | [k: string]: any 48 | } 49 | /** 50 | * postcss autoprefixer, default flexbox: no-2009 51 | */ 52 | autoprefixer?: { 53 | 54 | } 55 | base?: string 56 | chainWebpack?: (() => any) 57 | chunks?: string[] 58 | /** 59 | * more css-loader options see https://webpack.js.org/loaders/css-loader/#options 60 | */ 61 | cssLoader?: { 62 | url?: (boolean | (() => any)) 63 | import?: (boolean | (() => any)) 64 | modules?: (boolean | string | { 65 | 66 | }) 67 | sourceMap?: boolean 68 | importLoaders?: number 69 | onlyLocals?: boolean 70 | esModule?: boolean 71 | localsConvention?: ("asIs" | "camelCase" | "camelCaseOnly" | "dashes" | "dashesOnly") 72 | } 73 | cssModulesTypescriptLoader?: { 74 | mode?: ("emit" | "verify") 75 | } 76 | cssnano?: { 77 | 78 | } 79 | copy?: any[] 80 | define?: { 81 | 82 | } 83 | devScripts?: { 84 | 85 | } 86 | /** 87 | * devServer configs 88 | */ 89 | devServer?: { 90 | /** 91 | * devServer port, default 8000 92 | */ 93 | port?: number 94 | host?: string 95 | https?: ({ 96 | key?: string 97 | cert?: string 98 | [k: string]: any 99 | } | boolean) 100 | headers?: { 101 | 102 | } 103 | writeToDisk?: (boolean | (() => any)) 104 | [k: string]: any 105 | } 106 | devtool?: string 107 | /** 108 | * Code splitting for performance optimization 109 | */ 110 | dynamicImport?: { 111 | /** 112 | * loading the component before loaded 113 | */ 114 | loading?: string 115 | } 116 | /** 117 | * Code splitting for import statement syntax 118 | */ 119 | dynamicImportSyntax?: { 120 | 121 | } 122 | exportStatic?: { 123 | htmlSuffix?: boolean 124 | dynamicRoot?: boolean 125 | /** 126 | * extra render paths only enable in ssr 127 | */ 128 | extraRoutePaths?: (() => any) 129 | } 130 | externals?: ({ 131 | 132 | } | string | (() => any)) 133 | extraBabelIncludes?: any[] 134 | extraBabelPlugins?: any[] 135 | extraBabelPresets?: any[] 136 | extraPostCSSPlugins?: any[] 137 | /** 138 | * fork-ts-checker-webpack-plugin options see https://github.com/TypeStrong/fork-ts-checker-webpack-plugin#options 139 | */ 140 | forkTSChecker?: { 141 | async?: boolean 142 | typescript?: (boolean | { 143 | 144 | }) 145 | eslint?: { 146 | 147 | } 148 | issue?: { 149 | 150 | } 151 | formatter?: (string | { 152 | 153 | }) 154 | logger?: { 155 | 156 | } 157 | [k: string]: any 158 | } 159 | fastRefresh?: { 160 | 161 | } 162 | hash?: boolean 163 | ignoreMomentLocale?: boolean 164 | inlineLimit?: number 165 | lessLoader?: { 166 | 167 | } 168 | manifest?: { 169 | fileName?: string 170 | publicPath?: "" 171 | basePath?: string 172 | writeToFileEmit?: boolean 173 | } 174 | /** 175 | * open mfsu feature 176 | */ 177 | mfsu?: { 178 | development?: { 179 | output?: string 180 | } 181 | production?: { 182 | output?: string 183 | } 184 | mfName?: string 185 | exportAllMembers?: { 186 | 187 | } 188 | chunks?: string[] 189 | ignoreNodeBuiltInModules?: boolean 190 | } 191 | mountElementId?: "" 192 | mpa?: { 193 | 194 | } 195 | nodeModulesTransform?: { 196 | type?: ("all" | "none") 197 | exclude?: string[] 198 | } 199 | outputPath?: "" 200 | plugins?: string[] 201 | postcssLoader?: { 202 | 203 | } 204 | presets?: string[] 205 | proxy?: { 206 | 207 | } 208 | publicPath?: string 209 | runtimePublicPath?: boolean 210 | ssr?: { 211 | /** 212 | * force execing Page getInitialProps functions 213 | */ 214 | forceInitial?: boolean 215 | /** 216 | * remove window.g_initialProps in html 217 | */ 218 | removeWindowInitialProps?: boolean 219 | /** 220 | * disable serve-side render in umi dev mode. 221 | */ 222 | devServerRender?: boolean 223 | mode?: ("stream" | "string") 224 | /** 225 | * static markup in static site 226 | */ 227 | staticMarkup?: boolean 228 | } 229 | singular?: boolean 230 | styleLoader?: { 231 | 232 | } 233 | targets?: { 234 | 235 | } 236 | terserOptions?: { 237 | 238 | } 239 | theme?: { 240 | 241 | } 242 | runtimeHistory?: { 243 | 244 | } 245 | webpack5?: { 246 | lazyCompilation?: { 247 | entries?: boolean 248 | imports?: boolean 249 | test?: any 250 | } 251 | } 252 | workerLoader?: { 253 | 254 | } 255 | favicon?: string 256 | headScripts?: any[] 257 | links?: any[] 258 | metas?: any[] 259 | scripts?: any[] 260 | styles?: any[] 261 | title?: string 262 | mock?: { 263 | exclude?: string[] 264 | } 265 | antd?: { 266 | dark?: boolean 267 | compact?: boolean 268 | config?: { 269 | 270 | } 271 | } 272 | dva?: { 273 | disableModelsReExport?: boolean 274 | /** 275 | * lazy load dva model avoiding the import modules from umi undefined 276 | */ 277 | lazyLoad?: boolean 278 | extraModels?: string[] 279 | hmr?: boolean 280 | immer?: (boolean | { 281 | 282 | }) 283 | skipModelValidate?: boolean 284 | } 285 | locale?: { 286 | default?: string 287 | useLocalStorage?: boolean 288 | baseNavigator?: boolean 289 | title?: boolean 290 | antd?: boolean 291 | baseSeparator?: string 292 | } 293 | layout?: { 294 | 295 | } 296 | request?: { 297 | dataField?: "" 298 | } 299 | [k: string]: any 300 | } 301 | -------------------------------------------------------------------------------- /src/renderer/.umi-production/plugin-request/request.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | /** 3 | * Base on https://github.com/umijs//Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/umi-request 4 | */ 5 | import { 6 | extend, 7 | Context, 8 | RequestOptionsInit, 9 | OnionMiddleware, 10 | RequestOptionsWithoutResponse, 11 | RequestMethod, 12 | RequestOptionsWithResponse, 13 | RequestResponse, 14 | RequestInterceptor, 15 | ResponseInterceptor, 16 | } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/umi-request'; 17 | // @ts-ignore 18 | 19 | import { ApplyPluginsType } from 'umi'; 20 | import { history, plugin } from '../core/umiExports'; 21 | 22 | // decoupling with antd UI library, you can using `alias` modify the ui methods 23 | // @ts-ignore 24 | import { message, notification } from '@umijs/plugin-request/lib/ui'; 25 | import useUmiRequest, { UseRequestProvider } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/@ahooksjs/use-request'; 26 | import { 27 | BaseOptions, 28 | BasePaginatedOptions, 29 | BaseResult, 30 | CombineService, 31 | LoadMoreFormatReturn, 32 | LoadMoreOptions, 33 | LoadMoreOptionsWithFormat, 34 | LoadMoreParams, 35 | LoadMoreResult, 36 | OptionsWithFormat, 37 | PaginatedFormatReturn, 38 | PaginatedOptionsWithFormat, 39 | PaginatedParams, 40 | PaginatedResult, 41 | } from '/Users/xiaobei/Documents/private/toolbox/umi3.0-electron/node_modules/@ahooksjs/use-request/lib/types'; 42 | 43 | type ResultWithData = { data?: T; [key: string]: any }; 44 | 45 | function useRequest< 46 | R = any, 47 | P extends any[] = any, 48 | U = any, 49 | UU extends U = any, 50 | >( 51 | service: CombineService, 52 | options: OptionsWithFormat, 53 | ): BaseResult; 54 | function useRequest( 55 | service: CombineService, 56 | options?: BaseOptions, 57 | ): BaseResult; 58 | function useRequest( 59 | service: CombineService>, 60 | options: LoadMoreOptionsWithFormat, 61 | ): LoadMoreResult; 62 | function useRequest< 63 | R extends ResultWithData = any, 64 | RR extends R = any, 65 | >( 66 | service: CombineService>, 67 | options: LoadMoreOptions, 68 | ): LoadMoreResult; 69 | 70 | function useRequest( 71 | service: CombineService, 72 | options: PaginatedOptionsWithFormat, 73 | ): PaginatedResult; 74 | function useRequest( 75 | service: CombineService< 76 | ResultWithData>, 77 | PaginatedParams 78 | >, 79 | options: BasePaginatedOptions, 80 | ): PaginatedResult; 81 | function useRequest(service: any, options: any = {}) { 82 | return useUmiRequest(service, { 83 | formatResult: result => result?.data, 84 | requestMethod: (requestOptions: any) => { 85 | if (typeof requestOptions === 'string') { 86 | return request(requestOptions); 87 | } 88 | if (typeof requestOptions === 'object') { 89 | const { url, ...rest } = requestOptions; 90 | return request(url, rest); 91 | } 92 | throw new Error('request options error'); 93 | }, 94 | ...options, 95 | }); 96 | } 97 | 98 | export interface RequestConfig extends RequestOptionsInit { 99 | errorConfig?: { 100 | errorPage?: string; 101 | adaptor?: (resData: any, ctx: Context) => ErrorInfoStructure; 102 | }; 103 | middlewares?: OnionMiddleware[]; 104 | requestInterceptors?: RequestInterceptor[]; 105 | responseInterceptors?: ResponseInterceptor[]; 106 | } 107 | 108 | export enum ErrorShowType { 109 | SILENT = 0, 110 | WARN_MESSAGE = 1, 111 | ERROR_MESSAGE = 2, 112 | NOTIFICATION = 4, 113 | REDIRECT = 9, 114 | } 115 | 116 | interface ErrorInfoStructure { 117 | success: boolean; 118 | data?: any; 119 | errorCode?: string; 120 | errorMessage?: string; 121 | showType?: ErrorShowType; 122 | traceId?: string; 123 | host?: string; 124 | [key: string]: any; 125 | } 126 | 127 | interface RequestError extends Error { 128 | data?: any; 129 | info?: ErrorInfoStructure; 130 | request?: Context['req']; 131 | response?: Context['res']; 132 | } 133 | 134 | const DEFAULT_ERROR_PAGE = '/exception'; 135 | 136 | let requestMethodInstance: RequestMethod; 137 | const getRequestMethod = () => { 138 | if (requestMethodInstance) { 139 | // request method 已经示例化 140 | return requestMethodInstance; 141 | } 142 | 143 | // runtime 配置可能应为依赖顺序的问题在模块初始化的时候无法获取,所以需要封装一层在异步调用后初始化相关方法 144 | // 当用户的 app.ts 中依赖了该文件的情况下就该模块的初始化时间就会被提前,无法获取到运行时配置 145 | const requestConfig: RequestConfig = plugin.applyPlugins({ 146 | key: 'request', 147 | type: ApplyPluginsType.modify, 148 | initialValue: {}, 149 | }); 150 | 151 | const errorAdaptor = 152 | requestConfig.errorConfig?.adaptor || ((resData) => resData); 153 | 154 | requestMethodInstance = extend({ 155 | errorHandler: (error: RequestError) => { 156 | // @ts-ignore 157 | if (error?.request?.options?.skipErrorHandler) { 158 | throw error; 159 | } 160 | let errorInfo: ErrorInfoStructure | undefined; 161 | if (error.name === 'ResponseError' && error.data && error.request) { 162 | const ctx: Context = { 163 | req: error.request, 164 | res: error.response, 165 | }; 166 | errorInfo = errorAdaptor(error.data, ctx); 167 | error.message = errorInfo?.errorMessage || error.message; 168 | error.data = error.data; 169 | error.info = errorInfo; 170 | } 171 | errorInfo = error.info; 172 | 173 | if (errorInfo) { 174 | const errorMessage = errorInfo?.errorMessage; 175 | const errorCode = errorInfo?.errorCode; 176 | const errorPage = 177 | requestConfig.errorConfig?.errorPage || DEFAULT_ERROR_PAGE; 178 | 179 | switch (errorInfo?.showType) { 180 | case ErrorShowType.SILENT: 181 | // do nothing 182 | break; 183 | case ErrorShowType.WARN_MESSAGE: 184 | message.warn(errorMessage); 185 | break; 186 | case ErrorShowType.ERROR_MESSAGE: 187 | message.error(errorMessage); 188 | break; 189 | case ErrorShowType.NOTIFICATION: 190 | notification.open({ 191 | description: errorMessage, 192 | message: errorCode, 193 | }); 194 | break; 195 | case ErrorShowType.REDIRECT: 196 | // @ts-ignore 197 | history.push({ 198 | pathname: errorPage, 199 | query: { errorCode, errorMessage }, 200 | }); 201 | // redirect to error page 202 | break; 203 | default: 204 | message.error(errorMessage); 205 | break; 206 | } 207 | } else { 208 | message.error(error.message || 'Request error, please retry.'); 209 | } 210 | throw error; 211 | }, 212 | ...requestConfig, 213 | }); 214 | 215 | // 中间件统一错误处理 216 | // 后端返回格式 { success: boolean, data: any } 217 | // 按照项目具体情况修改该部分逻辑 218 | requestMethodInstance.use(async (ctx, next) => { 219 | await next(); 220 | const { req, res } = ctx; 221 | // @ts-ignore 222 | if (req.options?.skipErrorHandler) { 223 | return; 224 | } 225 | const { options } = req; 226 | const { getResponse } = options; 227 | const resData = getResponse ? res.data : res; 228 | const errorInfo = errorAdaptor(resData, ctx); 229 | if (errorInfo.success === false) { 230 | // 抛出错误到 errorHandler 中处理 231 | const error: RequestError = new Error(errorInfo.errorMessage); 232 | error.name = 'BizError'; 233 | error.data = resData; 234 | error.info = errorInfo; 235 | throw error; 236 | } 237 | }); 238 | 239 | // Add user custom middlewares 240 | const customMiddlewares = requestConfig.middlewares || []; 241 | customMiddlewares.forEach((mw) => { 242 | requestMethodInstance.use(mw); 243 | }); 244 | 245 | // Add user custom interceptors 246 | const requestInterceptors = requestConfig.requestInterceptors || []; 247 | const responseInterceptors = requestConfig.responseInterceptors || []; 248 | requestInterceptors.map((ri) => { 249 | requestMethodInstance.interceptors.request.use(ri); 250 | }); 251 | responseInterceptors.map((ri) => { 252 | requestMethodInstance.interceptors.response.use(ri); 253 | }); 254 | 255 | return requestMethodInstance; 256 | }; 257 | 258 | interface RequestMethodInUmi { 259 | ( 260 | url: string, 261 | options: RequestOptionsWithResponse & { skipErrorHandler?: boolean }, 262 | ): Promise>; 263 | ( 264 | url: string, 265 | options: RequestOptionsWithoutResponse & { skipErrorHandler?: boolean }, 266 | ): Promise; 267 | ( 268 | url: string, 269 | options?: RequestOptionsInit & { skipErrorHandler?: boolean }, 270 | ): R extends true ? Promise> : Promise; 271 | } 272 | const request: RequestMethodInUmi = (url: any, options: any) => { 273 | const requestMethod = getRequestMethod(); 274 | return requestMethod(url, options); 275 | }; 276 | 277 | export { request, useRequest, UseRequestProvider }; 278 | --------------------------------------------------------------------------------