├── .browserslistrc ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .npmrc ├── .prettierrc ├── .stylelintrc.json ├── README.md ├── babel.config.js ├── build ├── index.html └── static │ ├── css │ └── main-style.da4059.css │ ├── dll │ ├── core-js@3.15.1.production.js │ ├── dll_basic_cfedf4.production.js │ ├── dll_basic_cfedf4.production.js.VERSION.txt │ ├── dll_tool_441c03.production.js │ └── dll_tool_441c03.production.js.VERSION.txt │ └── js │ ├── main.f961ed.js │ └── main.f961ed.js.LICENSE.txt ├── dll ├── basic_manifest_development.json ├── basic_manifest_production.json ├── dll_basic_cfedf4.production.js ├── dll_basic_cfedf4.production.js.VERSION.txt ├── dll_basic_e0cff6.development.js ├── dll_basic_e0cff6.development.js.map ├── dll_tool_441c03.production.js ├── dll_tool_441c03.production.js.VERSION.txt ├── dll_tool_9f2285.development.js ├── dll_tool_9f2285.development.js.map ├── tool_manifest_development.json └── tool_manifest_production.json ├── lint-staged.config.js ├── package.json ├── postcss.config.js ├── report └── dll │ ├── report.html │ └── stats.json ├── scripts ├── build.js ├── config │ ├── devServer.config.js │ ├── htmlPlugins.config.js │ ├── loaders.config.js │ ├── paths.config.js │ ├── webpack.base.config.js │ ├── webpack.dev.config.js │ ├── webpack.dll.config.js │ └── webpack.prod.config.js ├── dll-version-check.js ├── dll.js ├── start.js └── utils │ ├── findDLLFile.js │ ├── parseCommandLine.js │ └── tools.js ├── src ├── bootstrap.tsx ├── components │ └── DynamicSystem │ │ └── index.tsx ├── index.html ├── index.tsx ├── modules │ ├── remote │ │ ├── Cache.tsx │ │ ├── Content.tsx │ │ ├── index.tsx │ │ └── remoteSlice.ts │ └── root │ │ ├── routes.tsx │ │ └── styles │ │ └── root.scss └── store │ ├── StoreNames.ts │ ├── createSlice.ts │ ├── createStore.ts │ ├── index.ts │ └── initImmer.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tsconfig.tsbuildinfo ├── typings ├── global.d.ts └── media.d.ts └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | 3 | >0.1% 4 | last 4 versions 5 | Firefox ESR 6 | not ie <= 8 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | scripts/* 2 | node_modules/**/* 3 | .eslintrc.js 4 | lint-staged.config.js 5 | babel.config.js 6 | typings/* 7 | dll/* 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // 仅 commit 时会用 2 | const commitRules = { 3 | 'no-alert': 2, 4 | 'no-debugger': 2, 5 | 'no-console': [ 6 | 'error', 7 | { 8 | allow: ['warn', 'error'], 9 | }, 10 | ], 11 | 'no-unused-vars': 2, 12 | }; 13 | 14 | module.exports = { 15 | root: true, 16 | extends: [ 17 | 'airbnb-typescript', 18 | 'plugin:@typescript-eslint/recommended', 19 | 'plugin:prettier/recommended', 20 | 'prettier', 21 | ], 22 | parserOptions: { 23 | project: './tsconfig.json', 24 | ecmaVersion: 6, 25 | sourceType: 'module', 26 | ecmaFeatures: { 27 | jsx: true, 28 | }, 29 | }, 30 | globals: { 31 | React: true, 32 | ReactDOM: true, 33 | _: true, 34 | moment: true, 35 | mobx: true 36 | }, 37 | rules: { 38 | // javascript 39 | 'brace-style': [2, '1tbs', { allowSingleLine: true }], 40 | 'block-spacing': [2, 'always'], 41 | 'no-const-assign': 2, 42 | 'prefer-destructuring': 0, // 推荐通过结构赋值访问 object 或者 array 43 | 'no-prototype-builtins': 0, 44 | 'no-useless-escape': 0, 45 | 'no-unused-expressions': 2, // 开启对短路求值和三元表达式的支持 46 | 'no-console': [2, { allow: ['warn', 'error', 'log'] }], 47 | 'no-nested-ternary': 2, // 禁止三元表达式嵌套 48 | 'no-param-reassign': 0, // 禁止对函数参数再赋值,immer 需要打开 49 | // jsx 50 | 'jsx-a11y/anchor-is-valid': 0, 51 | 'jsx-a11y/click-events-have-key-events': 0, // 强制 绑定 onClick 事件同时也绑定 onKeyUp, onKeyDown, onKeyPress 等事件 52 | 'jsx-a11y/no-static-element-interactions': 0, // 强制给 div span 等没有语义的标签加上 role 角色,(在有onClick 等事件的前提下) 53 | // react 54 | '@typescript-eslint/no-unused-expressions': 2, // 开启对短路求值和三元表达式的支持 55 | 'react/jsx-no-undef': [ 56 | 2, 57 | { 58 | allowGlobals: true, 59 | }, 60 | ], 61 | 'react/static-property-placement': 0, // static 静态类型强制定义在class 组件外 62 | 'react/no-deprecated': 0, 63 | 'react/jsx-no-target-blank': 0, 64 | 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx', '.ts', '.tsx'] }], // 识别扩展名 65 | 'react/jsx-props-no-spreading': 0, // 禁止 jsx 组件上使用 44 | 45 | 46 |

count: {count}

47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 63 | 64 |
65 |
66 | {/* */} 72 |
73 | 74 | ); 75 | } 76 | } 77 | 78 | const mapStateToProps = (state: RootState) => { 79 | return { 80 | remoteStore: state.remote, 81 | }; 82 | }; 83 | 84 | export default connect(mapStateToProps)(DynamicRemote); 85 | -------------------------------------------------------------------------------- /src/modules/remote/remoteSlice.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Slice 结合了 action 与 reduce 的能力 3 | * */ 4 | import { STORE_NAMES } from 'store'; 5 | import { BaseSlice, createSubSlice } from '../../store/createSlice'; 6 | 7 | interface InitialState { 8 | count: number; 9 | remote: { 10 | data: { remoteUrl: string; scope: string; module: string }; 11 | cache: { remoteUrl: string; scope: string; module: string }[]; 12 | }; 13 | } 14 | 15 | class HomeSlice extends BaseSlice { 16 | storeName = STORE_NAMES.REMOTE; 17 | constructor() { 18 | // 通过 super() 传入初始数据 19 | super({ 20 | count: 0, 21 | remote: { 22 | data: { remoteUrl: '', scope: '', module: '' }, 23 | cache: [], 24 | }, 25 | }); 26 | } 27 | 28 | increase() { 29 | console.log(this.store); 30 | console.log(this.currentState); 31 | console.log(this.state); 32 | // Slice 内通过 this.updateStore() 来更新 state 33 | this.updateStore((state) => { 34 | // updateStore 参数函数内,实际就是 Immer 的 produce, 35 | // 在这里可以直接操作数据,而不用结构,Immer 会帮助生成新的不可变数据 36 | state.count += 1; 37 | }); 38 | 39 | // Slice 内通过 this.resetStore() 来重置 state 40 | } 41 | 42 | getData() { 43 | return new Promise((resolve) => { 44 | setTimeout( 45 | () => resolve({ remoteUrl: 'default', scope: 'default', module: 'default' }), 46 | 3000, 47 | ); 48 | }).then((cache: InitialState['remote']['data']) => { 49 | this.updateStore((state) => { 50 | state.remote.cache.push(cache); 51 | }); 52 | }); 53 | } 54 | } 55 | 56 | export default createSubSlice(new HomeSlice()); 57 | -------------------------------------------------------------------------------- /src/modules/root/routes.tsx: -------------------------------------------------------------------------------- 1 | import { Route, Switch, Redirect, Link } from 'react-router-dom'; 2 | import { Button } from 'antd'; 3 | import './styles/root.scss'; 4 | import Remote from '../remote'; 5 | 6 | const RouteEntry: React.FC = () => { 7 | const [state, setState] = React.useState({ 8 | title: '', 9 | age: 0, 10 | }); 11 | 12 | const reset = () => { 13 | setState( 14 | immer.produce((draft: Immer.Draft) => { 15 | draft.title = 'new title'; 16 | }), 17 | ); 18 | }; 19 | console.log(state); 20 | return ( 21 | 22 | } /> 23 | 24 | ( 28 |
29 | 30 | /remote 31 |
32 | )} 33 | /> 34 |
35 | ); 36 | }; 37 | 38 | export default RouteEntry; 39 | -------------------------------------------------------------------------------- /src/modules/root/styles/root.scss: -------------------------------------------------------------------------------- 1 | .root-login { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | width: 400px; 6 | height: 400px; 7 | border: 1px saddlebrown solid; 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | -------------------------------------------------------------------------------- /src/store/StoreNames.ts: -------------------------------------------------------------------------------- 1 | enum STORE_NAMES { 2 | REMOTE = 'remote', 3 | } 4 | 5 | export default STORE_NAMES; 6 | -------------------------------------------------------------------------------- /src/store/createSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction, Store } from '@reduxjs/toolkit'; 2 | 3 | type TUpdateFn = (state: Immer.Draft) => void; 4 | 5 | // eslint-disable-next-line @typescript-eslint/ban-types 6 | abstract class BaseSlice { 7 | public store: Store; 8 | abstract storeName: string; 9 | public initialState: State; 10 | public updateStore: (updateFn: Partial | TUpdateFn) => void; 11 | public resetStore: () => void; 12 | 13 | constructor(initialState: State) { 14 | this.initialState = initialState; 15 | } 16 | 17 | public get state() { 18 | return this.store.getState(); 19 | } 20 | 21 | public get currentState() { 22 | const state = this.state; 23 | return state[this.storeName]; 24 | } 25 | } 26 | 27 | // eslint-disable-next-line @typescript-eslint/ban-types 28 | function createSubSlice(sliceInstance: SI) { 29 | const { storeName, initialState } = sliceInstance; 30 | 31 | const slice = createSlice({ 32 | name: storeName, 33 | initialState: initialState as IState, 34 | reducers: { 35 | updateStore: ( 36 | state: Immer.Draft, 37 | action: PayloadAction<{ updateFn: TUpdateFn }>, 38 | ) => { 39 | return action.payload.updateFn(state); 40 | }, 41 | resetStore: (state: Immer.Draft) => { 42 | Object.keys(initialState).forEach((key: string) => { 43 | state[key] = initialState[key]; 44 | }); 45 | }, 46 | }, 47 | }); 48 | 49 | const { updateStore, resetStore } = slice.actions; 50 | 51 | sliceInstance.updateStore = function (param: Partial | TUpdateFn) { 52 | if (typeof param === 'function') { 53 | this.store.dispatch(updateStore({ updateFn: param })); 54 | } else { 55 | this.store.dispatch( 56 | updateStore({ 57 | updateFn: (state) => { 58 | Object.keys(param).forEach((key: string) => { 59 | state[key] = param[key]; 60 | }); 61 | }, 62 | }), 63 | ); 64 | } 65 | }; 66 | 67 | sliceInstance.resetStore = function () { 68 | this.store.dispatch(resetStore()); 69 | }; 70 | 71 | return { 72 | reducer: slice.reducer, 73 | sliceInstance, 74 | }; 75 | } 76 | 77 | export { BaseSlice, createSubSlice }; 78 | -------------------------------------------------------------------------------- /src/store/createStore.ts: -------------------------------------------------------------------------------- 1 | import { 2 | configureStore, 3 | ConfigureStoreOptions, 4 | getDefaultMiddleware, 5 | Store, 6 | } from '@reduxjs/toolkit'; 7 | import { Action, AnyAction, Reducer, ReducersMapObject } from 'redux'; 8 | import { BaseSlice, createSubSlice } from './createSlice'; 9 | 10 | // 给每一个 Slice 注入 store 11 | function beforeMountStore(store: Store) { 12 | function mountStoreToSlice(sliceInstance: SI): SI { 13 | sliceInstance.store = store; 14 | return sliceInstance; 15 | } 16 | 17 | return { mountStoreToSlice }; 18 | } 19 | 20 | /** 21 | * 默认的 middleware 中 serializableCheck 会带来封装代码的报错: 22 | * "不让传 函数给 redux state" 实际上我们传入的函数只是个中间态,不会挂载到 state 上 23 | * 所以这里将他关掉 24 | * */ 25 | function createStore(options: { 26 | reducer: Reducer | ReducersMapObject; 27 | devTools?: boolean | ConfigureStoreOptions['devTools']; 28 | }) { 29 | const { reducer, devTools } = options; 30 | return configureStore({ 31 | reducer, 32 | devTools, 33 | middleware: getDefaultMiddleware({ 34 | serializableCheck: false, 35 | }), 36 | }); 37 | } 38 | 39 | export { beforeMountStore, createStore, BaseSlice, createSubSlice }; 40 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 模块 store 创建 3 | * */ 4 | 5 | import { createStore, beforeMountStore } from './createStore'; 6 | import STORE_NAMES from './StoreNames'; 7 | import remoteSlice from '../modules/remote/remoteSlice'; 8 | 9 | const store = createStore({ 10 | reducer: { 11 | [STORE_NAMES.REMOTE]: remoteSlice.reducer, 12 | }, 13 | }); 14 | 15 | export type RootState = ReturnType; 16 | // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} 17 | export type AppDispatch = typeof store.dispatch; 18 | 19 | const { mountStoreToSlice } = beforeMountStore(store); 20 | 21 | export const actions = { 22 | [STORE_NAMES.REMOTE]: mountStoreToSlice(remoteSlice.sliceInstance), 23 | }; 24 | 25 | export { mountStoreToSlice, STORE_NAMES }; 26 | 27 | export default store; 28 | -------------------------------------------------------------------------------- /src/store/initImmer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Immer 兼容低版本浏览器设置 3 | * */ 4 | 5 | import { enableAllPlugins } from 'immer'; 6 | 7 | enableAllPlugins(); 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "jsx": "preserve", 6 | "noEmit": true, 7 | "moduleResolution": "node", 8 | "rootDir": "src", 9 | "noImplicitReturns": true, 10 | "noImplicitThis": true, 11 | "noImplicitAny": true, 12 | "importHelpers": false, 13 | "strictNullChecks": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "noUnusedLocals": false, 16 | "useDefineForClassFields": true, 17 | "experimentalDecorators": true, 18 | "baseUrl": "./src", 19 | "incremental": true, // 增量编译,加快速度 20 | "allowUmdGlobalAccess": true, 21 | "downlevelIteration": true, 22 | "paths": { 23 | "store/*": [ 24 | "src/store/*" 25 | ] 26 | }, 27 | "esModuleInterop": true, 28 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 29 | }, 30 | "include": [ 31 | "typings/**/*", 32 | "src/**/*" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "noUnusedLocals": true 5 | }, 6 | "include": [ 7 | "typings/**/*", 8 | "src/**/*" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.tsbuildinfo: -------------------------------------------------------------------------------- 1 | {"version":"4.3.4"} -------------------------------------------------------------------------------- /typings/global.d.ts: -------------------------------------------------------------------------------- 1 | import * as lodash from 'lodash'; 2 | import * as OriginImmer from 'immer'; 3 | import { Draft as ODraft, Immutable as OImmutable } from 'immer'; 4 | 5 | // React 已经是全局模块了不需要再引入 6 | declare global { 7 | type _ = typeof lodash; 8 | const immer: typeof OriginImmer; 9 | const __webpack_init_sharing__: any; 10 | const __webpack_share_scopes__: any; 11 | const RUNTIME_NODE_ENV: string; 12 | 13 | interface Window { 14 | __webpack_init_sharing__: any; 15 | __webpack_share_scopes__: any; 16 | __REDUX_DEVTOOLS_EXTENSION_COMPOSE__: Function; 17 | } 18 | 19 | interface Navigator { 20 | userLanguage: string; 21 | } 22 | 23 | namespace Immer { 24 | export type Draft = ODraft; 25 | export type Immutable = OImmutable; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /typings/media.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.less' { 2 | const content: any; 3 | export default content; 4 | } 5 | 6 | declare module '*.svg' { 7 | const content: string; 8 | export default content; 9 | } 10 | 11 | declare module '*.png' { 12 | const content: string; 13 | export default content; 14 | } 15 | declare module '*.jpg' { 16 | const content: string; 17 | export default content; 18 | } 19 | declare module '*.jpeg' { 20 | const content: string; 21 | export default content; 22 | } 23 | declare module '*.gif' { 24 | const content: string; 25 | export default content; 26 | } 27 | declare module '*.bmp' { 28 | const content: string; 29 | export default content; 30 | } 31 | declare module '*.tiff' { 32 | const content: string; 33 | export default content; 34 | } 35 | --------------------------------------------------------------------------------