├── .babelrc.js ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── README.md ├── examples └── demo │ ├── .gitignore │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── public │ ├── favicon.ico │ └── index.html │ └── src │ ├── App.vue │ ├── main.js │ └── store │ ├── index.js │ └── modules │ ├── product │ └── store.js │ └── user │ └── store.js ├── lib └── vue-savedata.umd.js ├── package.json ├── packages └── index.js ├── rollup.config.js └── test └── savedata.test.js /.babelrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | "presets": [ 4 | ["@babel/env",{ 5 | "targets": { 6 | "ie": 9 7 | } 8 | }] 9 | ] 10 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // extends: 'react-app', 3 | extends: 'eslint:recommended', // eslint 自带 4 | env: { 5 | node: true, 6 | es6: true, 7 | commonjs: true, 8 | }, 9 | parserOptions: { 10 | "ecmaVersion": 2018 11 | }, 12 | rules: { 13 | "no-console": 0, 14 | "indent": ['error', 4], //缩进风格 15 | }, 16 | parser : "babel-eslint" 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | coverage 4 | tmp 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | package-lock.json* 14 | yarn.lock* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw* 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "11" 5 | 6 | before_script: 7 | - npm install codecov --save-dev 8 | - npm run build 9 | 10 | script: 11 | - npm run test 12 | #指定分支,只有指定的分支提交时才会运行脚本 13 | branches: 14 | only: 15 | - master 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # vue-savedata 3 | [![Build Status](https://www.travis-ci.org/Banlangenn/vue-savedata.svg?branch=master)](https://www.travis-ci.org/Banlangenn/vue-savedata) [![codecov](https://codecov.io/gh/Banlangenn/vue-savedata/branch/master/graph/badge.svg)](https://codecov.io/gh/Banlangenn/vue-savedata) 4 | 5 | vuex 指定【模块】的state持久化(配置简,性能佳,体积小: gzip压缩之后 1238字节 ≈ 1.2kb) 6 | ## updata 2.0.6 7 | * 修复ie9 ie10 报错问题 8 | * 添加 ciphertext密文支持 9 | * 添加 SS LS 支持数组 (每一个module要添加store中modules中) 10 | * 添加 默认储存位置配置 11 | * 支持 模块命名空间 12 | ## Requirements 13 | 14 | * [Vue.js](https://vuejs.org) (v2.0.0+) 15 | * [Vuex](http://vuex.vuejs.org) (v2.0.0+) 16 | 17 | ## Installation 18 | 19 | ```bash 20 | $ npm install vue-savedata 21 | $ yarn add vue-savedata 22 | ``` 23 | 24 | ## Usage 25 | 26 | ```js 27 | import createPersiste from 'vue-savedata' 28 | // 默认全部持久化,你也可以通过一丢丢配置项,指定数据持久化 29 | const store = new Vuex.Store({ 30 | // ... 31 | plugins: [createPersiste()], 32 | }) 33 | ``` 34 | ## API 35 | 36 | ### `createPersiste([options])` 37 | 下列选项(默认保存store中的每个数据到本地 ) 38 | ### (`温馨提示`: LS即Localstorage本地存储, SS即sessionStorage本地存储, LS、SS可同时使用,也可单独使用 ) 39 | 可以为您的特定需求配置插件: 40 | (参数都是可选的:有默认值) 41 | * `saveName `: 本地save的key 默认: savedata 42 | * `ciphertext `: 是不是密文存本地(base64) 默认 false 43 | * `mode `: 默认存储模式(LS,SS配置不存在时有效) 默认: LS 44 | * `MMD `: 模块 深度合并, 深度值 默认:2(如果出现数据丢失可以尝试把这个开高一点) 45 | * `SS || `: { storePath: xx, module: xx } __注:storePath:(和Vuex中的option.modules:{key:value}的key,一,一对应)__ 46 | * `SL || `: { storePath: xx, module: xx } 同上, 支持多个模块,传入数组 47 | 48 | 49 | 50 | ```js 51 | import createPersiste from 'vue-savedata' 52 | import module1 from './modules/module1' 53 | import module2 from './modules/module2' 54 | const persiste = createPersiste({ 55 | ciphertext: true, // 加密存本地, 默认为false 56 | LS: { 57 | module: module1, 58 | storePath: 'module100' // __storePath:(和Vuex中的option.modules:{key:value}的key,一,一对应)__ 59 | }, 60 | SS: { 61 | module: module2, 62 | storePath: 'module2' 63 | } 64 | }) 65 | /** 66 | * 67 | * 数组 支持传入多个模块,相应,__storePath:和Vuex中的option.modules:{key:value}的key,一一对应__ 68 | * const persiste = createPersiste({ 69 | LS:[{ 70 | module: module1, 71 | storePath: 'module100' 72 | },...], 73 | SS: [{ 74 | module: module2, 75 | storePath: 'module2' 76 | },...] 77 | }) 78 | ***/ 79 | const store = new Vuex.Store({ 80 | // ... 81 | modules: { 82 | module100: module1, 83 | module2 84 | }, 85 | plugins: [persiste], 86 | }) 87 | ``` 88 | 89 | -------------------------------------------------------------------------------- /examples/demo/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /examples/demo/README.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn run build 16 | ``` 17 | 18 | ### Run your tests 19 | ``` 20 | yarn run test 21 | ``` 22 | 23 | ### Lints and fixes files 24 | ``` 25 | yarn run lint 26 | ``` 27 | 28 | ### Customize configuration 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /examples/demo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ], 5 | //添加忽略项 6 | ignore: [ 7 | './../../lib', 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /examples/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint" 9 | }, 10 | "dependencies": { 11 | "core-js": "^2.6.5", 12 | "vue": "^2.6.10", 13 | "vuex": "^3.5.1" 14 | }, 15 | "devDependencies": { 16 | "@vue/cli-plugin-babel": "^3.11.0", 17 | "@vue/cli-plugin-eslint": "^3.11.0", 18 | "@vue/cli-service": "^3.11.0", 19 | "babel-eslint": "^10.0.1", 20 | "eslint": "^5.16.0", 21 | "eslint-plugin-vue": "^5.0.0", 22 | "vue-template-compiler": "^2.6.10" 23 | }, 24 | "eslintConfig": { 25 | "root": true, 26 | "env": { 27 | "node": true 28 | }, 29 | "extends": [ 30 | "plugin:vue/essential", 31 | "eslint:recommended" 32 | ], 33 | "rules": {}, 34 | "parserOptions": { 35 | "parser": "babel-eslint" 36 | } 37 | }, 38 | "postcss": { 39 | "plugins": { 40 | "autoprefixer": {} 41 | } 42 | }, 43 | "browserslist": [ 44 | "> 1%", 45 | "last 2 versions" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /examples/demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Banlangenn/vue-savedata/af9c65c1dd3d94841b149941189f67f810a98768/examples/demo/public/favicon.ico -------------------------------------------------------------------------------- /examples/demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | demo 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/demo/src/App.vue: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 9 | 10 | 28 | 29 | -------------------------------------------------------------------------------- /examples/demo/src/main.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Vue from 'vue'; 3 | import App from './App'; 4 | import store from './store/index'; 5 | 6 | Vue.config.productionTip = false; 7 | 8 | new Vue({ 9 | store, 10 | render: h => h(App) 11 | }).$mount('#app'); 12 | -------------------------------------------------------------------------------- /examples/demo/src/store/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import Vue from 'vue'; 3 | import Vuex from 'vuex'; 4 | import createPersisted from './../../../../lib/vue-savedata.umd'; 5 | import user from './modules/user/store'; 6 | import product from './modules/product/store'; 7 | const persisted = createPersisted({ 8 | saveName: 'aaa', 9 | ciphered: false, 10 | mode: 'LS', 11 | LS: [ 12 | { 13 | module: user, 14 | storePath: 'user' 15 | }, 16 | { 17 | module: product, 18 | storePath: 'product' 19 | }, 20 | ] 21 | }); 22 | 23 | // const persiste2 = createPersiste({ 24 | // saveName: 'bbb', 25 | // ciphered: true, 26 | // mode: 'LS', 27 | // LS: [ 28 | // { 29 | // module: user, 30 | // storePath: 'user' 31 | // }, 32 | // ] 33 | // }); 34 | 35 | Vue.use(Vuex); 36 | const store = new Vuex.Store({ 37 | modules: { 38 | user, 39 | product 40 | }, 41 | plugins: [ persisted ] 42 | }); 43 | 44 | export default store; 45 | -------------------------------------------------------------------------------- /examples/demo/src/store/modules/product/store.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const productstore = { 3 | // namespaced: true, 4 | state: { 5 | brand: [], 6 | products: '1459856855', 7 | pictures: [] 8 | }, 9 | mutations: { 10 | nickname(state, nickname) { 11 | state.products = nickname; 12 | } 13 | }, 14 | actions: { 15 | setactnickname(context, nickname) { 16 | context.commit('nickname', nickname); 17 | } 18 | } 19 | }; 20 | 21 | export default productstore; 22 | -------------------------------------------------------------------------------- /examples/demo/src/store/modules/user/store.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const user = { 3 | namespaced: true, 4 | state: { 5 | nickname: '' 6 | }, 7 | mutations: { 8 | nickname(state, nickname) { 9 | state.nickname = nickname; 10 | } 11 | }, 12 | actions: { 13 | setactnickname(context, nickname) { 14 | context.commit('nickname', nickname); 15 | } 16 | } 17 | }; 18 | 19 | export default user; 20 | -------------------------------------------------------------------------------- /lib/vue-savedata.umd.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):((e=e||self)["vue-savedata"]=e["vue-savedata"]||{},e["vue-savedata"].umd=t())}(this,function(){"use strict";function e(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function t(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function r(r){for(var n=1;arguments.length>n;n++){var o=null!=arguments[n]?arguments[n]:{};n%2?t(Object(o),!0).forEach(function(t){e(r,t,o[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(o)):t(Object(o)).forEach(function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(o,e))})}return r}function n(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=[];t>r;r++)n[r]=e[r];return n}function o(e,t){var r;if("undefined"==typeof Symbol||null==e[Symbol.iterator]){if(Array.isArray(e)||(r=function(e,t){if(e){if("string"==typeof e)return n(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?n(e,t):void 0}}(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var o=0,a=function(){};return{s:a,n:function(){return e.length>o?{done:!1,value:e[o++]}:{done:!0}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var i,u=!0,c=!1;return{s:function(){r=e[Symbol.iterator]()},n:function(){var e=r.next();return u=e.done,e},e:function(e){c=!0,i=e},f:function(){try{u||null==r.return||r.return()}finally{if(c)throw i}}}}return function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t.SS,a=void 0===n?null:n,i=t.LS,u=void 0===i?null:i,c=t.saveName,l=void 0===c?"saveData":c,f=t.mode,s=void 0===f?"LS":f,d=t.MMD,y=void 0===d?2:d,p=t.ciphertext,v=void 0!==p&&p,b=t.encode,S=void 0===b?function(e){return window.btoa(encodeURIComponent(JSON.stringify(e)))}:b,m=t.decode,O=void 0===m?function(e){return JSON.parse(decodeURIComponent(window.atob(e)))}:m,g=t.setState,h=void 0===g?function(e,t){e=v?S(e):JSON.stringify(e),window[t].setItem(l,e)}:g,w=t.getState,j=void 0===w?function(e){try{var t=window[e].getItem(l);return t?v?O(t):JSON.parse(t):null}catch(e){return null}}:w,P=t.checkParams,A=void 0===P?function(e){return!(!e.hasOwnProperty("storePath")||!e.hasOwnProperty("module"))||(console.warn("SS,LS的key约定必须包含storePath、module"),!1)}:P,I=t.deepMerge,D=void 0===I?function(e,t,r){if(!r)return t;for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=e[n]&&"[object Object]"===Object.prototype.toString.call(e[n])?D(e[n],t[n],--r):e[n]=t[n]);return e}:I;function k(e,t){if(e){var r=t.split("/");return r[r.length-1]}return t}function N(t,n,a){var i,u=null,c=o(t);try{for(c.s();!(i=c.n()).done;){var l=i.value,f=k(l.module.namespaced,n.type),s=l.module.mutations;s&&Object.prototype.hasOwnProperty.call(s,f)&&(u=r(r({},u),{},e({},l.storePath,a[l.storePath])))}}catch(e){c.e(e)}finally{c.f()}return u}return function(e){var t,n=!a||Array.isArray(a)?a:[a],i=!u||Array.isArray(u)?u:[u],c=null,l=null;if(i){var f,d=o(i);try{for(d.s();!(f=d.n()).done;)if(!A(f.value)){i=null;break}}catch(e){d.e(e)}finally{d.f()}i&&(l=t=j("localStorage"))}if(n){var p,v=o(n);try{for(v.s();!(p=v.n()).done;)if(!A(p.value)){n=null;break}}catch(e){v.e(e)}finally{v.f()}n&&(c=j("sessionStorage"),t=l?r(r({},l),c):c)}i||n||(t=j("".concat("SS"!==s?"localStorage":"sessionStorage"))),t&&e.replaceState(D(e.state,t,y+1)),e.subscribe(function(e,t){if(i){var o=N(i,e,t);if(o&&(l=r(r({},l),o),h(l,"localStorage")),!n)return}if(n){var f=N(n,e,t);f&&(c=r(r({},c),f),h(c,"sessionStorage"))}else!u&&!a&&h(t,"".concat("SS"!==s?"localStorage":"sessionStorage"))})}}}); 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-savedata", 3 | "version": "2.0.5", 4 | "main": "./lib/vue-savedata.umd.js", 5 | "description": "vuex插件 指定数据持久化(配置简,性能佳,体积小)", 6 | "scripts": { 7 | "test": "jest && codecov", 8 | "test:l": "jest", 9 | "build": "rm -rf ./lib && rollup -c", 10 | "lint": "eslint packages test" 11 | }, 12 | "jest": { 13 | "coverageDirectory": "./coverage/", 14 | "collectCoverage": true, 15 | "transform": { 16 | "^.+\\.js?$": "babel-jest" 17 | } 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/Banlangenn/vue-savedata" 22 | }, 23 | "keywords": [ 24 | "vuex", 25 | "persitste", 26 | "vue", 27 | "savedata" 28 | ], 29 | "author": "balangen", 30 | "license": "ISC", 31 | "devDependencies": { 32 | "@babel/core": "^7.3.4", 33 | "@babel/preset-env": "^7.3.4", 34 | "babel-eslint": "^10.0.3", 35 | "babel-jest": "^24.9.0", 36 | "eslint": "^5.16.0", 37 | "global": "^4.3.2", 38 | "jest": "^24.8.0", 39 | "pre-commit": "^1.2.2", 40 | "rollup": "^1.6.0", 41 | "rollup-plugin-babel": "^4.3.2", 42 | "rollup-plugin-terser": "^4.0.4", 43 | "rollup-plugin-uglify": "^6.0.2", 44 | "vue": "^2.5.20", 45 | "vuex": "^3.0.1" 46 | }, 47 | "pre-commit": [ 48 | "lint", 49 | "test:l" 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /packages/index.js: -------------------------------------------------------------------------------- 1 | /*eslint no-undef: "error"*/ 2 | /*eslint-env browser*/ 3 | 4 | /* 5 | * @Author:banlangen 6 | * @Date: 2018-08-12 01:05:13 7 | * @Last Modified by: banlangen 8 | * @Last Modified time: 2019-11-20 09:51:20 9 | * @param {Object} 10 | * SS {storePath: xx, module: xx } 11 | * LS {storePath: xx, module: xx } 12 | * storePath 在store 上的路径 13 | * module 需要 本地存的 模块 14 | * 可以同时 存 LS SS 15 | * saveName key 16 | * Storage 默认 localStorage 存 也可以 sessionStorage (刷新不关闭 变量 刷新就没有了) 17 | * setState 本地存 可以自定义 18 | * getState 本地取 可以自定义 19 | * @returns {Store} 20 | * TODU 21 | * 可以可以 同时支持 sessionStorage localStorage 22 | * ssModule: {} 23 | */ 24 | function createPersiste ({ 25 | SS = null, 26 | LS = null, 27 | saveName = 'saveData', 28 | mode = 'LS', 29 | MMD = 2, // 模块合并深度 => 30 | ciphertext = false, 31 | encode = (data) => { 32 | return window.btoa(encodeURIComponent(JSON.stringify(data))) 33 | }, 34 | decode = (data) => { 35 | return JSON.parse(decodeURIComponent(window.atob(data))) 36 | }, 37 | setState = (state, Sg) => { 38 | state = ciphertext ? encode(state) : JSON.stringify(state) 39 | window[Sg].setItem(saveName, state) 40 | }, 41 | getState = (Sg) => { 42 | try { 43 | let data = window[Sg].getItem(saveName) // 如果 没有key 会返回 null 44 | if (!data) return null 45 | return ciphertext ? decode(data) : JSON.parse(data) 46 | } catch (error) { 47 | return null 48 | } 49 | }, 50 | checkParams = (params) => { 51 | if (!(params.hasOwnProperty('storePath') && params.hasOwnProperty('module'))) { 52 | console.warn(`SS,LS的key约定必须包含storePath、module`) 53 | return false 54 | } 55 | // if (!(params.module.hasOwnProperty('state') && params.module.hasOwnProperty('mutations'))) { 56 | // console.warn(`module约定必须要有mutations、state`) 57 | // return false 58 | // } 59 | return true 60 | }, 61 | deepMerge = (origin, target, deep) => { 62 | // target 覆盖 origin // 63 | if (!deep) return target 64 | for (let key in target) { 65 | if (Object.prototype.hasOwnProperty.call(target, key)) { 66 | origin[key] = origin[key] && Object.prototype.toString.call(origin[key]) === '[object Object]' ? deepMerge(origin[key], target[key], --deep) : origin[key] = target[key] 67 | } 68 | } 69 | return origin 70 | } 71 | } = {}) { 72 | // 辅助函数 减少代码 73 | function typeHandle(namespaced, type) { 74 | if (namespaced) { 75 | const arr = type.split('/') 76 | return arr[arr.length - 1] 77 | } 78 | return type 79 | } 80 | // 处理函数 81 | function dataHandle(data, mutation, state) { 82 | let result = null 83 | // data 组件传入的模块 84 | for (const item of data) { 85 | // 处理命名空间 86 | const type = typeHandle(item.module.namespaced, mutation.type) 87 | // 属于当前模块 改 88 | // 2019.11.20 新加-- 屏蔽掉不合法的 module 89 | const mutations = item.module.mutations 90 | if (!mutations) continue 91 | // 2019.11.20 新加-- 屏蔽掉不合法的 module 92 | 93 | if (Object.prototype.hasOwnProperty.call(mutations, type)) { 94 | result = {...result, [item.storePath]: state[item.storePath]} 95 | // 不能break 不同模块 可能有同名的mutation, 需要触发储存 96 | } 97 | } 98 | return result 99 | } 100 | // SS LS 可以支持数组 101 | // 所以 存取都要数组 102 | return store => { 103 | let _SS = !SS || Array.isArray(SS) ? SS : [SS] 104 | let _LS = !LS || Array.isArray(LS) ? LS : [LS] 105 | let data 106 | let initSSData = null 107 | let initLSData = null 108 | if (_LS) { 109 | // 一次就全部取出来了 110 | for (const item of _LS) { 111 | if (!checkParams(item)) { 112 | _LS = null 113 | break 114 | } 115 | } 116 | if (_LS) { 117 | initLSData = data = getState('localStorage') 118 | } 119 | } 120 | if (_SS) { 121 | // 一次就全部取出来了 122 | for (const item of _SS) { 123 | if (!checkParams(item)) { 124 | _SS = null 125 | break 126 | } 127 | } 128 | // data 存在不能这样子取 129 | if (_SS) { 130 | initSSData = getState('sessionStorage') 131 | // LS 是否有 132 | // 合并数据 133 | data = initLSData ? {...initLSData, ...initSSData} : initSSData 134 | } 135 | } 136 | if (!_LS && !_SS) { 137 | // 'localStorage' 138 | data = getState(`${mode !== 'SS' ? 'localStorage' : 'sessionStorage'}`) 139 | } 140 | data && store.replaceState(deepMerge(store.state, data, MMD + 1)) // {...store.state, ...data} 141 | // 当 store 初始化后调用 142 | store.subscribe((mutation, state) => { 143 | // 1. SS = null 144 | // 2. LS = null 145 | // 3. LS SS = null 146 | // 4. LS SS != null 147 | // 每次 mutation 之后调用 148 | // 这两个可以合并为一个 149 | if (_LS) { 150 | let localData = dataHandle(_LS, mutation, state) 151 | if (localData) { 152 | // 本身已经合并过了 为什么还要合并历史(initLSData) 153 | // localData 拿到的是 当前模块的 value => LS 是数组,可能会有多个 要合并其余的 154 | // 二级 value 肯定不一样 --Object 不允许重复key 155 | initLSData = {...initLSData, ...localData} 156 | setState(initLSData, 'localStorage') 157 | } 158 | if (!_SS) return 159 | } 160 | 161 | if (_SS) { 162 | let sessionData = dataHandle(_SS, mutation, state) 163 | // 要和以前的合并 164 | if (sessionData) { 165 | initSSData = {...initSSData, ...sessionData} 166 | setState(initSSData, 'sessionStorage') 167 | } 168 | return 169 | } 170 | !LS && !SS && setState(state, `${mode !== 'SS' ? 'localStorage' : 'sessionStorage'}`) 171 | }) 172 | } 173 | } 174 | 175 | export default createPersiste 176 | // module.exports = createPersiste -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | 2 | // rollup.config.js 3 | // import resolve from 'rollup-plugin-node-resolve'; 4 | import babel from 'rollup-plugin-babel'; 5 | import { terser } from 'rollup-plugin-terser' 6 | export default { 7 | input: './packages/index.js', 8 | output: { 9 | name: 'vue-savedata.umd', 10 | file: './lib/vue-savedata.umd.js', 11 | format: 'umd' 12 | }, 13 | plugins: [ 14 | babel({ 15 | exclude: 'node_modules/**', // 只编译我们的源代码 16 | runtimeHelpers: true 17 | }), 18 | terser({ 19 | compress: { 20 | pure_getters: true, 21 | unsafe: true, 22 | unsafe_comps: true, 23 | warnings: false 24 | } 25 | }) 26 | ] 27 | }; -------------------------------------------------------------------------------- /test/savedata.test.js: -------------------------------------------------------------------------------- 1 | /*global it expect window jest*/ 2 | /*eslint-disable*/ 3 | 4 | import createPersisted from './../packages' 5 | import Vue from 'vue' 6 | import Vuex from 'vuex' 7 | 8 | Vue.config.productionTip = false; 9 | Vue.use(Vuex); 10 | 11 | // 用 store._subscribers[0]({type: 'increment2'}, { module2:{count: 88888} }); 可以脱离 本地数 mock 数据 12 | // 没办法 全局使用 - 会被改掉 13 | 14 | const module1 = { 15 | 16 | state: { 17 | count100: 100, 18 | count200: 200 19 | }, 20 | mutations: { 21 | increment1(state) { 22 | state.count100++; 23 | }, 24 | decrement1(state) { 25 | state.count100--; 26 | } 27 | } 28 | } 29 | const module4 = { 30 | namespaced: true, 31 | state: { 32 | count100: 100, 33 | count200: 200 34 | }, 35 | mutations: { 36 | increment1(state) { 37 | state.count100++; 38 | }, 39 | decrement1(state) { 40 | state.count100--; 41 | } 42 | } 43 | } 44 | 45 | const module2 = { 46 | state: { 47 | count: 666 48 | }, 49 | mutations: { 50 | increment2(state) { 51 | state.count+=2; 52 | }, 53 | decrement2(state) { 54 | state.count-=2; 55 | } 56 | } 57 | } 58 | 59 | // 数组情况 和 base解析 60 | const module3 = { 61 | state: { 62 | one: 666, 63 | count: { 64 | c: 3 65 | } 66 | }, 67 | mutations: { 68 | increment3(state) { 69 | state.count+=3; 70 | }, 71 | decrement3(state) { 72 | state.count-=3; 73 | } 74 | } 75 | } 76 | function clear () { 77 | window.localStorage.clear() 78 | window.sessionStorage.clear() 79 | } 80 | 81 | // 二级 深度合并 82 | it("二级 深度合并", () => { 83 | 84 | window.localStorage.setItem('saveData', JSON.stringify({ 85 | module1: { 86 | one: 333, 87 | count: { 88 | a: 2 89 | } 90 | } 91 | })) 92 | const store = new Vuex.Store({ modules: { 93 | module1: module3 94 | } } ); 95 | store.replaceState = jest.fn() 96 | store.subscribe = jest.fn() 97 | const plugin = createPersisted( 98 | { 99 | LS: { 100 | // 新添属性3 101 | module: module3, 102 | storePath: "module1" 103 | } 104 | } 105 | ); 106 | plugin(store); 107 | expect(store.replaceState).toBeCalledWith( { 108 | module1: { 109 | one: 333, 110 | count: { 111 | a: 2, 112 | c: 3 113 | } 114 | } 115 | }); 116 | expect(store.subscribe).toBeCalled(); 117 | }); 118 | 119 | 120 | it("base64 解析是否正确", () => { 121 | var encode = (data) => { 122 | return window.btoa(encodeURIComponent(JSON.stringify(data))) 123 | } 124 | var decode = (data) => { 125 | return JSON.parse(decodeURIComponent(window.atob(data))) 126 | } 127 | expect(JSON.stringify(decode(encode({"name":"saveData"})))).toBe(JSON.stringify({"name":"saveData"})); 128 | }); 129 | 130 | 131 | 132 | it("初始化state和修改state存本地", () => { 133 | clear() 134 | window.localStorage.setItem('saveData', JSON.stringify({ 135 | module1: { 136 | count: 333 137 | } 138 | })) 139 | const store = new Vuex.Store({ modules: { 140 | module1 141 | } } ); 142 | store.replaceState = jest.fn(); 143 | store.subscribe = jest.fn(); 144 | const plugin = createPersisted( 145 | { 146 | LS: { 147 | module: module1, 148 | storePath: "module1" 149 | } 150 | } 151 | ); 152 | plugin(store); 153 | expect(store.replaceState).toBeCalledWith( { module1: { count: 333 , 154 | count100: 100, 155 | count200: 200 156 | } }); 157 | expect(store.subscribe).toBeCalled(); 158 | }); 159 | 160 | it("默认存全部", () => { 161 | clear() 162 | const all = { 163 | state: { 164 | count100: 100, 165 | count200: 200 166 | }, 167 | mutations: { 168 | increment1(state) { 169 | state.count100++; 170 | } 171 | } 172 | } 173 | const store = new Vuex.Store({ 174 | modules: { 175 | all 176 | }, state: { 177 | count: 1 178 | } 179 | }); 180 | const plugin = createPersisted({ 181 | mode: 'LS' 182 | }); 183 | plugin(store); 184 | store.commit("increment1") 185 | // store._subscribers[0]({type: 'increment1'}, { module1:{count: 56789} }); 186 | // store._subscribers[0]({type: 'increment1'}, { module1:{count: 56789, age: 18} }); 187 | expect(window.localStorage.getItem('saveData')).toBe( 188 | JSON.stringify({ 189 | count: 1, 190 | all: { 191 | count100: 101, 192 | count200: 200 193 | } 194 | }) 195 | ); 196 | }); 197 | 198 | it("默认取全部", () => { 199 | clear() 200 | const store = new Vuex.Store({ modules: { 201 | module1 202 | } } ); 203 | window.localStorage.setItem('saveData', JSON.stringify({ 204 | module1: { 205 | count: 666666 206 | } 207 | })) 208 | const plugin = createPersisted({ 209 | mode: 'LS' 210 | }); 211 | store.replaceState = jest.fn(); 212 | store.subscribe = jest.fn(); 213 | plugin(store); 214 | expect(store.replaceState).toBeCalledWith({ module1: {count: 666666, 215 | count100: 100, 216 | count200: 200 217 | }}); 218 | expect(store.subscribe).toBeCalled(); 219 | }); 220 | 221 | it(' LS更改状态 保存到本地', () => { 222 | clear() 223 | const store = new Vuex.Store({ modules: { 224 | module1, 225 | } } ); 226 | const plugin = createPersisted( 227 | { LS: 228 | { 229 | module: module1, 230 | storePath: "module1" 231 | } 232 | } 233 | ); 234 | 235 | plugin(store); 236 | store._subscribers[0]({type: 'increment1'}, { module1:{count: 6666} }); 237 | expect(window.localStorage.getItem('saveData')).toBe( 238 | JSON.stringify({ module1:{count: 6666} }) 239 | ); 240 | }); 241 | it(' LS更改状态 保存到本地开启namespaced', () => { 242 | clear() 243 | const store = new Vuex.Store({ modules: { 244 | module4, 245 | } } ); 246 | const plugin = createPersisted( 247 | { LS: 248 | { 249 | module: module4, 250 | storePath: "module4" 251 | } 252 | } 253 | ); 254 | 255 | plugin(store); 256 | store._subscribers[0]({type: 'module4/increment1'}, { module4:{count: 6666} }); 257 | expect(window.localStorage.getItem('saveData')).toBe( 258 | JSON.stringify({ module4:{count: 6666} }) 259 | ); 260 | }); 261 | 262 | 263 | it(' SS更改状态 保存到本地', () => { 264 | clear() 265 | const store = new Vuex.Store({ modules: { 266 | module2, 267 | } } ); 268 | const plugin = createPersisted( 269 | { SS: 270 | { 271 | module: module2, 272 | storePath: "module2" 273 | } 274 | } 275 | ); 276 | 277 | plugin(store); 278 | store._subscribers[0]({type: 'increment2'}, { module2:{count: 88888} }); 279 | // console.log(store.) 280 | expect(window.sessionStorage.getItem('saveData')).toBe( 281 | JSON.stringify({module2:{count: 88888}}) 282 | ); 283 | }); 284 | 285 | it(' SS,LS更改状态 保存到本地', () => { 286 | clear() 287 | const store = new Vuex.Store({ state:{ 288 | name: "saveData" 289 | }, 290 | modules: { 291 | module1, module2, user: module2 292 | }}); 293 | const plugin = createPersisted( 294 | { 295 | SS: [ 296 | { 297 | module: module2, 298 | storePath: "module2" 299 | }, 300 | { 301 | module: module2, 302 | storePath: "user" 303 | } 304 | ], 305 | LS: { 306 | module: module1, 307 | storePath: "module1" 308 | } 309 | } 310 | ); 311 | 312 | plugin(store); 313 | // console.dir(store) 314 | // 这边是直接调用 那个方法 到时候会传入正确的 state的 315 | store._subscribers[0]({type: 'increment2'}, {module2:{count: 88888},module1:{count: 6666},user:{age:18}}); 316 | store._subscribers[0]({type: 'increment1'}, {module2:{count: 88888},module1:{count: 6666},user:{age:18}}); 317 | expect(window.sessionStorage.getItem('saveData')).toBe( 318 | JSON.stringify({ module2:{count: 88888},user:{age:18} }) 319 | ); 320 | expect(window.localStorage.getItem('saveData')).toBe( 321 | JSON.stringify({ module1:{count: 6666} }) 322 | ); 323 | }); 324 | 325 | 326 | 327 | 328 | it('SS效验不通过module,取存全部', () => { 329 | 330 | window.localStorage.setItem('saveData', JSON.stringify({ 331 | module1: {count: 123456789} 332 | })) 333 | const store = new Vuex.Store({ modules: { 334 | module1 335 | } } ); 336 | 337 | store.replaceState = jest.fn(); 338 | store.subscribe = jest.fn(); 339 | const plugin = createPersisted( 340 | { SS: { 341 | module: module2, 342 | } 343 | } 344 | ); 345 | 346 | plugin(store); 347 | 348 | expect(store.replaceState).toBeCalledWith( {module1: { 349 | count: 123456789, 350 | count100: 100, 351 | count200: 200 352 | }}); 353 | expect(store.subscribe).toBeCalled(); 354 | }); 355 | 356 | it('LS效验不通过module,取存全部', () => { 357 | 358 | window.localStorage.setItem('saveData', JSON.stringify({ 359 | module1: {count: 123456789} 360 | })) 361 | const store = new Vuex.Store({ modules: { 362 | module1 363 | } } ); 364 | 365 | store.replaceState = jest.fn(); 366 | store.subscribe = jest.fn(); 367 | const plugin = createPersisted( 368 | { LS: { 369 | module: module2, 370 | } 371 | } 372 | ); 373 | 374 | plugin(store); 375 | 376 | expect(store.replaceState).toBeCalledWith( {module1: {count: 123456789, 377 | count100: 100, 378 | count200: 200}}); 379 | expect(store.subscribe).toBeCalled(); 380 | }); 381 | 382 | // LS 不合法 383 | // 加密 解密 384 | 385 | 386 | 387 | it("在本地存不合法的数据 null", () => { 388 | 389 | window.localStorage.setItem('saveData', JSON.stringify(null)); 390 | 391 | const store = new Vuex.Store({ modules: { 392 | module1 393 | } } ); 394 | store.replaceState = jest.fn(); 395 | store.subscribe = jest.fn(); 396 | 397 | const plugin = createPersisted( 398 | { LS: 399 | { 400 | module: module1, 401 | storePath: "module1" 402 | } 403 | } 404 | ); 405 | plugin(store); 406 | expect(store.replaceState).not.toBeCalled(); 407 | expect(store.subscribe).toBeCalled(); 408 | }) 409 | 410 | 411 | it("在本地存JSON不能解析数据 <不合法>", () => { 412 | 413 | window.localStorage.setItem('saveData', '<不合法>'); 414 | 415 | const store = new Vuex.Store({ modules: { 416 | module1 417 | } } ); 418 | store.replaceState = jest.fn(); 419 | store.subscribe = jest.fn(); 420 | 421 | const plugin = createPersisted( 422 | { LS: 423 | { 424 | module: module1, 425 | storePath: "module1" 426 | } 427 | } 428 | ); 429 | plugin(store); 430 | expect(store.replaceState).not.toBeCalled(); 431 | expect(store.subscribe).toBeCalled(); 432 | }) 433 | 434 | 435 | it('传入模块不合法', () => { 436 | clear() 437 | const store = new Vuex.Store({ modules: { 438 | a: 123 439 | }, 440 | state: { 441 | default: 'vue-savedata' 442 | } 443 | }); 444 | const plugin = createPersisted( 445 | { LS: 446 | { 447 | module: module4, 448 | storePath: "module4" 449 | } 450 | } 451 | ); 452 | 453 | plugin(store); 454 | store.commit('module4/increment1') 455 | expect(window.localStorage.getItem('saveData')).toBe(null); 456 | }); 457 | 458 | it('传入模块正常', () => { 459 | clear() 460 | const store = new Vuex.Store({ modules: { 461 | module4 462 | }, 463 | state: { 464 | default: 'vue-savedata' 465 | } 466 | }); 467 | const plugin = createPersisted( 468 | { LS: 469 | { 470 | module: module4, 471 | storePath: "module4" 472 | } 473 | } 474 | ); 475 | 476 | plugin(store); 477 | store.commit('module4/increment1') 478 | // 上面这是在干嘛 479 | expect(window.localStorage.getItem('saveData')).toBe( 480 | JSON.stringify({ module4:{count100: 101, count200: 200} }) 481 | ); 482 | }); 483 | --------------------------------------------------------------------------------