├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── test └── unit │ ├── runner.js │ ├── .eslintrc │ ├── index.js │ ├── karma.conf.js │ └── specs │ ├── persist.spec.js │ └── storage.spec.js ├── .travis.yml ├── .babelrc ├── .editorconfig ├── cli ├── release.sh └── build.js ├── README.md ├── package.json └── src └── index.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | dist/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "plato" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | *.bak 3 | *.log 4 | *.old 5 | *.out 6 | 7 | coverage 8 | dist 9 | node_modules 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | *.bak 3 | *.log 4 | *.old 5 | *.out 6 | 7 | cli 8 | coverage 9 | node_modules 10 | test 11 | -------------------------------------------------------------------------------- /test/unit/runner.js: -------------------------------------------------------------------------------- 1 | // midway for es6 style 2 | require('babel-register') 3 | 4 | module.exports = require('./karma.conf') 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "7" 5 | 6 | after_success: 7 | - npm i coveralls 8 | - cat ./coverage/*/lcov.info | coveralls 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0" 5 | ], 6 | "plugins": [ 7 | "add-module-exports", 8 | ["__coverage__", { "ignore": "test/" }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/unit/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc", 3 | "env": { 4 | "mocha": true 5 | }, 6 | "globals": { 7 | "sinon": false, 8 | "assert": false, 9 | "expect": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /cli/release.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | echo "Enter release version: " 3 | read VERSION 4 | 5 | read -p "Releasing $VERSION - are you sure? (y/n)" -n 1 -r 6 | echo # (optional) move to a new line 7 | if [[ $REPLY =~ ^[Yy]$ ]] 8 | then 9 | echo "Releasing $VERSION ..." 10 | 11 | # test 12 | npm run test 13 | 14 | # build 15 | rm -rf dist 16 | mkdir dist 17 | VERSION=$VERSION npm run build 18 | 19 | # commit 20 | git add -A 21 | git commit -m "build $VERSION" 22 | npm version $VERSION --message "bump $VERSION" 23 | 24 | # publish 25 | git push 26 | npm publish 27 | fi 28 | -------------------------------------------------------------------------------- /test/unit/index.js: -------------------------------------------------------------------------------- 1 | // --------------------------------------- 2 | // Test Environment Setup 3 | // --------------------------------------- 4 | import chai from 'chai' 5 | import sinonChai from 'sinon-chai' 6 | 7 | localStorage.clear() 8 | 9 | chai.use(sinonChai) 10 | 11 | global.assert = chai.assert 12 | global.expect = chai.expect 13 | 14 | // --------------------------------------- 15 | // Require Tests 16 | // --------------------------------------- 17 | // for use with karma-webpack-with-fast-source-maps 18 | const __karmaWebpackManifest__ = [] // eslint-disable-line 19 | const inManifest = path => ~__karmaWebpackManifest__.indexOf(path) 20 | 21 | // require all `**/*.spec.js` 22 | const testsContext = require.context('./', true, /\.spec\.js$/) 23 | 24 | // only run tests that have changed after the first pass. 25 | const testsToRun = testsContext.keys().filter(inManifest) 26 | ;(testsToRun.length ? testsToRun : testsContext.keys()).forEach(testsContext) 27 | 28 | // require all `src/**/*.js` (for isparta coverage reporting) 29 | const componentsContext = require.context('../../src/', true, /\.js$/) 30 | 31 | componentsContext.keys().forEach(componentsContext) 32 | -------------------------------------------------------------------------------- /test/unit/karma.conf.js: -------------------------------------------------------------------------------- 1 | import { argv } from 'yargs' 2 | 3 | const debug = require('debug')('app:karma') 4 | debug('Create configuration.') 5 | 6 | const karmaConfig = { 7 | basePath: '../../', // project root in relation to bin/karma.js 8 | files: [ 9 | './node_modules/phantomjs-polyfill/bind-polyfill.js', 10 | './node_modules/sinon/pkg/sinon.js', 11 | { 12 | pattern: './test/unit/index.js', 13 | watched: false, 14 | served: true, 15 | included: true 16 | } 17 | ], 18 | singleRun: !argv.watch, 19 | frameworks: ['mocha', 'es6-shim'], 20 | preprocessors: { 21 | 'test/unit/index.js': ['webpack', 'sourcemap'] 22 | }, 23 | reporters: ['mocha', 'coverage'], 24 | coverageReporter: { 25 | reporters: [ 26 | { type: 'text-summary' }, 27 | { type: 'lcov', dir: 'coverage' } 28 | ] 29 | }, 30 | browsers: ['PhantomJS'], 31 | webpack: { 32 | devtool: 'inline-source-map', 33 | resolve: ['.js'], 34 | module: { 35 | loaders: [ 36 | { 37 | test: /\.js$/, 38 | exclude: /node_modules/, 39 | loader: 'babel' 40 | } 41 | ] 42 | } 43 | }, 44 | webpackMiddleware: { 45 | noInfo: true 46 | } 47 | } 48 | 49 | export default cfg => cfg.set(karmaConfig) 50 | -------------------------------------------------------------------------------- /cli/build.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var rollup = require('rollup') 3 | var uglify = require('uglify-js') 4 | var buble = require('rollup-plugin-buble') 5 | var version = process.env.VERSION || require('../package.json').version 6 | 7 | var banner = 8 | '/*!\n' + 9 | ' * VUEX-LOCALSTORAGE v' + version + '\n' + 10 | ' * (c) ' + new Date().getFullYear() + ' crossjs\n' + 11 | ' * Released under the MIT License.\n' + 12 | ' */' 13 | 14 | rollup.rollup({ 15 | entry: 'src/index.js', 16 | plugins: [buble()] 17 | }) 18 | .then(function (bundle) { 19 | var code = bundle.generate({ 20 | format: 'cjs', 21 | banner 22 | }).code 23 | return write('dist/index.js', code).then(function () { 24 | return code 25 | }) 26 | }) 27 | .then(function (code) { 28 | var minified = banner + '\n' + uglify.minify(code, { 29 | fromString: true, 30 | output: { 31 | ascii_only: true 32 | } 33 | }).code 34 | return write('dist/index.min.js', minified) 35 | }) 36 | .catch(logError) 37 | 38 | function write (dest, code) { 39 | return new Promise(function (resolve, reject) { 40 | fs.writeFile(dest, code, function (err) { 41 | if (err) return reject(err) 42 | console.log(blue(dest) + ' ' + getSize(code)) 43 | resolve() 44 | }) 45 | }) 46 | } 47 | 48 | function getSize (code) { 49 | return (code.length / 1024).toFixed(2) + 'kb' 50 | } 51 | 52 | function logError (e) { 53 | console.log(e) 54 | } 55 | 56 | function blue (str) { 57 | return '\x1b[1m\x1b[34m' + str + '\x1b[39m\x1b[22m' 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VUEX-LOCALSTORAGE 2 | 3 | > :dvd: Persist Vuex state with expires by localStorage or some else storage. 4 | 5 | [![Travis](https://img.shields.io/travis/crossjs/vuex-localstorage.svg?style=flat-square)](https://travis-ci.org/crossjs/vuex-localstorage) 6 | [![Coveralls](https://img.shields.io/coveralls/crossjs/vuex-localstorage.svg?style=flat-square)](https://coveralls.io/github/crossjs/vuex-localstorage) 7 | [![dependencies](https://david-dm.org/crossjs/vuex-localstorage.svg?style=flat-square)](https://david-dm.org/crossjs/vuex-localstorage) 8 | [![devDependency Status](https://david-dm.org/crossjs/vuex-localstorage/dev-status.svg?style=flat-square)](https://david-dm.org/crossjs/vuex-localstorage?type=dev) 9 | [![NPM version](https://img.shields.io/npm/v/vuex-localstorage.svg?style=flat-square)](https://npmjs.org/package/vuex-localstorage) 10 | 11 | ## Usage 12 | 13 | ``` js 14 | import { Store } from 'vuex' 15 | import createPersist from 'vuex-localstorage' 16 | 17 | new Store({ 18 | plugins: [createPersist({ 19 | namespace: 'namespace-for-state', 20 | initialState: {}, 21 | // ONE_WEEK 22 | expires: 7 * 24 * 60 * 60 * 1e3 23 | })] 24 | } 25 | ``` 26 | 27 | [Live Example at PLATO](https://github.com/platojs/plato/blob/master/src/modules/persist/index.js) 28 | 29 | ## Development Setup 30 | 31 | ``` bash 32 | # install deps 33 | npm install 34 | 35 | # build dist files 36 | npm run build 37 | 38 | # lint & run all tests 39 | npm test 40 | 41 | # run unit tests only 42 | npm run unit 43 | ``` 44 | 45 | ## Special Thanks 46 | 47 | [vuex-persistedstate](https://github.com/robinvdvleuten/vuex-persistedstate) 48 | 49 | ## License 50 | 51 | [MIT](http://opensource.org/licenses/MIT) 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuex-localstorage", 3 | "version": "1.0.0", 4 | "description": ":dvd: Persist Vuex state with expires by localStorage or some else storage.", 5 | "main": "dist/index.js", 6 | "jsnext:main": "src/index.js", 7 | "scripts": { 8 | "build": "node --harmony ./cli/build", 9 | "lint": "eslint --max-warnings 10 .", 10 | "lint:fix": "npm run lint -- --fix", 11 | "unit": "karma start ./test/unit/runner.js", 12 | "unit:dev": "karma start ./test/unit/runner.js --watch", 13 | "test": "npm run lint && npm run unit", 14 | "release": "bash ./cli/release.sh" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/crossjs/vuex-localstorage.git" 19 | }, 20 | "keywords": [ 21 | "expires", 22 | "localstorage", 23 | "persist", 24 | "vuex" 25 | ], 26 | "author": "crossjs", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/crossjs/vuex-localstorage/issues" 30 | }, 31 | "homepage": "https://github.com/crossjs/vuex-localstorage#readme", 32 | "devDependencies": { 33 | "babel-loader": "^6.2.7", 34 | "babel-plugin-__coverage__": "^11.0.0", 35 | "babel-plugin-add-module-exports": "^0.2.1", 36 | "babel-polyfill": "^6.16.0", 37 | "babel-preset-es2015": "^6.18.0", 38 | "babel-preset-stage-0": "^6.16.0", 39 | "babel-register": "^6.18.0", 40 | "chai": "^3.5.0", 41 | "cross-spawn": "^4.0.2", 42 | "eslint": "^3.9.0", 43 | "eslint-config-plato": "^0.0.1", 44 | "eslint-loader": "^1.6.0", 45 | "karma": "^1.3.0", 46 | "karma-coverage": "^1.1.1", 47 | "karma-es6-shim": "^1.0.0", 48 | "karma-mocha": "^1.2.0", 49 | "karma-mocha-reporter": "^2.2.0", 50 | "karma-phantomjs-launcher": "^1.0.2", 51 | "karma-sourcemap-loader": "^0.3.7", 52 | "karma-webpack": "^1.8.0", 53 | "mocha": "^3.1.2", 54 | "phantomjs-polyfill": "^0.0.2", 55 | "phantomjs-prebuilt": "^2.1.13", 56 | "rollup": "^0.36.3", 57 | "rollup-plugin-buble": "^0.14.0", 58 | "sinon": "^1.17.7", 59 | "sinon-chai": "^2.8.0", 60 | "uglify-js": "^2.7.4", 61 | "vue": "^2.2.6", 62 | "vuex": "^2.2.1", 63 | "webpack": "^1.13.3", 64 | "yargs": "^6.3.0" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/unit/specs/persist.spec.js: -------------------------------------------------------------------------------- 1 | import createPersist from '../../../src' 2 | 3 | import Vue from 'vue' 4 | import Vuex, { Store } from 'vuex' 5 | 6 | Vue.use(Vuex) 7 | 8 | let index = 0 9 | 10 | function setItem (namespace, value) { 11 | localStorage.setItem(`${namespace}-default`, JSON.stringify({ 12 | value: JSON.stringify(value), 13 | expires: 0 14 | })) 15 | } 16 | 17 | function getItem (namespace) { 18 | return JSON.parse(JSON.parse(localStorage.getItem(`${namespace}-default`)).value) 19 | } 20 | 21 | localStorage.clear() 22 | 23 | it('replaces store\'s state and subscribes to changes when initializing', () => { 24 | const namespace = `vuex-${++index}` 25 | setItem(namespace, { persisted: 'json' }) 26 | 27 | const store = new Store({ 28 | state: { original: 'state' } 29 | }) 30 | sinon.spy(store, 'replaceState') 31 | sinon.spy(store, 'subscribe') 32 | 33 | createPersist({ namespace })(store) 34 | 35 | assert(store.replaceState.calledWith({ original: 'state', persisted: 'json' })) 36 | assert(store.subscribe.called) 37 | }) 38 | 39 | it('respects nested values when it replaces store\'s state on initializing', () => { 40 | const namespace = `vuex-${++index}` 41 | setItem(namespace, { 42 | nested: { persisted: 'json' } 43 | }) 44 | 45 | const store = new Store({ 46 | state: { 47 | nested: { original: 'state' } 48 | } 49 | }) 50 | sinon.spy(store, 'replaceState') 51 | sinon.spy(store, 'subscribe') 52 | 53 | createPersist({ namespace })(store) 54 | 55 | assert(store.replaceState.calledWith({ 56 | nested: { persisted: 'json' } 57 | })) 58 | assert(store.subscribe.called) 59 | }) 60 | 61 | it('persist the changed parial state back to serialized JSON', () => { 62 | const namespace = `vuex-${++index}` 63 | const store = new Store({ 64 | state: {} 65 | }) 66 | sinon.spy(store, 'replaceState') 67 | sinon.spy(store, 'subscribe') 68 | 69 | createPersist({ namespace, paths: ['changed'] })(store) 70 | 71 | const subscriber = store.subscribe.getCall(0).args[0] 72 | subscriber('mutation', { changed: 'state' }) 73 | 74 | assert.deepEqual(getItem(namespace), { changed: 'state' }) 75 | }) 76 | 77 | it('persist the changed partial state back to serialized JSON under a configured key', () => { 78 | const store = new Store({ 79 | state: {} 80 | }) 81 | sinon.spy(store, 'replaceState') 82 | sinon.spy(store, 'subscribe') 83 | 84 | createPersist({ namespace: 'custom', paths: ['changed'] })(store) 85 | 86 | const subscriber = store.subscribe.getCall(0).args[0] 87 | subscriber('mutation', { changed: 'state' }) 88 | 89 | assert.deepEqual(getItem('custom'), { changed: 'state' }) 90 | }) 91 | 92 | it('persist the changed full state back to serialized JSON when no paths are given', () => { 93 | const namespace = `vuex-${++index}` 94 | const store = new Store({ 95 | state: {} 96 | }) 97 | sinon.spy(store, 'replaceState') 98 | sinon.spy(store, 'subscribe') 99 | 100 | createPersist({ namespace })(store) 101 | 102 | const subscriber = store.subscribe.getCall(0).args[0] 103 | subscriber('mutation', { changed: 'state' }) 104 | 105 | assert.deepEqual(getItem(namespace), { changed: 'state' }) 106 | }) 107 | 108 | it('uses the configured reducer when persisting the state', () => { 109 | const store = new Store({ 110 | state: {} 111 | }) 112 | sinon.spy(store, 'replaceState') 113 | sinon.spy(store, 'subscribe') 114 | 115 | const customReducer = sinon.spy() 116 | 117 | const plugin = createPersist({ paths: ['custom'], reducer: customReducer }) 118 | plugin(store) 119 | 120 | const subscriber = store.subscribe.getCall(0).args[0] 121 | subscriber('mutation', { custom: 'value' }) 122 | 123 | assert(customReducer.calledWith({ custom: 'value' }, ['custom'])) 124 | }) 125 | -------------------------------------------------------------------------------- /test/unit/specs/storage.spec.js: -------------------------------------------------------------------------------- 1 | import { createStorage } from '../../../src' 2 | 3 | localStorage.clear() 4 | 5 | describe('getStorage', () => { 6 | it('init', () => { 7 | const storage = createStorage() 8 | expect(storage.get()).to.eql({}) 9 | }) 10 | 11 | it('with initialState', () => { 12 | const storage = createStorage({ 13 | initialState: { x: 1 } 14 | }) 15 | expect(storage.get()).to.eql({ x: 1 }) 16 | }) 17 | 18 | it('key should default to `default`', () => { 19 | const storage = createStorage({ 20 | initialState: { x: 1 } 21 | }) 22 | expect(storage.get()).to.eql({ x: 1 }) 23 | expect(storage.get('default')).to.eql({ x: 1 }) 24 | }) 25 | }) 26 | 27 | describe('setStorage', () => { 28 | it('with initialState', () => { 29 | const storage = createStorage({ 30 | initialState: { z: 1 } 31 | }) 32 | storage.set({ y: 1 }) 33 | expect(storage.get()).to.eql({ y: 1, z: 1 }) 34 | }) 35 | 36 | it('key should default to `default`', () => { 37 | const storage = createStorage({ 38 | initialState: { z: 1 } 39 | }) 40 | storage.set({ y: 1 }) 41 | expect(storage.get()).to.eql({ y: 1, z: 1 }) 42 | }) 43 | 44 | it('override initialState', () => { 45 | const storage = createStorage({ 46 | initialState: { z: 1 } 47 | }) 48 | storage.set({ z: 0 }) 49 | expect(storage.get()).to.eql({ z: 0 }) 50 | storage.set({ z: null }) 51 | expect(storage.get()).to.eql({ z: null }) 52 | }) 53 | 54 | describe('with key', () => { 55 | it('with initialState', () => { 56 | const storage = createStorage({ 57 | initialState: { z: 1 } 58 | }) 59 | storage.set('key1', { y: 1 }) 60 | expect(storage.get('key1')).to.eql({ y: 1 }) 61 | expect(storage.get()).to.eql({ z: 1 }) 62 | }) 63 | 64 | it('override initialState', () => { 65 | const storage = createStorage({ 66 | initialState: { z: 1 } 67 | }) 68 | expect(storage.get()).to.eql({ z: 1 }) 69 | storage.set('key2', { z: 0 }) 70 | expect(storage.get('key2')).to.eql({ z: 0 }) 71 | storage.set('key2', { z: null }) 72 | expect(storage.get('key2')).to.eql({ z: null }) 73 | storage.set('key3', { z: '' }) 74 | expect(storage.get('key3')).to.eql({ z: '' }) 75 | }) 76 | }) 77 | }) 78 | 79 | describe('customize', () => { 80 | it('with sessionStorage', () => { 81 | const storage = createStorage({ 82 | initialState: { z: 1 }, 83 | provider: sessionStorage 84 | }) 85 | storage.set({ y: 1 }) 86 | expect(storage.get()).to.eql({ y: 1, z: 1 }) 87 | }) 88 | 89 | it('serialize/deserialize', () => { 90 | const value = {} 91 | const storage = createStorage({ 92 | initialState: { z: 1 }, 93 | provider: { 94 | getItem (key) { 95 | return value[key] 96 | }, 97 | setItem (key, val) { 98 | value[key] = val 99 | } 100 | }, 101 | serialize: val => val, 102 | deserialize: val => val 103 | }) 104 | storage.set({ z: 0 }) 105 | expect(storage.get()).to.eql({ z: 0 }) 106 | storage.set({ z: null }) 107 | expect(storage.get()).to.eql({ z: null }) 108 | }) 109 | 110 | it('with expires', done => { 111 | const storage = createStorage({ 112 | initialState: { z: 1 }, 113 | // one second 114 | expires: 1000 115 | }) 116 | storage.set({ y: 1 }) 117 | expect(storage.get()).to.eql({ y: 1, z: 1 }) 118 | setTimeout(() => { 119 | expect(storage.get()).to.eql({ z: 1 }) 120 | done() 121 | }, 1000) 122 | }) 123 | 124 | it('with merge', done => { 125 | const storage = createStorage({ 126 | initialState: { z: 1 }, 127 | merge (a, b) { 128 | // always return storaged 129 | return b 130 | }, 131 | // one second 132 | expires: 1000 133 | }) 134 | storage.set({ y: 1 }) 135 | expect(storage.get()).to.eql({ y: 1 }) 136 | setTimeout(() => { 137 | // expired to undefined 138 | expect(storage.get()).to.be.undifined 139 | done() 140 | }, 1000) 141 | }) 142 | }) 143 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | let index = Date.now() 2 | 3 | /** 4 | * 创建数据存储器 5 | * 6 | * @method createStorage 7 | * @param {object} [param] 配置项 8 | * @param {string} [param.namespace] 命名空间,用于数据隔离 9 | * @param {object} [param.initialState={}] 默认数据 10 | * @param {object} [param.provider=localStorage] 基础存储器,必须拥有 getItem 与 setItem 方法 11 | * @param {function} [param.serialize=JSON.stringify] 序列化方法 12 | * @param {function} [param.deserialize=JSON.parse] 反序列化方法 13 | * @param {number} [param.expires=0] 过期时间,以毫秒为单位 14 | * @param {function} [param.merge] 用于数据合并的方法 15 | * @return {object} 数据存储器 16 | * @example 17 | * const storage = createStorage() 18 | * storage.set({ x: 1 }) 19 | * storage.get() // return { x: 1 } 20 | * storage.set('my-key', { x: 1 }) 21 | * storage.get('my-key') // return { x: 1 } 22 | */ 23 | export function createStorage ({ 24 | namespace, 25 | initialState = {}, 26 | provider = localStorage, 27 | serialize = JSON.stringify, 28 | deserialize = JSON.parse, 29 | expires = 0, // never expires 30 | merge = defaultMerge 31 | } = {}) { 32 | if (!namespace) { 33 | namespace = `vuex-${++index}` 34 | } 35 | 36 | return { 37 | /** 38 | * Get the value of the provided key 39 | * 40 | * @method get 41 | * @param {string} [key='default'] key 42 | * @return {object} A plainobject value 43 | */ 44 | get (key = 'default') { 45 | let state 46 | 47 | try { 48 | const { value, expires } = deserialize(provider.getItem(`${namespace}-${key}`)) 49 | if (expires === 0 || expires > Date.now()) { 50 | // always a plain object 51 | state = deserialize(value) 52 | } 53 | } catch (e) { 54 | // console.log(e) 55 | } 56 | 57 | return merge(key === 'default' ? initialState : initialState[key], state) 58 | }, 59 | 60 | /** 61 | * Add or update a key with a plainobject value 62 | * 63 | * @method set 64 | * @param {string} [key='default'] key 65 | * @param {object} value A plainobject value 66 | */ 67 | set (key, value) { 68 | if (arguments.length === 1) { 69 | value = key 70 | key = 'default' 71 | } 72 | try { 73 | provider.setItem(`${namespace}-${key}`, serialize({ 74 | value: serialize(value), 75 | expires: expires ? expires + Date.now() : expires 76 | })) 77 | } catch (e) { 78 | // console.log(e) 79 | } 80 | } 81 | } 82 | } 83 | 84 | /** 85 | * 创建 Vuex 持久化插件 86 | * 87 | * @method createPersist 88 | * @param {object} [param] 配置项 89 | * @param {string} [param.namespace] 命名空间,用于数据隔离 90 | * @param {object} [param.initialState] 默认数据 91 | * @param {object} [param.provider] 基础存储器,必须拥有 getItem 与 setItem 方法 92 | * @param {function} [param.serialize] 序列化方法 93 | * @param {function} [param.deserialize] 反序列化方法 94 | * @param {number} [param.expires] 过期时间,以毫秒为单位 95 | * @param {function} [param.merge] 用于数据合并的方法 96 | * @param {function} [param.reducer] 用于数据过滤的方法 97 | * @param {string[]} [param.paths = []] 需要持久化的数据的路径,state 的 keys。 98 | * 如需处理更多层级,可以配合自定义 reducer 实现 99 | * @return {function(store: object)} 插件函数 100 | * @example 101 | * const plugin = createPersist() 102 | * const store = new Vuex.Store({ 103 | * // ... 104 | * plugins: [plugin] 105 | * }) 106 | */ 107 | export default function createPersist ({ 108 | namespace, 109 | initialState, 110 | provider, 111 | serialize, 112 | deserialize, 113 | expires, 114 | merge = defaultMerge, 115 | reducer = defaultReducer, 116 | paths = [] 117 | } = {}) { 118 | return store => { 119 | const storage = createStorage({ 120 | namespace, 121 | initialState, 122 | provider, 123 | serialize, 124 | deserialize, 125 | merge, 126 | expires 127 | }) 128 | 129 | store.replaceState( 130 | merge(store.state, storage.get()) 131 | ) 132 | 133 | store.subscribe((mutation, state) => { 134 | storage.set(reducer(state, paths)) 135 | }) 136 | } 137 | } 138 | 139 | function defaultMerge (...args) { 140 | return Object.assign({}, ...args) 141 | } 142 | 143 | function defaultReducer (state, paths) { 144 | return paths.length === 0 145 | ? state 146 | : paths.reduce((substate, path) => { 147 | if (state.hasOwnProperty(path)) { 148 | return Object.assign(substate, { [path]: state[path] }) 149 | } 150 | return substate 151 | }, {}) 152 | } 153 | --------------------------------------------------------------------------------