-
Notifications
You must be signed in to change notification settings - Fork 237
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
518 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default function() { | ||
return { | ||
description: 'bar', | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
|
||
.normal { | ||
} | ||
|
||
.title { | ||
background: rgb(121, 207, 242); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import React from 'react'; | ||
import { useModel } from 'umi'; | ||
import styles from './plugin-model.css'; | ||
|
||
export default () => { | ||
const bar = useModel('bar'); | ||
return ( | ||
<div> | ||
<h1 className={styles.title}>Page plugin-model {bar.description}</h1> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export const DIR_NAME_IN_TMP = 'plugin-model'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import React from 'react'; | ||
|
||
export const UmiContext = React.createContext({}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
export default class Dispatcher { | ||
callbacks = {}; | ||
|
||
data = {}; | ||
|
||
update = (namespace: string) => { | ||
(this.callbacks[namespace] || []).forEach( | ||
(callback: (val: any) => void) => { | ||
try { | ||
const data = this.data[namespace]; | ||
callback(data); | ||
} catch (e) { | ||
callback(undefined); | ||
} | ||
}, | ||
); | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import React from 'react'; | ||
|
||
interface ExecutorProps { | ||
hook: () => any; | ||
onUpdate: (val: any) => void; | ||
namespace: string; | ||
} | ||
|
||
export default (props: ExecutorProps) => { | ||
const { hook, onUpdate, namespace } = props; | ||
try { | ||
const data = hook(); | ||
onUpdate(data); | ||
} catch (e) { | ||
console.error( | ||
`plugin-model: Invoking '${namespace || 'unknown'}' model failed:`, | ||
e, | ||
); | ||
} | ||
|
||
return <></>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { join } from 'path'; | ||
import { IApi } from 'umi'; | ||
import { DIR_NAME_IN_TMP } from './constants'; | ||
import getProviderContent from './utils/getProviderContent'; | ||
import getUseModelContent from './utils/getUseModelContent'; | ||
|
||
export default (api: IApi) => { | ||
const { | ||
paths, | ||
utils: { winPath }, | ||
} = api; | ||
|
||
function getModelsPath() { | ||
return join(paths.absSrcPath!, api.config.singular ? 'model' : 'models'); | ||
} | ||
|
||
// Add provider wrapper with rootContainer | ||
api.addRuntimePlugin(() => join(winPath(__dirname), './runtime')); | ||
|
||
api.onGenerateFiles(async () => { | ||
const modelsPath = getModelsPath(); | ||
try { | ||
const additionalModels = await api.applyPlugins({ | ||
key: 'addExtraModels', | ||
type: api.ApplyPluginsType.add, | ||
initialValue: [], | ||
}); | ||
// Write models/provider.tsx | ||
api.writeTmpFile({ | ||
path: `${DIR_NAME_IN_TMP}/Provider.tsx`, | ||
content: getProviderContent(modelsPath, additionalModels), | ||
}); | ||
// Write models/useModel.tsx | ||
api.writeTmpFile({ | ||
content: getUseModelContent(), | ||
path: `${DIR_NAME_IN_TMP}/useModel.tsx`, | ||
}); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
}); | ||
|
||
api.addTmpGenerateWatcherPaths(() => { | ||
const modelsPath = getModelsPath(); | ||
return [modelsPath]; | ||
}); | ||
|
||
// Export useModel and Models from umi | ||
api.addUmiExports(() => [ | ||
{ | ||
exportAll: true, | ||
source: winPath(join(paths.absTmpPath!, DIR_NAME_IN_TMP, 'useModel')), | ||
}, | ||
]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/* eslint-disable import/no-dynamic-require */ | ||
import React from 'react'; | ||
import { DIR_NAME_IN_TMP } from './constants'; | ||
|
||
export function rootContainer(container: React.ReactNode) { | ||
return React.createElement( | ||
// eslint-disable-next-line global-require | ||
require(`@@/${DIR_NAME_IN_TMP}/Provider`).default, | ||
null, | ||
container, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
import { join } from 'path'; | ||
import { EOL } from 'os'; | ||
import { utils } from 'umi'; | ||
|
||
import { | ||
genImports, | ||
genModels, | ||
genExtraModels, | ||
ModelItem, | ||
getValidFiles, | ||
} from './index'; | ||
|
||
const { winPath } = utils; | ||
|
||
function getFiles(cwd: string) { | ||
return utils.glob | ||
.sync('./**/*.{ts,tsx,js,jsx}', { | ||
cwd, | ||
}) | ||
.filter( | ||
(file: string) => | ||
!file.endsWith('.d.ts') && | ||
!file.endsWith('.test.js') && | ||
!file.endsWith('.test.jsx') && | ||
!file.endsWith('.test.ts') && | ||
!file.endsWith('.test.tsx'), | ||
); | ||
} | ||
|
||
function getModels(files: string[]) { | ||
const sortedModels = genModels(files); | ||
return sortedModels | ||
.map(ele => `'${ele.namespace.replace(/'/g, "\\'")}': ${ele.importName}`) | ||
.join(', '); | ||
} | ||
|
||
function getExtraModels(models: ModelItem[] = []) { | ||
const extraModels = genExtraModels(models); | ||
return extraModels | ||
.map(ele => `'${ele.namespace}': ${winPath(ele.importName)}`) | ||
.join(', '); | ||
} | ||
|
||
function getExtraImports(models: ModelItem[] = []) { | ||
const extraModels = genExtraModels(models); | ||
return extraModels | ||
.map( | ||
ele => | ||
`import ${ele.importName} from '${winPath( | ||
ele.importPath.replace(/'/g, "\\'"), | ||
)}';`, | ||
) | ||
.join(EOL); | ||
} | ||
|
||
export default function(modelsDir: string, extra: ModelItem[] = []) { | ||
const files = getValidFiles(getFiles(modelsDir), modelsDir); | ||
const imports = genImports(files); | ||
const userModels = getModels(files); | ||
const extraModels = getExtraModels(extra); | ||
const extraImports = getExtraImports(extra); | ||
return `import React from 'react'; | ||
${extraImports} | ||
${imports} | ||
// @ts-ignore | ||
import Dispatcher from '${winPath( | ||
join(__dirname, '..', 'helpers', 'dispatcher'), | ||
)}'; | ||
// @ts-ignore | ||
import Executor from '${winPath(join(__dirname, '..', 'helpers', 'executor'))}'; | ||
// @ts-ignore | ||
import { UmiContext } from '${winPath( | ||
join(__dirname, '..', 'helpers', 'constant'), | ||
)}'; | ||
export const models = { ${extraModels ? `${extraModels}, ` : ''}${userModels} }; | ||
export type Model<T extends keyof typeof models> = { | ||
[key in keyof typeof models]: ReturnType<typeof models[T]>; | ||
}; | ||
export type Models<T extends keyof typeof models> = Model<T>[T] | ||
const dispatcher = new Dispatcher!(); | ||
const Exe = Executor!; | ||
export default ({ children }: { children: React.ReactNode }) => { | ||
return ( | ||
<UmiContext.Provider value={dispatcher}> | ||
{ | ||
Object.entries(models).map(pair => ( | ||
<Exe key={pair[0]} namespace={pair[0]} hook={pair[1] as any} onUpdate={(val: any) => { | ||
const [ns] = pair as [keyof typeof models, any]; | ||
dispatcher.data[ns] = val; | ||
dispatcher.update(ns); | ||
}} /> | ||
)) | ||
} | ||
{children} | ||
</UmiContext.Provider> | ||
) | ||
} | ||
`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { join } from 'path'; | ||
import { utils } from 'umi'; | ||
|
||
const { winPath } = utils; | ||
|
||
export default function() { | ||
return `import { useState, useEffect, useContext, useRef } from 'react'; | ||
// @ts-ignore | ||
import isEqual from '${winPath(require.resolve('lodash.isequal'))}'; | ||
// @ts-ignore | ||
import { UmiContext } from '${winPath( | ||
join(__dirname, '..', 'helpers', 'constant'), | ||
)}'; | ||
import { Model, models } from './Provider'; | ||
export type Models<T extends keyof typeof models> = Model<T>[T] | ||
export function useModel<T extends keyof Model<T>>(model: T): Model<T>[T] | ||
export function useModel<T extends keyof Model<T>, U>(model: T, selector: (model: Model<T>[T]) => U): U | ||
export function useModel<T extends keyof Model<T>, U>( | ||
namespace: T, | ||
updater?: (model: Model<T>[T]) => U | ||
) : typeof updater extends undefined ? Model<T>[T] : ReturnType<NonNullable<typeof updater>>{ | ||
type RetState = typeof updater extends undefined ? Model<T>[T] : ReturnType<NonNullable<typeof updater>> | ||
const dispatcher = useContext<any>(UmiContext); | ||
const updaterRef = useRef(updater); | ||
updaterRef.current = updater; | ||
const [state, setState] = useState<RetState>( | ||
() => updaterRef.current ? updaterRef.current(dispatcher.data![namespace]) : dispatcher.data![namespace] | ||
); | ||
const lastState = useRef<any>(state); | ||
useEffect(() => { | ||
const handler = (e: any) => { | ||
if(updater && updaterRef.current){ | ||
const ret = updaterRef.current(e); | ||
if(!isEqual(ret, lastState.current)){ | ||
lastState.current = ret; | ||
setState(ret); | ||
} | ||
} else { | ||
setState(e); | ||
} | ||
} | ||
try { | ||
dispatcher.callbacks![namespace]!.add(handler); | ||
} catch (e) { | ||
dispatcher.callbacks![namespace] = new Set(); | ||
dispatcher.callbacks![namespace]!.add(handler); | ||
} | ||
return () => { | ||
dispatcher.callbacks![namespace]!.delete(handler); | ||
} | ||
}, [namespace]); | ||
return state; | ||
}; | ||
`; | ||
} |
Oops, something went wrong.