├── example ├── pages │ ├── index.css │ └── index.js ├── .umirc.js ├── package.json ├── tsconfig.json └── yarn.lock ├── .fatherrc.js ├── src ├── index.ts.tpl ├── fixCustomizedRuntime.tsx.tpl ├── runtime.tsx.tpl └── index.js ├── .gitignore ├── .prettierrc ├── package.json ├── LICENSE ├── .github └── workflows │ └── publish.yml ├── README.md └── README_EN.md /example/pages/index.css: -------------------------------------------------------------------------------- 1 | 2 | .normal { 3 | background: #79F2AA; 4 | } 5 | -------------------------------------------------------------------------------- /.fatherrc.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | target: 'node', 4 | cjs: 'babel', 5 | }; 6 | -------------------------------------------------------------------------------- /example/.umirc.js: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | 3 | export default { 4 | plugins: [ 5 | 'umi-plugin-keep-alive' 6 | ], 7 | } 8 | -------------------------------------------------------------------------------- /src/index.ts.tpl: -------------------------------------------------------------------------------- 1 | export { KeepAlive, useActivate, useUnactivate, withActivation, withAliveScope, useAliveController, autoFixContext } from 'react-activation'; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /yarn.lock 3 | /package-lock.json 4 | /example/dist 5 | /example/node_modules 6 | /example/pages/.umi 7 | /example/pages/.umi-production 8 | /lib 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "semi": false, 5 | "printWidth": 100, 6 | "overrides": [ 7 | { 8 | "files": ".prettierrc", 9 | "options": { "parser": "json" } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/fixCustomizedRuntime.tsx.tpl: -------------------------------------------------------------------------------- 1 | import * as customizedRuntime from '@/app'; 2 | 3 | const key = 'rootContainer'; 4 | 5 | export function rootContainer(container, ...rest) { 6 | const customizedRootContainer = customizedRuntime[key]; 7 | return typeof customizedRootContainer === 'function' ? customizedRootContainer(container, ...rest) : container; 8 | }; 9 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": ".umirc.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "umi-plugin-keep-alive": "^0.0.1-beta.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/pages/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { KeepAlive } from 'umi' 3 | 4 | function Counter() { 5 | const [count, setCount] = useState(0) 6 | 7 | return ( 8 |
9 |

count: {count}

10 | 11 |
12 | ) 13 | } 14 | 15 | export default function() { 16 | const [show, setShow] = useState(true) 17 | 18 | return ( 19 |
20 |

Page index

21 | {show && ( 22 | 23 | 24 | 25 | )} 26 | 27 |
28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/runtime.tsx.tpl: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { AliveScope, NodeKey } from 'react-activation'; 3 | 4 | // @ts-ignore 5 | NodeKey.defaultProps.onHandleNode = (node, mark) => { 6 | // 因异步组件 loaded 后会去掉 LoadableComponent 层,导致 nodeKey 变化,缓存定位错误 7 | // 故排除对 LoadableComponent 组件的标记,兼容 dynamicImport 8 | if (node.type && node.type.displayName === 'LoadableComponent') { 9 | return undefined; 10 | } 11 | 12 | return mark; 13 | }; 14 | 15 | // 兼容因使用 rootContainer 导致 access 权限无效问题 (传入 routes 带有 unaccessible 才能成功) 16 | const Wrapper = ({ children, ...props }) => ( 17 | React.createElement(AliveScope, null, React.cloneElement(children, { ...children.props, ...props })) 18 | ); 19 | 20 | export function rootContainer(container: React.ReactNode) { 21 | return React.createElement(Wrapper, null, container); 22 | } 23 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "build/dist", 6 | "sourceMap": true, 7 | "jsx": "react", 8 | "declaration": false, 9 | "module": "es2015", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "target": "es5", 15 | "typeRoots": ["node_modules/@types"], 16 | "lib": ["es2018", "dom"], 17 | "allowSyntheticDefaultImports": true, 18 | "rootDirs": ["/src", "/test", "/mock", "./typings"], 19 | "forceConsistentCasingInFileNames": true, 20 | "noImplicitReturns": true, 21 | "suppressImplicitAnyIndexErrors": true, 22 | "noUnusedLocals": true, 23 | "allowJs": true, 24 | "paths": { 25 | "@/*": ["./src/*"] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "umi-plugin-keep-alive", 3 | "version": "0.0.1-beta.35", 4 | "description": " for umi base on react-activation", 5 | "authors": [ 6 | "xiaohuoi <448627663@qq.com>", 7 | "CJY <375564567@qq.com>" 8 | ], 9 | "repository": "alitajs/umi-plugin-keep-alive", 10 | "peerDependencies": { 11 | "umi": "3.x || 4.x" 12 | }, 13 | "main": "lib/index.js", 14 | "scripts": { 15 | "build": "father-build" 16 | }, 17 | "keywords": [ 18 | "React Keep Alive", 19 | "Keep Alive", 20 | "keep-alive", 21 | "KeepAlive", 22 | "react-activation", 23 | "activation", 24 | "react cache", 25 | "umi keep alive", 26 | "umijs", 27 | "cache" 28 | ], 29 | "devDependencies": { 30 | "father-build": "^1.8.0" 31 | }, 32 | "files": [ 33 | "lib", 34 | "src" 35 | ], 36 | "license": "MIT", 37 | "dependencies": { 38 | "react-activation": "^0.12.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present xiaohuoni (448627663@qq.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/js-md5@^0.4.2": 6 | version "0.4.2" 7 | resolved "https://registry.yarnpkg.com/@types/js-md5/-/js-md5-0.4.2.tgz#95b39911e2081bf2915436e61cc345e12459e5bb" 8 | 9 | hoist-non-react-statics@^3.3.0: 10 | version "3.3.0" 11 | resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" 12 | dependencies: 13 | react-is "^16.7.0" 14 | 15 | js-md5@^0.7.3: 16 | version "0.7.3" 17 | resolved "https://registry.yarnpkg.com/js-md5/-/js-md5-0.7.3.tgz#b4f2fbb0b327455f598d6727e38ec272cd09c3f2" 18 | 19 | react-is@^16.7.0: 20 | version "16.9.0" 21 | resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.9.0.tgz#21ca9561399aad0ff1a7701c01683e8ca981edcb" 22 | 23 | react-keep-alive@^2.4.1: 24 | version "2.4.1" 25 | resolved "https://registry.yarnpkg.com/react-keep-alive/-/react-keep-alive-2.4.1.tgz#145b226f0680e6518b585dccea733b8987ec465e" 26 | dependencies: 27 | "@types/js-md5" "^0.4.2" 28 | hoist-non-react-statics "^3.3.0" 29 | js-md5 "^0.7.3" 30 | 31 | umi-plugin-keep-alive@^0.0.1-beta.2: 32 | version "0.0.1-beta.2" 33 | resolved "https://registry.yarnpkg.com/umi-plugin-keep-alive/-/umi-plugin-keep-alive-0.0.1-beta.2.tgz#c2cd47a80578f12e1fb42f5250474b2deeee11e4" 34 | dependencies: 35 | react-keep-alive "^2.4.1" 36 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | npm-publish: 10 | name: npm-publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@master 15 | 16 | - name: Check if version has been updated 17 | id: check 18 | uses: EndBug/version-check@v1 19 | with: 20 | file-url: https://unpkg.com/umi-plugin-keep-alive/package.json 21 | static-checking: localIsNew 22 | 23 | - name: Log if version has been updated 24 | if: steps.check.outputs.changed == 'true' 25 | run: 'echo "Version change found in commit ${{ steps.check.outputs.commit }}! New version: ${{ steps.check.outputs.version }} (${{ steps.check.outputs.type }})"' 26 | 27 | - name: Set up Node.js 28 | if: steps.check.outputs.changed == 'true' 29 | uses: actions/setup-node@master 30 | with: 31 | node-version: 13.11.0 32 | 33 | - name: Install Dependencies 34 | if: steps.check.outputs.changed == 'true' 35 | uses: bahmutov/npm-install@v1 36 | with: 37 | args: install 38 | useLockFile: false 39 | 40 | - name: Build 41 | if: steps.check.outputs.changed == 'true' 42 | run: 'sudo npm run build' 43 | 44 | - name: Publish 45 | if: steps.check.outputs.changed == 'true' 46 | uses: pascalgn/npm-publish-action@1.3.9 47 | with: 48 | commit_pattern: "^Release (\\S+)" 49 | env: 50 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # umi-plugin-keep-alive 2 | 3 | 中文说明 | [English](./README_EN.md) 4 | 5 | [![NPM version](https://img.shields.io/npm/v/umi-plugin-keep-alive.svg?style=flat)](https://npmjs.org/package/umi-plugin-keep-alive) 6 | [![NPM downloads](http://img.shields.io/npm/dm/umi-plugin-keep-alive.svg?style=flat)](https://npmjs.org/package/umi-plugin-keep-alive) 7 | 8 | 此 `` 功能基于 [react-activation](https://github.com/CJY0208/react-activation/blob/master/README_CN.md) 9 | 10 | ## 在线示例 11 | 12 | umi 多 tabs 示例:https://codesandbox.io/s/umi-keep-alive-tabs-demo-knfxy 13 | 14 | ## 使用方法 15 | 16 | 1. 安装 17 | 18 | ```bash 19 | npm install umi-plugin-keep-alive --save 20 | # or 21 | yarn add umi-plugin-keep-alive 22 | ``` 23 | 24 | 2. 从 `umi` 中导出 `KeepAlive`,包裹在需要被缓存的组件上 25 | 26 | ```javascript 27 | import { useState } from 'react' 28 | import { KeepAlive } from 'umi' 29 | 30 | function Counter() { 31 | const [count, setCount] = useState(0) 32 | 33 | return ( 34 |
35 |

count: {count}

36 | 37 |
38 | ) 39 | } 40 | 41 | export default function() { 42 | const [show, setShow] = useState(true) 43 | 44 | return ( 45 |
46 |

Page index

47 | {show && ( 48 | 49 | 50 | 51 | )} 52 | 53 |
54 | ) 55 | } 56 | ``` 57 | 58 | ## 文档 59 | 60 | 所有来自 [react-activation](https://github.com/CJY0208/react-activation/blob/master/README_CN.md) 都可以由 `umi` 导出 61 | 62 | ```javascript 63 | import { 64 | KeepAlive, 65 | useActivate, 66 | useUnactivate, 67 | withActivation, 68 | withAliveScope, 69 | useAliveController 70 | } from 'umi' 71 | ``` 72 | 73 | 访问 [react-activation](https://github.com/CJY0208/react-activation/blob/master/README_CN.md) 查阅完整的文档 74 | 75 | ## LICENSE 76 | 77 | MIT 78 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # umi-plugin-keep-alive 2 | 3 | [![NPM version](https://img.shields.io/npm/v/umi-plugin-keep-alive.svg?style=flat)](https://npmjs.org/package/umi-plugin-keep-alive) 4 | [![NPM downloads](http://img.shields.io/npm/dm/umi-plugin-keep-alive.svg?style=flat)](https://npmjs.org/package/umi-plugin-keep-alive) 5 | 6 | `` for umijs base on [react-activation](https://github.com/CJY0208/react-activation) 7 | 8 | English | [中文说明](./README.md) 9 | 10 | ## Online Demo 11 | 12 | https://codesandbox.io/s/umi-keep-alive-tabs-demo-knfxy 13 | 14 | ## Usage 15 | 16 | 1. Install 17 | 18 | ```bash 19 | npm install umi-plugin-keep-alive --save 20 | # or 21 | yarn add umi-plugin-keep-alive 22 | ``` 23 | 24 | 2. export `KeepAlive` from umi and wrap any component you want to be keeped 25 | 26 | ```javascript 27 | import { useState } from 'react' 28 | import { KeepAlive } from 'umi' 29 | 30 | function Counter() { 31 | const [count, setCount] = useState(0) 32 | 33 | return ( 34 |
35 |

