├── example
├── package.json
├── .umirc.js
├── models
│ └── counter.ts
├── pages
│ └── index.tsx
├── app.ts
└── tsconfig.json
├── .fatherrc.ts
├── .prettierrc
├── .gitignore
├── src
├── utils
│ ├── getExportContent.tsx
│ ├── getProviderContent.tsx
│ └── getModelContent.tsx
├── runtime.ts
├── constants.ts
├── index.test.js
└── index.ts
├── jest.config.js
├── tsconfig.json
├── .github
└── workflows
│ └── ci.yml
├── package.json
├── circle.yml
├── README_CN.md
└── README.md
/example/package.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.fatherrc.ts:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | cjs: 'babel',
4 | disableTypeCheck: true,
5 | };
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all",
4 | "printWidth": 90
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /yarn.lock
3 | /package-lock.json
4 | /example/dist
5 | /test/fixtures/*/.umi
6 | /lib
7 | /coverage
8 | .umi
9 | .umi-production
10 | node_modules
11 |
--------------------------------------------------------------------------------
/example/.umirc.js:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 |
3 | export default {
4 | plugins: [
5 | [join(__dirname, '..', require('../package').main || 'index.js')],
6 | ['@umijs/plugin-model'],
7 | ],
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/getExportContent.tsx:
--------------------------------------------------------------------------------
1 | export default (
2 | modelPath: string,
3 | ) => `import { InitialState as InitialStateType } from '../${modelPath}';
4 |
5 | export type InitialState = InitialStateType;
6 | `;
7 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | collectCoverageFrom: [
4 | `src/**/*.{js,jsx,ts,tsx}`,
5 | '!**/fixtures/**',
6 | ],
7 | coveragePathIgnorePatterns: [
8 | '/test',
9 | ],
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { DIR_NAME } from './constants';
3 |
4 | export function rootContainer(container: React.ReactNode) {
5 | return React.createElement(
6 | require(`@tmp/${DIR_NAME}/Provider`).default,
7 | null,
8 | container,
9 | );
10 | }
--------------------------------------------------------------------------------
/example/models/counter.ts:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 |
3 | export default () => {
4 | const [ counter, setCounter ] = useState(0);
5 | const increment = () => setCounter(c => c + 1);
6 | const decrement = () => setCounter(c => c - 1);
7 | return { counter, increment, decrement };
8 | }
9 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 |
3 | export const DIR_NAME = 'plugin-init';
4 | export const MODEL_NAME = 'initialState';
5 | export const RELATIVE_MODEL = join(DIR_NAME, 'models', MODEL_NAME);
6 | export const RELATIVE_MODEL_PATH = `${RELATIVE_MODEL}.ts`;
7 | export const RELATIVE_EXPORT = join(DIR_NAME, 'exports');
8 | export const RELATIVE_EXPORT_PATH = `${RELATIVE_EXPORT}.ts`;
9 |
--------------------------------------------------------------------------------
/example/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useModel } from 'umi';
3 |
4 | export default () => {
5 | const { initialState = {}, loading, refresh } = useModel('@@initialState');
6 | return (<>
7 |
8 | {
9 | loading ? 'loading' : <>name: {initialState.name}>
10 | }
11 |
12 |
13 | >);
14 | }
15 |
--------------------------------------------------------------------------------
/example/app.ts:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const getInitialState = async() => {
4 | const mockService = () => new Promise<{ email: string, name: string }>(res => setTimeout(()=>{
5 | res({
6 | name: 'troy',
7 | email: 'troy.lty@alipay.com',
8 | })
9 | }, 2000));
10 | const userInfo = await mockService();
11 |
12 | return {
13 | avatar: `avatarUrl`,
14 | gender: 'male',
15 | ...userInfo,
16 | };
17 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build/dist",
4 | "module": "esnext",
5 | "target": "esnext",
6 | "lib": ["esnext", "dom"],
7 | "sourceMap": true,
8 | "baseUrl": ".",
9 | "jsx": "react",
10 | "allowSyntheticDefaultImports": true,
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "suppressImplicitAnyIndexErrors": true,
15 | "noUnusedLocals": true,
16 | "allowJs": true,
17 | "experimentalDecorators": true,
18 | "strict": true
19 | },
20 | "exclude": [
21 | "node_modules",
22 | "build",
23 | "dist",
24 | "script"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Node CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ${{ matrix.os }}
8 | strategy:
9 | matrix:
10 | node_version: [8.x, 10.x, 12.x]
11 | os: [ubuntu-latest]
12 | steps:
13 | - uses: actions/checkout@v1
14 | - name: Use Node.js ${{ matrix.node_version }}
15 | uses: actions/setup-node@v1
16 | with:
17 | node-version: ${{ matrix.node_version }}
18 | - run: yarn
19 | - run: yarn build
20 | - run: yarn test --forceExit
21 | env:
22 | CI: true
23 | HEADLESS: false
24 | PROGRESS: none
25 | NODE_ENV: test
26 | NODE_OPTIONS: --max_old_space_size=4096
27 |
28 |
--------------------------------------------------------------------------------
/src/utils/getProviderContent.tsx:
--------------------------------------------------------------------------------
1 | export default `
2 | import React, { useRef, useEffect } from 'react';
3 | import { useModel } from 'umi';
4 | if (typeof useModel !== 'function') {
5 | throw new Error('[plugin-initial-state]: useModel is not a function, @umijs/plugin-model is required.')
6 | }
7 |
8 | interface Props {
9 | children: React.ReactNode;
10 | }
11 | export default (props: Props) => {
12 | const { children } = props;
13 | const appLoaded = useRef(false);
14 | const { loading = false } = useModel('@@initialState') || {};
15 | useEffect(()=>{
16 | if(!loading){
17 | appLoaded.current = true
18 | }
19 | }, [loading])
20 | // initial state loading 时,阻塞渲染
21 | if (loading && !appLoaded.current) {
22 | return null;
23 | }
24 | return children;
25 | };
26 | `
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build/dist",
4 | "module": "esnext",
5 | "target": "esnext",
6 | "lib": ["esnext", "dom"],
7 | "sourceMap": true,
8 | "baseUrl": ".",
9 | "jsx": "react",
10 | "allowSyntheticDefaultImports": true,
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "suppressImplicitAnyIndexErrors": true,
15 | "noUnusedLocals": true,
16 | "allowJs": true,
17 | "experimentalDecorators": true,
18 | "strict": true,
19 | "paths": {
20 | "@/*": ["./*"],
21 | "@tmp/*": ["./pages/.umi/*"]
22 | }
23 | },
24 | "exclude": [
25 | "node_modules",
26 | "build",
27 | "dist",
28 | "script"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/src/index.test.js:
--------------------------------------------------------------------------------
1 | import { join } from 'path';
2 | import pluginFunc from './index';
3 |
4 | const examplePath = join(__dirname, '../example');
5 |
6 | describe('plugin-request', () => {
7 | const getMockAPI = (writeTmpFile = () => {}) => {
8 | return {
9 | addRuntimePluginKey() {},
10 | onGenerateFiles(handler) {
11 | handler();
12 | },
13 | addRuntimePlugin() {},
14 | register() {},
15 | paths: {
16 | absTmpDirPath: '/test/page/.umi',
17 | absSrcPath: examplePath,
18 | cwd: examplePath,
19 | },
20 | findJS: p => `${p}.ts`,
21 | winPath() {
22 | return '/winpathtest';
23 | },
24 | addUmiExports() {},
25 | writeTmpFile,
26 | };
27 | };
28 |
29 | test('normal', () => {
30 | const writeTmpFile = jest.fn();
31 | pluginFunc(getMockAPI(writeTmpFile));
32 |
33 | expect(writeTmpFile).toHaveBeenCalledTimes(3);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@umijs/plugin-initial-state",
3 | "version": "1.0.1",
4 | "description": "Umi plugin to store initial state globally.",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/umijs/plugin-initial-state"
9 | },
10 | "homepage": "https://github.com/umijs/plugin-initial-state",
11 | "authors": [
12 | "Troy Li "
13 | ],
14 | "bugs": {
15 | "url": "https://github.com/umijs/plugin-initial-state/issues"
16 | },
17 | "peerDependencies": {
18 | "@umijs/plugin-model": "^0.1.0",
19 | "react": "^16.0.0",
20 | "umi": "2.x"
21 | },
22 | "main": "lib/index.js",
23 | "scripts": {
24 | "build": "father-build",
25 | "test": "umi-test --coverage",
26 | "debug": "umi-test",
27 | "prepublishOnly": "npm run build && np --no-cleanup --yolo --no-publish"
28 | },
29 | "devDependencies": {
30 | "@testing-library/jest-dom": "^4.2.4",
31 | "@testing-library/react": "^9.3.2",
32 | "@types/jest": "^24.0.23",
33 | "@types/node": "^12.12.11",
34 | "father-build": "^1.15.0",
35 | "np": "^5.2.1",
36 | "umi-test": "^1.3.3",
37 | "umi-types": "^0.5.7"
38 | },
39 | "files": [
40 | "lib",
41 | "src"
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2.0
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | - image: circleci/node:latest-browsers
11 | environment:
12 | CI: true
13 | NODE_ENV: test
14 | NODE_OPTIONS: --max_old_space_size=4096
15 | NPM_CONFIG_LOGLEVEL: error
16 | JOBS: max # https://gist.github.com/ralphtheninja/f7c45bdee00784b41fed
17 | branches:
18 | ignore:
19 | - gh-pages # list of branches to ignore
20 | - /release\/.*/ # or ignore regexes
21 | working_directory: ~/father
22 |
23 | steps:
24 | - checkout
25 | - restore_cache:
26 | key: node-modules-{{ checksum "package.json" }}
27 | - run: sudo npm install -g cnpm
28 | - run: cnpm install --registry=https://registry.npmjs.org
29 | - run: cnpm run build
30 | - run:
31 | command: npm run test -- --forceExit --detectOpenHandles --runInBand --maxWorkers=2
32 | no_output_timeout: 300m
33 | - run: bash <(curl -s https://codecov.io/bash)
34 | - save_cache:
35 | key: node-modules-{{ checksum "package.json" }}
36 | paths:
37 | - ./node_modules
38 |
--------------------------------------------------------------------------------
/src/utils/getModelContent.tsx:
--------------------------------------------------------------------------------
1 | export default (relEntryFile: string) => `import { useState, useEffect } from 'react';
2 | import { Models } from 'umi';
3 | import * as app from '@/app';
4 |
5 | export type InitialState = Models<'@@initialState'>;
6 | async function getInitialState() {
7 | if (!app.getInitialState) {
8 | throw new Error('getInitialState is not defined in ${relEntryFile}');
9 | }
10 | return await app.getInitialState();
11 | }
12 |
13 | type ThenArg = T extends Promise ? U : T
14 |
15 | const initState = {
16 | initialState: undefined as ThenArg> | undefined,
17 | loading: true,
18 | error: undefined as Error | undefined,
19 | }
20 |
21 | export default () => {
22 | const [ state, setState ] = useState(initState);
23 |
24 | const refresh = async() => {
25 | setState(s => ({ ...s, loading: true, initialState: undefined, error: undefined }))
26 | try {
27 | const asyncFunc = () => new Promise>(res => res(getInitialState()));
28 | const ret = await asyncFunc();
29 | setState(s => ({ ...s, initialState: ret, loading: false }));
30 | } catch(e) {
31 | setState(s => ({ ...s, error: e, loading: false }));
32 | }
33 | };
34 |
35 | useEffect(()=>{
36 | refresh();
37 | }, []);
38 |
39 | return {
40 | ...state,
41 | refresh,
42 | }
43 | }
44 | `
45 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { IApi } from 'umi-types';
2 | import { join, relative } from 'path';
3 | import providerContent from './utils/getProviderContent';
4 | import getModelContent from './utils/getModelContent';
5 | import getExportContent from './utils/getExportContent';
6 | import {
7 | DIR_NAME,
8 | RELATIVE_MODEL,
9 | RELATIVE_MODEL_PATH,
10 | RELATIVE_EXPORT,
11 | RELATIVE_EXPORT_PATH,
12 | } from './constants';
13 |
14 | export default (api: IApi) => {
15 | const { paths, findJS } = api;
16 | // 注册 getInitialState 方法
17 | api.addRuntimePluginKey('getInitialState');
18 |
19 | api.writeTmpFile(join(DIR_NAME, 'Provider.tsx'), providerContent);
20 |
21 | // Add provider to prevent render
22 | api.addRuntimePlugin(join(__dirname, './runtime'));
23 |
24 | api.onGenerateFiles(() => {
25 | const entryFile = findJS(join(paths.absSrcPath, 'app'));
26 | if (entryFile) {
27 | const relEntryFile = relative(paths.cwd, entryFile);
28 | api.writeTmpFile(RELATIVE_MODEL_PATH, getModelContent(relEntryFile));
29 | api.writeTmpFile(RELATIVE_EXPORT_PATH, getExportContent(RELATIVE_MODEL));
30 | } else {
31 | api.log.warn(
32 | '[@umijs/plugin-initial-state]: 检测到 @umijs/plugin-initial-state 插件已经开启,但是不存在 app.ts/js 入口文件。',
33 | );
34 | }
35 | });
36 |
37 | api.addUmiExports([
38 | {
39 | exportAll: true,
40 | source: api.winPath(join(api.paths.absTmpDirPath, RELATIVE_EXPORT)),
41 | },
42 | ]);
43 |
44 | api.register('addExtraModels', () => [
45 | {
46 | absPath: join(paths.absTmpDirPath, RELATIVE_MODEL_PATH),
47 | namespace: '@@initialState',
48 | },
49 | ]);
50 | };
51 |
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | [English](./README.md) | 中文文档
2 |
3 | # @umijs/plugin-initial-state
4 |
5 | [](https://codecov.io/gh/umijs/plugin-initial-state)
6 | [](https://npmjs.org/package/@umijs/plugin-initial-state)
7 | [](https://circleci.com/gh/umijs/plugin-initial-state/tree/master)
8 | [](https://github.com/umijs/plugin-initial-state)
9 | [](https://npmjs.org/package/@umijs/plugin-initial-state)
10 |
11 | 在全局中注册初始化信息的 umi plugin.
12 |
13 | ## 安装
14 |
15 | ```bash
16 | # or yarn
17 | $ npm install @umijs/plugin-initial-state --save
18 | ```
19 |
20 | ## 用法
21 |
22 | 3 步开始使用 @umijs/plugin-initial-state
23 |
24 | ### 1. 在 `.umirc.js` 中配置
25 |
26 | ```js
27 | export default {
28 | plugins: [['@umijs/plugin-initial-state', options]],
29 | };
30 | ```
31 |
32 | ### 2. 在 `src/app.ts` 中配置 getInitialState 方法
33 |
34 | ```js
35 | export async function getInitialState() {
36 | return 'Hello World';
37 | }
38 | ```
39 |
40 | ### 3. 在 React 组件或其他 Model 中使用 initialState
41 |
42 | ```js
43 | import React from 'react';
44 | import { useModel } from 'umi';
45 |
46 | export default () => {
47 | const { initialState, loading, refresh } = useModel('@@initialState');
48 | return <>{ loading ? 'loading...' : initialState }>;
49 | };
50 | ```
51 |
52 | 完整例子可参考 [./example](https://github.com/umijs/plugin-initial-state/tree/master/example).
53 |
54 | ## LICENSE
55 |
56 | MIT
57 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | English | [中文文档](./README_CN.md)
2 |
3 | # @umijs/plugin-initial-state
4 |
5 | [](https://codecov.io/gh/umijs/plugin-initial-state)
6 | [](https://npmjs.org/package/@umijs/plugin-initial-state)
7 | [](https://circleci.com/gh/umijs/plugin-initial-state/tree/master)
8 | [](https://github.com/umijs/plugin-initial-state)
9 | [](https://npmjs.org/package/@umijs/plugin-initial-state)
10 |
11 | Umi plugin to store initial state globally.
12 |
13 | ## Install
14 |
15 | ```bash
16 | # or yarn
17 | $ npm install @umijs/plugin-initial-state --save
18 | ```
19 |
20 | ## Usage
21 |
22 | Getting started in 3 steps.
23 |
24 | ### 1. Configure in `.umirc.js`
25 |
26 | ```js
27 | export default {
28 | plugins: [['@umijs/plugin-initial-state', options]],
29 | };
30 | ```
31 |
32 | ### 2. Add getInitialState into `src/app.ts`
33 |
34 | ```js
35 | export async function getInitialState() {
36 | return 'Hello World';
37 | }
38 | ```
39 |
40 | ### 3. Use it in your React Component or other models
41 |
42 | ```js
43 | import React from 'react';
44 | import { useModel } from 'umi';
45 |
46 | export default () => {
47 | const { initialState, loading, refresh } = useModel('@@initialState');
48 | return <>{ loading ? 'loading...' : initialState }>;
49 | };
50 | ```
51 |
52 | Full example can find in [./example](https://github.com/umijs/plugin-initial-state/tree/master/example).
53 |
54 | ## LICENSE
55 |
56 | MIT
57 |
--------------------------------------------------------------------------------