count: {count}

36 | 37 |
38 | ) 39 | } 40 | 41 | export default function() { 42 | const [show, setShow] = useState(true) 43 | 44 | return ( 45 |
46 |

Page index

47 | {show && ( 48 | 49 | 50 | 51 | )} 52 | 53 |
54 | ) 55 | } 56 | ``` 57 | 58 | ## Options 59 | 60 | TODO 61 | 62 | ## Documentation 63 | 64 | All function of react-activation can be completely imported from umi 65 | 66 | ```javascript 67 | import { 68 | KeepAlive, 69 | useActivate, 70 | useUnactivate, 71 | withActivation, 72 | withAliveScope, 73 | useAliveController 74 | } from 'umi' 75 | ``` 76 | 77 | Visit [react-activation](https://github.com/CJY0208/react-activation) for full documentation 78 | 79 | ## LICENSE 80 | 81 | MIT 82 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | join 3 | } from 'path' 4 | import { 5 | readFileSync, 6 | } from 'fs' 7 | import { 8 | Mustache 9 | } from '@umijs/utils'; 10 | 11 | const Encoding = 'utf-8'; 12 | 13 | export default (api) => { 14 | // 通过新增方法判断umi4 15 | const isUmi4 = typeof api.modifyAppData === 'function'; 16 | 17 | if (isUmi4) { 18 | // umi@4 自动生成插件目录 19 | api.addRuntimePlugin({ 20 | fn: () => '@@/plugin-keepAlive/runtime', 21 | stage: -1 * Number.MAX_SAFE_INTEGER, 22 | }) 23 | } else { 24 | api.addRuntimePlugin({ 25 | fn: () => '@@/plugin-keep-alive/runtime', 26 | stage: -1 * Number.MAX_SAFE_INTEGER, 27 | }) 28 | } 29 | 30 | // // 因 keep-alive 的 runtime 部分选择不渲染其 children,可能会丢失默认的用户 rootContainer 31 | // // 此处修复自定义 rootContainer 32 | // // https://github.com/umijs/umi/blob/master/packages/preset-built-in/src/plugins/generateFiles/core/plugin.ts#L23-L27 33 | // const customizedAppPath = getFile({ 34 | // base: api.paths.absSrcPath, 35 | // fileNameWithoutExt: 'app', 36 | // type: 'javascript', 37 | // })?.path 38 | 39 | // if (customizedAppPath) { 40 | // api.addRuntimePlugin({ 41 | // fn: () => '@@/plugin-keep-alive/fixCustomizedRuntime', 42 | // stage: -1 * Number.MAX_SAFE_INTEGER + 1, 43 | // }) 44 | 45 | // api.onGenerateFiles(async () => { 46 | // api.writeTmpFile({ 47 | // path: 'plugin-keep-alive/fixCustomizedRuntime.tsx', 48 | // content: Mustache.render( 49 | // readFileSync(join(__dirname, 'fixCustomizedRuntime.tsx.tpl'), Encoding), 50 | // {}, 51 | // ), 52 | // }) 53 | // }) 54 | // } 55 | 56 | // Babel Plugin for react-activation 57 | const babelOpt = [require.resolve('react-activation/babel')] 58 | if (typeof api.modifyBabelOpts === 'function') { 59 | api.modifyBabelOpts((babelOpts) => { 60 | babelOpts.plugins.push(babelOpt) 61 | return babelOpts 62 | }) 63 | } 64 | 65 | if (typeof api.addExtraBabelPlugins === 'function') { 66 | api.addExtraBabelPlugins(() => [babelOpt]) 67 | } 68 | // 生成:export * from 'react-activation' 69 | // TODO: 指明支持的 api,使用上述方式存在 bug https://github.com/guybedford/es-module-lexer/issues/76 70 | // 业务中可 import { KeepAlive } from 'umi' 71 | if (isUmi4) { 72 | // umi@4 需要通过插件文件进行自动导出 73 | api.onGenerateFiles(async () => { 74 | api.writeTmpFile({ 75 | path: 'index.ts', 76 | content: Mustache.render(readFileSync(join(__dirname, 'index.ts.tpl'), Encoding), {}), 77 | }) 78 | api.writeTmpFile({ 79 | path: 'runtime.tsx', 80 | content: Mustache.render(readFileSync(join(__dirname, 'runtime.tsx.tpl'), Encoding), {}), 81 | }) 82 | }) 83 | } else { 84 | api.addUmiExports(() => [{ 85 | specifiers: [ 86 | 'KeepAlive', 87 | 'useActivate', 88 | 'autoFixContext', 89 | 'useUnactivate', 90 | 'withActivation', 91 | 'withAliveScope', 92 | 'useAliveController', 93 | ], 94 | source: 'react-activation', 95 | }, ]) 96 | 97 | api.onGenerateFiles(async () => { 98 | api.writeTmpFile({ 99 | path: 'plugin-keep-alive/runtime.tsx', 100 | content: Mustache.render(readFileSync(join(__dirname, 'runtime.tsx.tpl'), Encoding), {}), 101 | }) 102 | }) 103 | } 104 | } --------------------------------------------------------------------------------