├── .babelrc
├── .browserslistrc
├── .eslintignore
├── .eslintrc.json
├── .github
└── FUNDING.yml
├── .gitignore
├── .htmlhintrc
├── .npmignore
├── .npmrc_cn
├── LICENSE
├── README.md
├── config.js
├── dev_favicon.png
├── dev_fit.html
├── dev_index_jump.html
├── dist
├── history-navigation-vue.css
├── history-navigation-vue.js
├── history-navigation-vue.min.css
└── history-navigation-vue.min.js
├── examples
├── app.js
├── config.js
├── create_app.js
├── css
│ ├── docs.css
│ └── transition-mask.css
├── index.ejs
├── pages
│ ├── _inner-show.vue
│ ├── api.vue
│ ├── detail.vue
│ ├── index.vue
│ ├── index_modal.vue
│ ├── list.vue
│ ├── list_modal.vue
│ ├── me.vue
│ ├── modal.vue
│ ├── nav.vue
│ ├── not-found.vue
│ ├── tra-performance-detail.vue
│ └── tra-performance.vue
├── root.vue
└── tip.vue
├── examples_vue3
├── .npmrc
├── package-lock.json
├── package.json
└── vue3_project
│ ├── .gitignore
│ ├── .npmrc
│ ├── README.md
│ ├── babel.config.js
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ └── index.html
│ └── src
│ ├── App.vue
│ ├── assets
│ └── logo.png
│ ├── components
│ └── HelloWorld.vue
│ └── main.js
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── script
├── after_webpack_build.js
├── common.js
├── get-static-map.js
├── get_config.js
└── pro.js
├── server
├── preview.js
├── preview_docs.js
├── preview_ex.js
└── setup.js
├── src
├── bundle.js
├── cmpt
│ ├── common.js
│ ├── default-not-found.vue
│ ├── navigation-controller.vue
│ ├── navigator.vue
│ ├── page.vue
│ ├── page_main.vue
│ ├── tab-bar-controller.vue
│ └── tab-bar.vue
├── constant.js
├── css
│ └── style.css
├── fit_vue.js
├── h_nav
│ └── h_nav.js
├── install.js
├── mixin
│ └── show-hide-mixin.js
├── navigator
│ ├── history.js
│ ├── libs
│ │ ├── back.js
│ │ ├── bae.js
│ │ └── modal.js
│ ├── native.js
│ ├── navigator.js
│ ├── state-key.js
│ └── url.js
└── util.js
├── ssr
├── conf.js
├── front-end
│ ├── app.js
│ └── entry-server.js
├── ssr-build.js
├── ssr-context-config.js
├── ssr-render.js
└── webpack.config.js
├── test
├── e2e
│ ├── .placeholder
│ └── history.js
└── unit
│ └── util.js
├── webpack.build.config.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/env"],
3 | "plugins": [
4 | "@babel/plugin-transform-runtime",
5 | "@babel/plugin-syntax-dynamic-import",
6 | "@babel/plugin-syntax-import-meta",
7 | "@babel/plugin-proposal-class-properties",
8 | "@babel/plugin-proposal-json-strings",
9 | [
10 | "@babel/plugin-proposal-decorators",
11 | {
12 | "legacy": true
13 | }
14 | ],
15 | "@babel/plugin-proposal-function-sent",
16 | "@babel/plugin-proposal-export-namespace-from",
17 | "@babel/plugin-proposal-numeric-separator",
18 | "@babel/plugin-proposal-throw-expressions",
19 | "@babel/plugin-proposal-export-default-from",
20 | "@babel/plugin-proposal-logical-assignment-operators",
21 | "@babel/plugin-proposal-optional-chaining",
22 | [
23 | "@babel/plugin-proposal-pipeline-operator",
24 | {
25 | "proposal": "minimal"
26 | }
27 | ],
28 | "@babel/plugin-proposal-nullish-coalescing-operator",
29 | "@babel/plugin-proposal-do-expressions",
30 | "@babel/plugin-proposal-function-bind"
31 | ],
32 | "env": {
33 | "production": {
34 | "plugins": [
35 | ["transform-remove-console", { "exclude": [ "error", "warn"] }]
36 | ]
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | ie >= 10
2 | android > 4.4.4
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | webpack*
2 | node_modules
3 | script
4 | dist
5 | build
6 | server
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "commonjs": true,
5 | "es6": true
6 | },
7 | "extends": ["eslint:recommended", "plugin:vue/recommended", "plugin:vue/strongly-recommended", "plugin:vue/essential"],
8 |
9 | "parserOptions": {
10 | "parser": "@babel/eslint-parser",
11 | "ecmaVersion": 2018,
12 | "sourceType": "module"
13 | },
14 | "plugins": [
15 | "vue"
16 | ],
17 | "rules": {
18 | "indent": [
19 | 0,
20 | 2,
21 | { "SwitchCase": 1 }
22 | ],
23 | "no-extra-semi": "warn",
24 | "no-unused-vars": "warn",
25 | "no-trailing-spaces": 0,
26 | "no-case-declarations": "warn",
27 | "no-unreachable": "warn",
28 | "no-multiple-empty-lines": 0,
29 | "prefer-const": 0,
30 | "space-infix-ops": "warn",
31 | "camelcase": 0,
32 | "no-console": 0,
33 | "linebreak-style": 0,
34 | "quotes": 0,
35 | "semi": 0,
36 |
37 | "vue/require-prop-types": 0,
38 | "vue/no-reserved-keys": 0,
39 | "vue/order-in-components": 0,
40 | "vue/require-default-prop": 0,
41 | "vue/return-in-computed-property": 0,
42 | "vue/max-attributes-per-line": 0,
43 | "vue/attributes-order": 0,
44 | "vue/singleline-html-element-content-newline": 0,
45 | "vue/mustache-interpolation-spacing": 0,
46 | "vue/html-self-closing": 0,
47 | "vue/html-indent": 0,
48 | "vue/multiline-html-element-content-newline": 0,
49 | "vue/no-multi-spaces": 0,
50 | "vue/html-quotes": 0,
51 | "vue/attribute-hyphenation": 0,
52 | "vue/html-closing-bracket-newline": 0,
53 | "vue/multi-word-component-names": 0,
54 | "vue/component-tags-order": 0,
55 | "vue/first-attribute-linebreak": 0
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | # patreon: # Replace with a single Patreon username
5 | # open_collective: # Replace with a single Open Collective username
6 | # ko_fi: # Replace with a single Ko-fi username
7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | # liberapay: # Replace with a single Liberapay username
10 | # issuehunt: # Replace with a single IssueHunt username
11 | # otechie: # Replace with a single Otechie username
12 | custom: ['https://www.paypal.me/hezedu']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /*.db
2 | /node_modules
3 | /examples_vue3/node_modules
4 | /examples_vue3/vue3_project/node_modules
5 | /package-lock.json
6 | /.vscode
7 | /.npmrc
8 | /dev_dist
9 |
--------------------------------------------------------------------------------
/.htmlhintrc:
--------------------------------------------------------------------------------
1 | {
2 | "tagname-lowercase": true,
3 | "attr-lowercase": true,
4 | "attr-value-double-quotes": true,
5 | "doctype-first": false,
6 | "id-unique": true,
7 | "space-tab-mixed-disabled": "space"
8 | }
9 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /test.js
2 | /src
3 | /dist/dev
4 | /script
5 | /.babelrc
6 | /.eslintignore
7 | /.eslintrc.json
8 | /.htmlhintrc
9 | /plan.txt
10 | /static-server.js
11 | /webpack.config.js
12 | /index.html
13 | /.vscode
14 | /.setup.js
15 |
--------------------------------------------------------------------------------
/.npmrc_cn:
--------------------------------------------------------------------------------
1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
2 | phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
3 | electron_mirror=https://npm.taobao.org/mirrors/electron/
4 | registry=https://registry.npm.taobao.org
5 | chromedriver_cdnurl=https://npm.taobao.org/mirrors/chromedriver
6 |
7 | detect_chromedriver_version=true
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Du Wei
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 all
13 | 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 THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ## History Navigation Vue
3 | The native-like Navigation for Web apps.
4 |
5 | ### Documentation
6 | To check out [live examples](https://hezedu.github.io/history-navigation-vue/examples.html) and docs, visit:
7 |
8 | [English docs](https://hezedu.github.io/history-navigation-vue/).
9 |
10 | [中文版文档](https://hezedu.github.io/history-navigation-vue/zh/)
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | port: 8003,
3 | previewPort: 8004,
4 | previewDocsPort: 8005,
5 | previewExPort: 8006,
6 | vue3Port: 8007,
7 | title: 'hello'
8 | };
9 |
--------------------------------------------------------------------------------
/dev_favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hezedu/history-navigation-vue/b68b1c59abb8dd4419d01cfc16ffc12b1cc9f311/dev_favicon.png
--------------------------------------------------------------------------------
/dev_fit.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
67 | hello
68 |
69 |
70 |
80 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/dev_index_jump.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | hello
9 |
10 |
11 |
12 | 去首页
13 |
14 |
15 |
--------------------------------------------------------------------------------
/dist/history-navigation-vue.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * history-navigation-vue v1.5.0
3 | * (c) 2021 hezedu
4 | * @license MIT
5 | */
6 | .h-nav-ctrler{
7 | width: 100%;
8 | height: 100%;
9 | overflow: hidden;
10 | position: relative;
11 | }
12 |
13 | .h-nav-page, .h-nav-tab-page {
14 | position: absolute;
15 | left: 0;
16 | top: 0;
17 | height: 100%;
18 | width: 100%;
19 | }
20 |
21 | .h-nav-page-main {
22 | background-color: #fff;
23 | position: relative;
24 | -webkit-overflow-scrolling: touch;
25 | overflow: auto;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
30 | .h-nav-tabs-ctrler{
31 | position: absolute;
32 | left: 0;
33 | top: 0;
34 | bottom: 0;
35 | right: 0;
36 | overflow: hidden;
37 | }
38 |
39 |
40 |
41 | .h-nav-page-handle,
42 | .h-nav-tab-page-handle{
43 | width: 0!important;
44 | height: 0!important;
45 | border: 0!important;
46 | margin: 0!important;
47 | padding: 0!important;
48 | box-shadow: none!important;
49 | outline: none!important;
50 | position: static!important;
51 | }
52 |
53 | .h-nav-page-enter-active,
54 | .h-nav-page-leave-active,
55 | .h-nav-tab-page-enter-active,
56 | .h-nav-tab-page-leave-active{
57 | transition: inherit;
58 | animation: inherit;
59 | }
60 |
61 | .h-nav-tab-pages-wrap {
62 | transition: inherit;
63 | animation: inherit;
64 | position: static!important;
65 | transition-property: none;
66 | animation-name: none;
67 | }
68 |
69 | .h-nav-tab-page-handle{
70 | transition: inherit;
71 | animation: inherit;
72 | }
73 | .h-nav-tab-behavior-_inherit{
74 | transition: inherit!important;
75 | animation: inherit!important;
76 | }
77 |
78 | .h-nav-modal {
79 | position: absolute;
80 | top: 0;
81 | left: 0;
82 | width: 100%;
83 | height: 100%;
84 | color: #fff;
85 | box-sizing: border-box;
86 | /* background-color: rgba(0, 0, 0, .5); */
87 | }
88 |
89 | /* .h-nav-tab-page .h-nav-modal {
90 | z-index: 2000;
91 | } */
92 | .h-nav-page-has-modal {
93 | overflow: hidden;
94 | }
95 |
96 | .h-nav-behavior-push > .h-nav-transition,
97 | .h-nav-behavior-back > .h-nav-transition,
98 | .h-nav-behavior-replace > .h-nav-transition{
99 | transition: all .3s ease;
100 | }
101 |
102 | .h-nav-behavior-push > .h-nav-transition > .h-nav-page-enter,
103 | .h-nav-behavior-back > .h-nav-transition > .h-nav-page-leave-to {
104 | transform: translateX(100%);
105 | }
106 |
107 | .h-nav-behavior-replace > .h-nav-transition > .h-nav-page-enter {
108 | transform: translateY(100%);
109 | }
110 |
111 | /* ---------------- behavior push/back start ---------------- */
112 | /* .h-nav-behavior-push > .h-nav-transition,
113 | .h-nav-behavior-back > .h-nav-transition,
114 | .h-nav-behavior-replace > .h-nav-transition{
115 | transition: all .3s ease;
116 | }
117 |
118 | .h-nav-behavior-push > .h-nav-transition > .h-nav-page-enter,
119 | .h-nav-behavior-back > .h-nav-transition > .h-nav-page-leave-to,
120 | .h-nav-behavior-replace > .h-nav-transition > .h-nav-page-enter {
121 | transform: translateX(100%);
122 | } */
123 | /* ---------------- behavior push/back end ---------------- */
124 |
125 | .h-nav-tabbar{
126 | box-sizing: border-box;
127 | border-top: 1px solid #c7c7c7;
128 | display: -ms-flexbox;
129 | display: flex;
130 | -ms-flex-pack: distribute;
131 | justify-content: space-around;
132 | background-color: #eee;
133 | }
134 | .h-nav-tabbar {
135 | height: 48px;
136 | position: absolute;
137 | bottom: 0;
138 | left: 0;
139 | width: 100%;
140 | z-index: 0;
141 | }
142 | .h-nav-tab-page {
143 | height: calc(100% - 48px);
144 | }
145 | .h-nav-tab-page > .h-nav-modal {
146 | height: calc(100% + 48px);
147 | }
148 | .h-nav-tab{
149 | -ms-flex-positive: 1;
150 | flex-grow: 1;
151 | display: -ms-flexbox;
152 | display: flex;
153 | -ms-flex-align: center;
154 | align-items: center;
155 | -ms-flex-pack: center;
156 | justify-content: center;
157 | text-decoration: none;
158 | color: #333;
159 | -ms-flex-direction: column;
160 | flex-direction: column;
161 | }
162 | .h-nav-tab.h-nav-active{
163 | color: #3eaf7c;
164 | }
165 | .h-nav-tab-icon{
166 | font-size: 26px;
167 | width: 26px;
168 | height: 26px;
169 | line-height: 26px;
170 | text-align: center;
171 | background-repeat: no-repeat;
172 | background-size: contain;
173 | background-position: center center;
174 | margin-bottom: 4px;
175 | }
176 | .h-nav-tab-text{
177 | font-size: 16px;
178 | line-height: 1;
179 | }
180 | .h-nav-tab-icon + .h-nav-tab-text{
181 | font-size: 12px;
182 | }
183 |
184 |
--------------------------------------------------------------------------------
/dist/history-navigation-vue.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * history-navigation-vue v1.5.0
3 | * (c) 2021 hezedu
4 | * @license MIT
5 | */
6 | .h-nav-ctrler{height:100%;overflow:hidden;position:relative;width:100%}.h-nav-page,.h-nav-tab-page{height:100%;left:0;position:absolute;top:0;width:100%}.h-nav-page-main{-webkit-overflow-scrolling:touch;background-color:#fff;height:100%;overflow:auto;position:relative;width:100%}.h-nav-tabs-ctrler{bottom:0;left:0;overflow:hidden;position:absolute;right:0;top:0}.h-nav-page-handle,.h-nav-tab-page-handle{border:0!important;box-shadow:none!important;height:0!important;margin:0!important;outline:none!important;padding:0!important;position:static!important;width:0!important}.h-nav-page-enter-active,.h-nav-page-leave-active,.h-nav-tab-page-enter-active,.h-nav-tab-page-leave-active,.h-nav-tab-pages-wrap{animation:inherit;transition:inherit}.h-nav-tab-pages-wrap{animation-name:none;position:static!important;transition-property:none}.h-nav-tab-page-handle{animation:inherit;transition:inherit}.h-nav-tab-behavior-_inherit{animation:inherit!important;transition:inherit!important}.h-nav-modal{box-sizing:border-box;color:#fff;height:100%;left:0;position:absolute;top:0;width:100%}.h-nav-page-has-modal{overflow:hidden}.h-nav-behavior-back>.h-nav-transition,.h-nav-behavior-push>.h-nav-transition,.h-nav-behavior-replace>.h-nav-transition{transition:all .3s ease}.h-nav-behavior-back>.h-nav-transition>.h-nav-page-leave-to,.h-nav-behavior-push>.h-nav-transition>.h-nav-page-enter{transform:translateX(100%)}.h-nav-behavior-replace>.h-nav-transition>.h-nav-page-enter{transform:translateY(100%)}.h-nav-tabbar{-ms-flex-pack:distribute;background-color:#eee;border-top:1px solid #c7c7c7;bottom:0;box-sizing:border-box;display:-ms-flexbox;display:flex;height:48px;justify-content:space-around;left:0;position:absolute;width:100%;z-index:0}.h-nav-tab-page{height:calc(100% - 48px)}.h-nav-tab-page>.h-nav-modal{height:calc(100% + 48px)}.h-nav-tab{-ms-flex-positive:1;-ms-flex-align:center;-ms-flex-pack:center;align-items:center;color:#333;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;flex-grow:1;justify-content:center;text-decoration:none}.h-nav-tab.h-nav-active{color:#3eaf7c}.h-nav-tab-icon{background-position:50%;background-repeat:no-repeat;background-size:contain;font-size:26px;height:26px;line-height:26px;margin-bottom:4px;text-align:center;width:26px}.h-nav-tab-text{font-size:16px;line-height:1}.h-nav-tab-icon+.h-nav-tab-text{font-size:12px}
--------------------------------------------------------------------------------
/dist/history-navigation-vue.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * history-navigation-vue v1.5.0
3 | * (c) 2021 hezedu
4 | * @license MIT
5 | */
6 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.historyNavigationVue=e():t.historyNavigationVue=e()}(self,(function(){return function(){"use strict";var t={d:function(e,a){for(var i in a)t.o(a,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:a[i]})},o:function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r:function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{plugin:function(){return Q},version:function(){return Z}});var a="_h_nav_page_show",i="_h_nav_page_hide",n="h-nav-page-",s="h-nav-page-not-found",o="_h_n_key",r="_h_n_modal_key",h="_H_NAV_BAE_MODAL_";function l(t,e,a,i,n,s,o,r){var h,l="function"==typeof t?t.options:t;if(e&&(l.render=e,l.staticRenderFns=a,l._compiled=!0),i&&(l.functional=!0),s&&(l._scopeId="data-v-"+s),o?(h=function(t){(t=t||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext)||"undefined"==typeof __VUE_SSR_CONTEXT__||(t=__VUE_SSR_CONTEXT__),n&&n.call(this,t),t&&t._registeredComponents&&t._registeredComponents.add(o)},l._ssrRegister=h):n&&(h=r?function(){n.call(this,(l.functional?this.parent:this).$root.$options.shadowRoot)}:n),h)if(l.functional){l._injectStyles=h;var c=l.render;l.render=function(t,e){return h.call(e),c(t,e)}}else{var u=l.beforeCreate;l.beforeCreate=u?[].concat(u,h):[h]}return{exports:t,options:l}}var c,u,p,_=l({name:"HistoryNavigationPage",provide:function(){return{$page:this}},props:{path:{type:String},title:String,isHome:Boolean,isTab:{type:Boolean,required:!0},tabIndex:Number,stateKey:Number,route:{type:Object,required:!0},isFirstLoad:{type:Boolean,required:!0},isActive:{type:Boolean,required:!0}},data:function(){return{isLoad:this.isFirstLoad}},watch:{isActive:function(){this._handleShowHide()}},methods:{_handleShowHide:function(){this.isActive?this._uniteEmit(a):this._uniteEmit(i)},_uniteEmit:function(t){this.$emit(t)},_uniteBeforeDestory:function(){this.isLoad&&(this.$options._tmp_is_before_destroy=!0,this.isActive&&this._uniteEmit(i),this.isLoad=!1)}},beforeCreate:function(){},mounted:function(){var t=this;this.isLoad?this._uniteEmit(a):setTimeout((function(){t.$options._tmp_is_before_destroy||(t.isLoad=!0,t.$nextTick((function(){t._uniteEmit(a)})))}))},beforeDestroy:function(){this._uniteBeforeDestory()},beforeUnmount:function(){this._uniteBeforeDestory()}},(function(){var t=this,e=t.$createElement;return(t._self._c||e)("div",{staticClass:"h-nav-page-main"},[t._t("default")],2)}),[],!1,null,null,null),d=l({name:"HistoryNavigationPageWrap",components:{PageMain:_.exports},props:{transitionName:{type:String,default:"h-nav-page"},v:{type:Object,required:!0},isActive:{type:Boolean,required:!0},isFirstLoad:{type:Boolean,required:!0},globalPageStyle:void 0}},(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("transition",{attrs:{name:t.transitionName,appear:!0}},[a("div",{directives:[{name:"show",rawName:"v-show",value:t.isActive,expression:"isActive"}],class:t.transitionName,style:{zIndex:t.v.stateKey}},[a("PageMain",{class:t.v.className,style:[t.globalPageStyle,t.v.style],attrs:{path:t.v.path,title:t.v.title,isHome:t.v.isHome,isTab:t.v.isTab,tabIndex:t.v.tabIndex,stateKey:t.v.stateKey,route:t.v.route,isFirstLoad:t.isFirstLoad,isActive:t.isActive}},[a(t.v.cmptKey,{tag:"component"})],1),t._v(" "),t._l(t.v.modalList,(function(t){return a("div",{directives:[{name:"show",rawName:"v-show",value:!t.isBAE,expression:"!modal.isBAE"}],key:t.key,staticClass:"h-nav-modal",style:{zIndex:t.key}},[a("div",{attrs:{id:"h_nav_modal_"+t.uid}})])}))],2)])}),[],!1,null,null,null),v=d.exports,f=l({props:{list:{type:Array,required:!0},currentIndex:{type:Number,required:!0}},name:"HistoryNavigationTabBar"},(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"h-nav-tabbar"},t._l(t.list,(function(e,i){return a("Navigator",{key:e.pagePath,staticClass:"h-nav-tab",attrs:{isActive:t.currentIndex===i,url:e.pagePath,type:"switchTab"}},[e.icon?a("div",{staticClass:"h-nav-tab-icon",class:e.icon}):t._e(),t._v(" "),a("div",{staticClass:"h-nav-tab-text"},[t._v(t._s(e.text))])])})),1)}),[],!1,null,null,null),b=l({name:"HistoryNavigationTabBarController",components:{Page:v,TabBar:f.exports},props:{currTabPage:{type:Object,required:!0},isActive:{type:Boolean,required:!0},isFirstLoad:{type:Boolean,required:!0},traClassName:{type:String},globalPageStyle:void 0,tabList:Array,tabStackMap:Object},data:function(){return{tabBehavior:"_inherit",tabBehaviorDistance:0}},watch:{"currTabPage.tabIndex":function(t,e){var a=t-e,i=a>0?"increase":"reduce";this.tabBehaviorDistance=a,this.tabBehavior=i},isActive:function(){this.tabBehavior="_inherit"}},created:function(){}},(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("transition",{attrs:{name:"h-nav-page",appear:!0}},[a("div",{directives:[{name:"show",rawName:"v-show",value:t.isActive,expression:"isActive"}],staticClass:"h-nav-tabs-ctrler h-nav-tabs-wrap"},[a("transition-group",{staticClass:"h-nav-tab-pages-wrap",class:"h-nav-tab-behavior-"+t.tabBehavior,style:{"--h-nav-tab-distance":t.tabBehaviorDistance},attrs:{tag:"div","enter-class":"","leave-class":"","enter-to-class":"","leave-to-class":"","enter-active-class":"h-nav-tab_page_load","leave-active-class":"h-nav-tab_page_unload"}},t._l(t.tabStackMap,(function(e){return a("div",{key:e.tabIndex,staticClass:"h-nav-tab-page-handle",class:t.traClassName,style:e.isClean?"transition: none!important; animation: none!important;":""},[a("Page",{attrs:{transitionName:"h-nav-tab-page",v:e,isActive:t.isActive&&t.currTabPage.tabIndex===e.tabIndex,isFirstLoad:t.isFirstLoad,globalPageStyle:t.globalPageStyle}})],1)})),0),t._v(" "),a("TabBar",{attrs:{list:t.tabList,currentIndex:t.currTabPage.tabIndex}})],1)])}),[],!1,null,null,null),y=l({components:{Page:v,TabCtrler:b.exports},name:"HistoryNavigationController",props:{entryPagePath:{type:String}},data:function(){var t=this.$navigator._h;return{isErr:!1,isFirstLoad:!0,stackMap:t.stackMap,behavior:t.behavior,currentPage:t.currentPage,globalPageStyle:this.$navigator.GLOBAL_CONFIG.pageStyle,transition:t._tra,tabStackMap:t.tabStackMap,tabList:t.tabList}},methods:{_uniteDestroy:function(){this.$navigator._h.destroy()}},created:function(){var t=this,e=this.$navigator._h;e.checkCompatibility()?(e._load(this.entryPagePath),this.$nextTick((function(){t.isFirstLoad=!1}))):this.isErr=!0},destroyed:function(){this._uniteDestroy()},unmounted:function(){this._uniteDestroy()}},(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("transition-group",{staticClass:"h-nav-ctrler",class:"h-nav-behavior-"+t.behavior.type,style:{"--h-nav-distance":t.behavior.distance},attrs:{tag:"div","enter-class":"","leave-class":"","enter-to-class":"","leave-to-class":"","enter-active-class":"h-nav--page_load","leave-active-class":"h-nav--page_unload"}},[t._l(t.stackMap,(function(e){return a("div",{key:e.stackId,staticClass:"h-nav-page-handle",class:t.transition.className,style:e.isClean?"transition: none!important; animation: none!important;":""},[e.isTab?a("TabCtrler",{key:e.stackId,style:[{zIndex:e.stateKey}],attrs:{tabStackMap:t.tabStackMap,tabList:t.tabList,traClassName:t.transition.className,currTabPage:e,isActive:e.stackId===t.currentPage.stackId,isFirstLoad:t.isFirstLoad,globalPageStyle:t.globalPageStyle}}):a("Page",{key:e.stackId,attrs:{transitionName:"h-nav-page",v:e,isActive:e.stackId===t.currentPage.stackId,isFirstLoad:t.isFirstLoad,globalPageStyle:t.globalPageStyle}})],1)})),t._v(" "),t.isErr?a("div",{key:"not_support",staticClass:"h-nav-not-support"},[t._t("default",[a("h1",[t._v("history-navigation-vue")]),t._v(" "),a("p",[t._v("Sorry, Your browser doesn't support HTML history API.")])])],2):t._e()],2)}),[],!1,null,null,null).exports,g=l({data:function(){return{homePagePath:this.$navigator.GLOBAL_CONFIG.homePagePath}}},(function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticStyle:{"padding-top":"8%","text-align":"center"}},[a("h1",[t._v("History Navigation Vue")]),t._v(" "),a("h2",[t._v("Page Not Found")]),t._v(" "),a("br"),t._v(" "),t.$page.stateKey>1?a("navigator",{staticStyle:{"text-decoration":"underline"},attrs:{type:"back"}},[t._v("Back")]):t._e(),t._v(" "),a("br"),t._v(" "),a("br"),t._v(" "),a("navigator",{staticStyle:{"text-decoration":"underline"},attrs:{type:"relaunch",url:t.homePagePath}},[t._v("Take me home")])],1)}),[],!1,null,null,null).exports,m=Object.assign(Object.create(null),{push:!0,replace:!0,relaunch:!0,switchTab:!0}),k=l({name:"HistoryNavigator",props:{url:{type:String},type:{type:String,default:"push"},steps:{type:Number,default:1},transition:{type:String},isActive:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1},event:{type:String}},computed:{href:function(){return this.url?this.$navigator.URL.toLocationUrl(this.url):void 0}},methods:{handleEvent:function(t){if(t.preventDefault(),!this.isActive&&!this.disabled)if("back"===this.type)this.$navigator.back(this.steps,this.transition);else m[this.type]?this.$navigator[this.type]({url:this.url,transition:this.transition}):console.error("history-navigation-vue navigator not support type: "+this.type)}},mounted:function(){var t=this.event||this.$navigator.GLOBAL_CONFIG.navigatorTriggerEvent;this.$el.addEventListener(t,this.handleEvent)}},(function(){var t=this,e=t.$createElement;return(t._self._c||e)("a",{class:{"h-nav-active":t.isActive,"h-nav-disabled":t.disabled},attrs:{href:t.href}},[t._t("default")],2)}),[],!1,null,null,null).exports;function P(t,e,a){return e in t?Object.defineProperty(t,e,{value:a,enumerable:!0,configurable:!0,writable:!0}):t[e]=a,t}function T(t){var e=t.isHashMode,a=void 0===e||e,i=t.base,n=void 0===i?"":i;this.isHashMode=a,this._location=p,a?this.hashBase=this._location.pathname+this._location.search+"#":this.base=n}function A(t){var e,a,i=t.indexOf("?");return-1!==i?(e=t.substr(0,i),a=t.substr(i+1)):(e=t,a=""),{path:e,query:S(a)}}function B(t){var e,a,i;return"string"==typeof t?(e=A(t),a=t):a=M(e=C(t)),i=I(e.path),Object.assign({fullPath:a,trimedPath:i},e)}function C(t){if(-1===t.path.indexOf("?"))return t;var e=A(t.path);return Object.assign(e.query,t.query),e}function M(t){var e=function(t){if(!t)return"";var e=[];return Object.keys(t).forEach((function(a){e.push(a+"="+encodeURIComponent(t[a]))})),e.join("&")}(t.query);return e&&(e="?"+e),t.path+e}function S(t){var e=Object.create(null);if(!t||"string"==typeof t&&0===t.length)return e;for(var a,i=t.split("&"),n=0,s=i.length;n0&&(t=t.substr(1)),t},T.prototype.getUrlByLocation=function(){if(this.isHashMode)return this.getHashUrlByLocation();var t=this._location.pathname;return 0===t.indexOf(this.base)&&(t=t.substr(this.base.length)),t+this._location.search},T.prototype.getRouteByLocation=function(){return A(this.getUrlByLocation())},T.prototype.toLocationUrl=function(t){return this.isHashMode?this.hashBase+t:this.base+t};var w=T;function L(){var t=u.state;return t&&"number"==typeof t._h_n_key?t._h_n_key:1}function E(){var t=u.state;return t&&"number"==typeof t._h_n_modal_key?t._h_n_modal_key:0}function x(){return{key:L(),modalKey:E()}}var O=x();function N(){return Object.assign({},O)}function $(){var t;t=x(),O=Object.assign({},t)}function j(){}function R(t,e,a,i){var n=e[a];t[a]=void 0===n?i:n}function H(t){throw new Error("history-navigation-vue: ".concat(t))}function F(t,e,a){var i=function(t,e,a){var i=U(t,e.key),n=U(t,a.key),s=0,o=t[i];for(o&&o[0]===e.key&&(s=t[i][1]-e.modalKey),s+=a.modalKey,i+=1;i=e);a++);return a}var K={init:function(){this._modal_crumbs=null,this._initModalCrumbs()},proto:{modal:function(t){var e,a=this,i=t.component,n=t.propsData,s=t.parent,l=t.success,c=arguments[0]===h;c||this._autoBAE();var u=L(),p=this.stackMap[u],_=E();_+=1,this._history.pushState((P(e={},o,u),P(e,r,_),e),""),this._setModalCrumbsWhenChange(),$();var d={key:_,uid:q()};if(p.modalList.push(d),!c){var v="h_nav_modal_"+d.uid;return i&&this.uniteVue.nextTick((function(){if(!d._isDestroy){var t=a.uniteVue.newComponent(i,{el:"#"+v,parent:s,propsData:n});d._destoryCmpt=function(){a.uniteVue.destroy(t)},l&&l(t)}})),v}d.isBAE=!0},removeModal:function(){var t,e=this,a=L(),i=E(),n=this.stackMap[a];n&&(t=n.modalList.splice(i)).forEach((function(t){t._isDestroy=!0,t._destoryCmpt&&t._destoryCmpt()})),0===i&&this._isNeedBAE()&&(t&&t.length>1?this._autoBAE():(this.BAE.onFirstTrigger(),setTimeout((function(){e._autoBAE()}),this.BAE.maxInterval)))},_autoRemoveModal:function(){var t=L(),e=this.stackMap[t],a=E();e&&e.modalList.length>a&&this.removeModal()},_setModalCrumbsWhenChange:function(){var t=L(),e=E();this._setModalCrumbs(t,e)},_initModalCrumbs:function(){var t=this._history,e=Object.assign({},t.state),a=e._h_n_crumbs;if(a)delete e._h_n_crumbs,this._history.replaceState(e,""),this._modal_crumbs=function(t){for(var e,a=t.split(";"),i=[],n=0,s=a.length;n=e);i++);t.splice(i,t.length),a&&t.push([e,a])}(this._modal_crumbs,t,e)},_saveModalCrumbs:function(){if(this._modal_crumbs.length){var t=this._history.state||{};t._h_n_crumbs=this._modal_crumbs.map((function(t){return t.join(":")})).join(";"),this._history.replaceState(t,"")}},getLastModalKeyByCrumbs:function(t){var e=this._modal_crumbs,a=e[e.length-1];return a&&a[0]===t?a[1]:0}}};var V=0;function q(){return V+=1}var D={proto:{_isBAEPage:function(){if(this.BAE){var t=L(),e=this.stackMap[t];if(1===t&&e&&(e.isHome||e.isTab))return!0}return!1},_isBAEPageByTK:function(t){if(this.BAE){var e=L(),a=this.pageMap[t];if(1===e&&a&&(a.isHome||a.isTab))return!0}return!1},_isNeedBAE:function(){if(this._isBAEPage()){var t=this._history.state;if(t&&!t._h_n_modal_key)return!0}return!1},_autoBAE:function(){this._isNeedBAE()&&this.modal(h)}}},W={init:function(){this._whenPopTra=null,this._whenPopInfo=null,this._whenBackPopInfo=null},proto:{_getStepsTotal:function(t){var e=x(),a=e.key,i=e.modalKey,n=t.key,s=t.modalKey,o=a-n;if(0===o)return i-s;var r,h,l=o>0;l?(r=t,h=e):(r=e,h=t);var c=F(this._modal_crumbs,r,h);return l||(c=-c),c},_backGetTo1Count:function(){return this._getStepsTotal({key:1,modalKey:0})},back:function(t,e){var a=t||1;a<1||(this._whenPopTra=e,a&&this._history.go(-a))},backToPage:function(t,e){var a=this._modal_crumbs;if(a.length){for(var i,n=L()-t,s=0,o=0,r=a.length;sn;s++)o+=i[1];this.back(t+o,e)}else this.back(t,e)},_backAndApply:function(t,e,a,i){t<1||(this._whenBackPopInfo={method:e,args:a},this.back(t,i))},_backToStartAndReplace:function(t,e,a){var i=this._backGetTo1Count();i>0?this._backAndApply(i,"_replace",[t,e],a):0===i?(this._setTra(a),this._replace(t,e)):console.error("_backToStartAndReplace not back",i)}}},G=!1;function z(t){G&&H("Only one instance can be generated."),G=!0,this._global=t.global,this._window=c,this._history=u,this._location=p,this._isOmitForwardEvent=!1,this.BAE=t.BAE,this._tra={className:this._global.transition},this.uniteVue=t.uniteVue,this.pageMap=t.pageMap,this.notFoundPage=t.notFoundPage,t.tabBar&&(this.tabMap=t.tabBar.map,this.tabList=t.tabBar.list,this.tabStackMap=Object.create(null)),this.stackMap=Object.create(null),this._stackItemId=1,this.tabCtrlerStackId=1,this.onRouted=t.onRouted||j,this.URL=new w({isHashMode:t.urlIsHashMode,base:t.urlBase}),this.behavior={type:"",distance:0,isPop:!1},this.currentPage={path:null,title:null,className:void 0,isHome:!1,isTab:!1,tabIndex:null,cmptKey:null,stackId:null,stateKey:null,modalList:[],isClean:!1,route:{}},W.init.apply(this),K.init.apply(this)}z.prototype.checkCompatibility=function(){return!(!this._history||!this._history.pushState)},z.prototype._bind=function(){var t=this;this._popstateHandle=function(){t.handlePop()},this._window.addEventListener("popstate",this._popstateHandle),this._handleWinUnload=function(){t.handleWinUnload()},this._window.addEventListener("beforeunload",this._handleWinUnload)},z.prototype._onRouted=function(){var t=this.currentPage;this.onRouted({title:t.title,routeFullPath:t.route.fullPath}),this._autoBAE(t.route.trimedPath)},z.prototype._genStackItemId=function(){return this._stackItemId=this._stackItemId+1,"stack_"+this._stackItemId},z.prototype._isTabRoute=function(t){return!!this.tabMap&&!!this.tabMap[t]},z.prototype._forMatInputArg=function(t){var e,a;return"string"==typeof t?e=B(t):(e=B({path:t.url,query:t.query}),a=t.transition),{fullParse:e,tra:a}},z.prototype._load=function(t){this._bind();var e=B(void 0===t?this.URL.getUrlByLocation():t);1!==L()&&this._isTabRoute(e.trimedPath)?this._backToStartAndReplace(e,"loaded"):this._replaceCurrPage(e,"loaded")},z.prototype._replaceCurrPage=function(){var t=E();t?this._backAndApply(t,"_replace",arguments):this._replace.apply(this,arguments)},z.prototype._setTra=function(t){this._tra.className=t||this._global.transition},z.prototype.push=function(t){var e=this._forMatInputArg(t),a=e.fullParse,i=e.tra;this._isTabRoute(a.trimedPath)?console.error("Cannot push the tab url, please use switchTab"):(this._setTra(i),this._push(a,i))},z.prototype.replace=function(t){var e=this._forMatInputArg(t),a=e.fullParse,i=e.tra;this._isTabRoute(a.trimedPath)?console.error("Cannot replace the tab url, please use switchTab"):(this._setTra(i),this._replaceCurrPage(a))},z.prototype.switchTab=function(t){var e=this._forMatInputArg(t),a=e.fullParse,i=e.tra;this._isTabRoute(a.trimedPath)?this._backToStartAndReplace(a,"switchtab",i):console.error(a," is not tab url")},z.prototype.relaunch=function(t){var e=this._forMatInputArg(t),a=e.fullParse,i=e.tra;this.tabCtrlerStackId=this.tabCtrlerStackId+1,this._backToStartAndReplace(a,"relaunch",i)},z.prototype._setMapItem=function(t,e){var a=this.pageMap[e.trimedPath]||this.notFoundPage,i={path:a.path,title:a.title,tabIndex:a.tabIndex,route:e,cmptKey:a.cmptKey,isHome:a.isHome,isTab:a.isTab,stateKey:t,className:a.className,style:a.style,modalList:[],isClean:!1};i.isTab?(i.stackId="tab_stack_"+this.tabCtrlerStackId,this.uniteVue.set(this.tabStackMap,a.tabIndex,i)):i.stackId=this._genStackItemId(),this.uniteVue.set(this.stackMap,t,i),Object.assign(this.currentPage,i)},z.prototype._getBackTra=function(){var t=this._history;if(t.state)return t.state._h_n_b_tra},z.prototype._push=function(t,e){if(this._autoBAE(),e!==this._getBackTra()){var a=Object.assign({},this._history.state);e?a._h_n_b_tra=e:delete a._h_n_b_tra,this._history.replaceState(a,"")}var i=L()+1;this._setModalCrumbsWhenChange(),this._history.pushState(P({},o,i),"",this.URL.toLocationUrl(t.fullPath)),$();Object.assign(this.behavior,{type:"push",distance:1,isPop:!1}),this._setMapItem(i,t),this._onRouted()},z.prototype._replace=function(t,e){var a=this,i=N().key,n=L(),s=n-i,o={type:e||"replace",distance:s,isPop:!1};Object.assign(this.behavior,o),("switchtab"!==this.behavior.type||s)&&(this.currentPage.stackId="unactive_"+this.currentPage.stackId);var r=this.URL.toLocationUrl(t.fullPath),h=this._history.state;h&&h._h_n_key||((h=Object.assign({},history.state))._h_n_key=n),this._history.replaceState(h,"",r),$(),this.uniteVue.nextTick((function(){if("relaunch"===o.type){a._setAllCleaned();var e=n-s;a.stackMap[e].isClean=!1,a.uniteVue.nextTick((function(){a._clearAll(),a._setMapItem(n,t),a._onRouted()}))}else a._clearAfter(),a._setMapItem(n,t),a._onRouted()}))},z.prototype._setMapCleaned=function(t){for(var e in t)t[e].isClean=!0},z.prototype._setAllCleaned=function(){this.tabStackMap&&this._setMapCleaned(this.tabStackMap),this._setMapCleaned(this.stackMap)},z.prototype._clearAfter=function(){var t,e,a=this,i=L(),n=this.stackMap,s=[];for(t in n)(e=n[t]).stateKey>i&&(e.isClean=!0,s.push(e));var o=s.length;if(o){var r=s.pop();r.isClean=!1,this.uniteVue.delete(n,r.stateKey),(o=s.length)&&this.uniteVue.nextTick((function(){for(t=0;t0)return console.error("Forward is disabled by history-navigation-vue"),this._isOmitForwardEvent=!0,void this.back(s);var r=x(),h=r.key,l=this.stackMap[h];if(l||!r.modalKey){if(this._autoRemoveModal(),$(),i!==h){var c=h-i,u=this._whenPopTra;u||-1!==c||(u=this._getBackTra()),this._setTra(u),this._whenPopTra=null;var p={type:"back",distance:c,isPop:!0};Object.assign(this.behavior,p),l?Object.assign(this.currentPage,l):this._setMapItem(h,B(this.URL.getUrlByLocation())),this.uniteVue.nextTick((function(){t._clearAfter(),t._onRouted()}))}}else this.back(r.modalKey)}},z.prototype.destroy=function(){G&&(this._popstateHandle&&this._window.removeEventListener("popstate",this._popstateHandle),this._handleWinUnload&&this._window.removeEventListener("_handleWinUnload",this._handleWinUnload),G=!1)},z.prototype.handleWinUnload=function(){this._saveModalCrumbs()},Object.assign(z.prototype,K.proto),Object.assign(z.prototype,D.proto),Object.assign(z.prototype,W.proto);var X=z;function Y(t){return P({inject:{$page:{default:null}},created:function(){(this.$options.onShow||this.$options.onHide)&&this.$page&&function(t){var e,n=t.$options;n.__tmp_h_nav_show_hide_has=!0,n.onShow&&(e=n.__tmp_h_nav_page_show_hanlde=function(){n.onShow.call(t)},t.$page.$on(a,e));n.onHide&&(e=n.__tmp_h_nav_page_hide_hanlde=function(){n.onHide.call(t)},t.$page.$on(i,e))}(this)}},t?"unmounted":"destroyed",(function(){this.$options.__tmp_h_nav_show_hide_has&&function(t){var e=t.$options,n=e.__tmp_h_nav_page_show_hanlde;n&&t.$page.$off(a,n);(n=e.__tmp_h_nav_page_hide_hanlde)&&t.$page.$off(i,n)}(this)}))}var J={title:"Not Found",component:g};var Q={install:function(t,e){var a,i=function(t){var e=t.version;return e=e.substr(0,e.indexOf(".")),Number(e)}(t);2===i?a=function(t){return{proto:t.prototype,nextTick:t.nextTick,newComponent:function(e,a){return new(t.extend(e))(a)},destroy:function(t){t.$destroy()},set:t.set,delete:t.delete}}(t):H("Unsupported version of Vue "+i),Array.isArray(e.pages)||H("config.pages is not Array.");var o,r=function(t){for(var e,a,i,s=Object.create(null),o=0,r=t.length;o= 2");for(var n,s,o=0,r=Object.create(null),h=[];o {
65 | isAgain = false;
66 | }, interval);
67 | }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/examples/create_app.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import config from './config';
4 |
5 |
6 |
7 | // import '../dist/history-navigation-vue.css';
8 | // import * as hNav from '../dist/history-navigation-vue';
9 | import * as hNav from '../src/bundle';
10 | import './css/docs.css';
11 | import './css/transition-mask.css';
12 |
13 | import Root from './root.vue';
14 |
15 | export default function createApp(_Vue, _config){
16 | if(_config){
17 | Object.assign(config, _config);
18 | }
19 | _Vue.use(hNav.plugin, config);
20 | var app = new _Vue({
21 | render: h => h(Root)
22 | });
23 | app.$mount('#app');
24 | return app;
25 | }
--------------------------------------------------------------------------------
/examples/css/docs.css:
--------------------------------------------------------------------------------
1 |
2 |
3 | *{
4 | box-sizing: border-box;
5 | }
6 | html, body {
7 | height: 100%;
8 | }
9 |
10 | body, h1, h2, ul, li, button, pre{
11 | padding: 0;
12 | margin: 0;
13 | }
14 | body {
15 | background-color: #ddd;
16 | }
17 |
18 | .h-nav-ctrler{
19 | max-width: 768px;
20 | margin: 0 auto;
21 | }
22 | h1{
23 | font-size: 2em;
24 | margin-bottom: 1em;
25 | margin-top: 1em;
26 | }
27 | h2{
28 | font-size: 1.5em;
29 | margin-bottom: 1em;
30 | margin-top: 1em;
31 | }
32 | body{
33 | font-size: 16px;
34 | }
35 | input {
36 | line-height: normal;
37 | }
38 | div:focus{
39 | outline: none;
40 | }
41 | ul {
42 | list-style: none;
43 | }
44 | form{
45 | display: block;
46 | }
47 | .root{
48 | min-height: 100vh;
49 | }
50 | .page{
51 | min-height: 200vh;
52 | background-image: linear-gradient(#eee, #999, #eee, #999);
53 | }
54 | a{
55 | text-decoration: none;
56 | cursor: pointer;
57 | color: #333;
58 | }
59 |
60 | .wrap{
61 | max-width: 1000px;
62 | margin: 0 auto;
63 | position: relative;
64 | }
65 | .bottom_bar{
66 | height: 50px;
67 | position: fixed;
68 | left: 0;
69 | bottom: 0;
70 | width: 100%;
71 | background-color: #ccc;
72 | border-top: 1px solid #fff;
73 | display: flex;
74 | }
75 | a.bottom_bar_item{
76 | width: 50%;
77 | flex-shrink: 0;
78 | text-align: center;
79 | line-height: 49px;
80 | color: #333;
81 | }
82 | a.bottom_bar_item.router-link-exact-active{
83 | color: #00008B;
84 | font-weight: bold;
85 | }
86 | .index_to_list{
87 | padding-top: 150px;
88 | text-align: center;
89 | display: block;
90 | }
91 |
92 | .item{
93 | display: flex;
94 | align-items: center;
95 | height: 35px;
96 | justify-content: space-around;
97 | padding: 0 10px;
98 | background-color: #fff;
99 | border-bottom: 1px solid #ddd;
100 | line-height: 1;
101 | }
102 | .item_active{
103 | background-color: #ddd;
104 | }
105 |
106 |
107 |
108 | body{
109 | font-size: 16px;
110 | }
111 |
112 | .index_page{
113 | min-height: 100vh;
114 | }
115 |
116 | .index_logo{
117 | width: 2em;
118 | height: 2em;
119 | display: block;
120 | }
121 | .index_page h1, .index_page h2 {
122 | text-align: center;
123 | }
124 | .index_desc{
125 | color: #999;
126 | margin-top: 0;
127 | }
128 | .nav{
129 |
130 | width: 100%;
131 | display: flex;
132 | align-items: center;
133 | padding-top: 8px;
134 | }
135 | .nav a {
136 | margin: 0 1em;
137 | }
138 | .nav .gitgub{
139 | width: 30px;
140 | height: 30px;
141 | }
142 |
143 | .index_page h2 b{
144 | color: #000;
145 | }
146 |
147 | a {
148 | cursor: pointer;
149 | }
150 | .h-nav-active{
151 | opacity: 1;
152 | }
153 | .api_nav.h-nav-active{
154 | color: #000;
155 | font-weight: bold;
156 | }
157 |
158 |
159 |
160 |
161 |
162 | .index_table_wrap{
163 | display: flex;
164 | align-items: center;
165 | justify-content: center;
166 | flex-direction: column;
167 | }
168 |
169 | .index_table{
170 | border-collapse: collapse;
171 | border-color: #ccc;
172 | border: 0;
173 | }
174 |
175 | .index_table th{
176 | background-color: #eee;
177 | }
178 |
179 | .index_table th, .index_table td{
180 | padding: 3px 12px;
181 | }
182 |
183 | td.index_table_f{
184 | padding-right: 45px;
185 | font-weight: bold;
186 | }
187 |
188 | .index_table i{
189 | color: #999;
190 | }
191 |
192 |
193 | .index_diff_loading{
194 | cursor: auto;
195 | }
196 |
197 | .index_diff_loading::before {
198 | padding:0;
199 | margin:0;
200 | position: absolute;
201 | top:0;
202 | bottom:0;
203 | right:0;
204 | left:0;
205 | margin:auto;
206 | height: 13px;
207 | width: 13px;
208 | animation: rotate 0.8s infinite linear;
209 | border: 3px solid #999;
210 | border-right-color: #fff;
211 | border-radius: 50%;
212 | }
213 |
214 | .index_diff_loading::before{
215 | content: '';
216 | }
217 |
218 | @keyframes rotate {
219 | 0% { transform: rotate(0deg); }
220 | 100% { transform: rotate(360deg); }
221 | }
222 |
223 | .index_diff_loading_more .index_diff_loading::before{
224 | width: 9px;
225 | height: 9px;
226 | border-width: 2px;
227 | }
228 | .index_diff_start_link{
229 | text-decoration: underline;
230 | padding: 3px 10px;
231 | border-radius: 5px;
232 | color: #333;
233 | }
234 |
235 | .index_diff_list_item_active,
236 | .index_diff_page_back_active,
237 | .index_diff_start_active{
238 | transition: all .15s;
239 | background-color: #aaa;
240 | color: #fff;
241 | }
242 | .index_diff_start_active{
243 | text-decoration: none;
244 | }
245 | .index_codepen_wrap{
246 | width: 980px;
247 | margin: 0 auto;
248 | height: 565px;
249 | }
250 |
251 | footer{
252 | padding: 25px 0px;
253 | line-height: 1.7em;
254 | background-color: #eee;
255 | text-align: center;
256 | font-size: 14px;
257 | }
258 |
259 | .index_get_start{
260 | text-align: center;
261 | font-size: 30px;
262 | margin-top: 20px;
263 | padding-bottom: 50px;
264 | }
265 |
266 | .api_wrap h1{
267 | text-align: center;
268 | }
269 |
270 | .red{
271 | height: 100px;
272 | width: 100px;
273 |
274 | }
275 |
276 | .index_icon{
277 | background-image: url('https://www.w3school.com.cn/ui2017/compatible_firefox.png');
278 | }
279 | .h-nav-actived > .index_icon{
280 | background-image: url('https://www.w3school.com.cn/ui2017/compatible_ie.png');
281 | }
282 |
283 |
284 | /* .h-nav-tab-page {
285 | top: 48px;
286 | }
287 | .h-nav-tabbar {
288 | top: 0;
289 | bottom: initial;
290 | border-top: 0;
291 | border-bottom: 1px solid #c7c7c7;
292 | } */
293 |
294 |
295 | /* .h-nav-tabs-wrap{
296 | padding-bottom: 0;
297 | padding-top: 48px;
298 | }
299 | .h-nav-tabbar{
300 | top: 0;
301 | bottom: initial;
302 | border-top: 0;
303 | border-bottom: 1px solid #c7c7c7;
304 | } */
--------------------------------------------------------------------------------
/examples/css/transition-mask.css:
--------------------------------------------------------------------------------
1 | /* .h-nav-behavior-loaded > .h-nav-transition{
2 | transition: all 1s ease;
3 | }
4 | .h-nav-behavior-loaded > .h-nav-transition > .h-nav-page-enter{
5 | top: -100%;
6 | }
7 |
8 | .h-nav-behavior-switchtab > .h-nav-transition{
9 | transition: all 1s ease;
10 | }
11 | .h-nav-behavior-switchtab > .h-nav-transition > .h-nav-page-enter{
12 | top: -33%;
13 | left: -33%;
14 | }
15 | .h-nav-behavior-switchtab > .h-nav-transition > .h-nav-page-leave-to{
16 | left: 100%;
17 | top: 100%;
18 | }
19 |
20 | .h-nav-tab-behavior-reduce > .h-nav-transition,
21 | .h-nav-tab-behavior-increase > .h-nav-transition{
22 | transition: all .3s ease;
23 | }
24 |
25 | .h-nav-tab-behavior-reduce > .h-nav-transition > .h-nav-tab-page-enter,
26 | .h-nav-tab-behavior-increase > .h-nav-transition > .h-nav-tab-page-leave-to{
27 | left: -100%;
28 | }
29 |
30 | .h-nav-tab-behavior-increase > .h-nav-transition > .h-nav-tab-page-enter,
31 | .h-nav-tab-behavior-reduce > .h-nav-transition > .h-nav-tab-page-leave-to{
32 | left: 100%;
33 | }
34 | .h-nav-tab-behavior-reduce > .h-nav-transition > .h-nav-tab-page-enter,
35 | .h-nav-tab-behavior-increase > .h-nav-transition > .h-nav-tab-page-enter{
36 | left: calc(100% * var(--h-nav-tab-distance));
37 | } */
38 |
39 |
40 | /* .h-nav-tab-behavior-reduce > .h-nav-transition,
41 | .h-nav-tab-behavior-increase > .h-nav-transition{
42 | transition: all .3s ease;
43 | }
44 |
45 | .h-nav-tab-behavior-reduce > .h-nav-transition > .h-nav-tab-page-enter,
46 | .h-nav-tab-behavior-increase > .h-nav-transition > .h-nav-tab-page-leave-to{
47 | left: -100%;
48 | }
49 |
50 | .h-nav-tab-behavior-increase > .h-nav-transition > .h-nav-tab-page-enter,
51 | .h-nav-tab-behavior-reduce > .h-nav-transition > .h-nav-tab-page-leave-to{
52 | left: 100%;
53 | }
54 |
55 | .h-nav-behavior-push > .h-nav-transition,
56 | .h-nav-behavior-back > .h-nav-transition,
57 | .h-nav-behavior-replace > .h-nav-transition{
58 | transition: all 3s ease;
59 | }
60 |
61 | .h-nav-behavior-push > .h-nav-transition > .h-nav-page-enter,
62 | .h-nav-behavior-back > .h-nav-transition > .h-nav-page-leave-to,
63 | .h-nav-behavior-replace > .h-nav-transition > .h-nav-page-enter {
64 | transform: translateX(100%);
65 | }
66 |
67 | .h-nav-behavior-push > .h-nav-transition > .h-nav-page-leave-to,
68 | .h-nav-behavior-back > .h-nav-transition > .h-nav-page-enter,
69 | .h-nav-behavior-replace > .h-nav-transition > .h-nav-page-leave-to{
70 | transform: translateX(-33%);
71 | } */
72 |
73 | /* .h-nav-behavior-back > .h-nav-transition > .h-nav-page-leave-to{
74 |
75 | }
76 | .h-nav-behavior-push > .h-nav-transition > .h-nav-page-leave-to{
77 | transform: translateX(-100%);
78 | }
79 |
80 | .h-nav-behavior-back > .h-nav-transition > .h-nav-page-enter{
81 | transform: translateX(0);
82 | } */
83 | /*
84 | h-nav-page-enter-to // .h-nav-behavior-back > .h-nav-transition > .h-nav-page-enter{
85 | transform: translateX(calc(100% * var(--h-nav-distance)));
86 | } */
--------------------------------------------------------------------------------
/examples/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 | <% var opts = htmlWebpackPlugin.options, statics = opts.staticMap; data = opts.data; %>
4 |
5 |
6 |
7 | <%=data.title%>
8 |
9 |
10 | Loading...
11 | <% for(let i in statics){%>
12 |
13 | <%}%>
14 |
15 |
16 |
--------------------------------------------------------------------------------
/examples/pages/_inner-show.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Inner Show
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/examples/pages/api.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
API {{now}}
5 | switchTab to Index
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/examples/pages/detail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Detail {{Date.now()}}
4 |
5 |
6 | back to start
7 |
8 | back2
9 |
10 | backToPage2
11 |
12 | replace to list
13 |
14 | Relaunch to list
15 |
16 | Relaunch to Index
17 | back999
18 |
19 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/examples/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
showModal
5 |
6 |
Index {{now}}
7 |
Close
8 |
switchTab to API
9 |
10 |
relaunch API
11 |
12 |
relaunch /
13 |
14 |
15 |
16 |
List
17 |
18 |
19 |
20 |
21 |
22 |
23 |
replace to List
24 |
25 |
26 |
showModal
27 |
isHomePage
28 | isHomePage
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/pages/index_modal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{text}}
6 | replaceList
7 | pushList
8 | Close
9 |
10 |
11 |
12 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/pages/list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
List {{Date.now()}}
6 |
switchInner
7 |
8 |
9 |
10 |
11 |
now: {{now}}
12 |
13 |
To Detail
14 |
15 |
custom To Detail
16 |
17 |
showModal
18 |
19 |
replace to List
20 |
21 |
replace to Detail
22 |
23 |
Relaunch to Detail
24 |
25 |
switchTab to api
26 |
27 |
switchTab to Me
28 |
29 |
Back
30 |
31 |
Replace to List
32 |
33 |
Replace to Index
34 |
35 |
Relaunch to Index
36 |
37 |
Tra push to Detail
38 |
right
39 |
40 |
Detail
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
127 |
--------------------------------------------------------------------------------
/examples/pages/list_modal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
{{text}}
6 | pushDetail
7 | Close
8 |
9 |
10 |
11 |
30 |
31 |
--------------------------------------------------------------------------------
/examples/pages/me.vue:
--------------------------------------------------------------------------------
1 |
2 | Me
3 |
--------------------------------------------------------------------------------
/examples/pages/modal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Modal
6 |
7 |
8 |
9 |
10 |
23 |
--------------------------------------------------------------------------------
/examples/pages/nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | API
6 |
7 | GitHub Dark icon
8 |
9 |
10 |
--------------------------------------------------------------------------------
/examples/pages/not-found.vue:
--------------------------------------------------------------------------------
1 |
2 | 404
3 |
--------------------------------------------------------------------------------
/examples/pages/tra-performance-detail.vue:
--------------------------------------------------------------------------------
1 |
2 | Hello ! {{id}}
3 |
4 |
--------------------------------------------------------------------------------
/examples/pages/tra-performance.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
displayNone Toggle
4 |
visHiddenToggle
5 |
To Detail 999
6 |
7 |
8 | {{v}}
9 | To Detail
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/root.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | hahaah
4 |
5 |
6 |
7 |
23 |
--------------------------------------------------------------------------------
/examples/tip.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{text}}
5 |
6 |
7 |
8 |
9 |
44 |
--------------------------------------------------------------------------------
/examples_vue3/.npmrc:
--------------------------------------------------------------------------------
1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
2 | phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
3 | electron_mirror=https://npm.taobao.org/mirrors/electron/
4 | registry=https://registry.npm.taobao.org
--------------------------------------------------------------------------------
/examples_vue3/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "examples_vue3",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "create": "vue create vue3_project"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@vue/cli": "^4.5.15"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/.npmrc:
--------------------------------------------------------------------------------
1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
2 | phantomjs_cdnurl=https://npm.taobao.org/mirrors/phantomjs/
3 | electron_mirror=https://npm.taobao.org/mirrors/electron/
4 | registry=https://registry.npm.taobao.org
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/README.md:
--------------------------------------------------------------------------------
1 | # vue3_project
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue3_project",
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": "^3.6.5",
12 | "vue": "^3.0.0"
13 | },
14 | "devDependencies": {
15 | "@vue/cli-plugin-babel": "~4.5.0",
16 | "@vue/cli-plugin-eslint": "~4.5.0",
17 | "@vue/cli-service": "~4.5.0",
18 | "@vue/compiler-sfc": "^3.0.0",
19 | "babel-eslint": "^10.1.0",
20 | "eslint": "^6.7.2",
21 | "eslint-plugin-vue": "^7.0.0"
22 | },
23 | "eslintConfig": {
24 | "root": true,
25 | "env": {
26 | "node": true
27 | },
28 | "extends": [
29 | "plugin:vue/vue3-essential"
30 | ],
31 | "rules": {}
32 | },
33 | "browserslist": [
34 | "> 1%",
35 | "last 2 versions",
36 | "not dead"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hezedu/history-navigation-vue/b68b1c59abb8dd4419d01cfc16ffc12b1cc9f311/examples_vue3/vue3_project/public/favicon.ico
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
18 |
19 |
20 |
21 | We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | splice
4 | remove
5 | push
6 | add
7 | change
8 |
9 |
10 | {{v}} -
11 |
12 | change
13 | -
14 | del
15 |
16 |
17 |
18 |
19 |
20 |
73 |
74 |
84 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hezedu/history-navigation-vue/b68b1c59abb8dd4419d01cfc16ffc12b1cc9f311/examples_vue3/vue3_project/src/assets/logo.png
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation .
8 |
9 |
Installed CLI Plugins
10 |
14 |
Essential Links
15 |
22 |
Ecosystem
23 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
59 |
--------------------------------------------------------------------------------
/examples_vue3/vue3_project/src/main.js:
--------------------------------------------------------------------------------
1 | import * as vue from 'vue';
2 | import { fitVue3, plugin } from '../../../src/bundle';
3 |
4 | import '../../../examples/css/docs.scss';
5 | import '../../../examples/css/transition-mask.css';
6 |
7 | import config from '../../../examples/config';
8 | import Root from '../../../examples/root.vue';
9 | // import App from './App.vue'
10 | fitVue3(vue);
11 |
12 | const app = vue.createApp(Root);
13 | app.use(plugin, config);
14 |
15 | console.log(app);
16 |
17 | app.mount('#app')
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | hello
9 |
10 |
11 | Loading...
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "history-navigation-vue",
3 | "version": "1.5.0",
4 | "description": "Native-like Navigation for Web apps.",
5 | "keywords": [
6 | "history",
7 | "navigation",
8 | "vue",
9 | "controller",
10 | "html5",
11 | "multi-page",
12 | "architecture",
13 | "single-page",
14 | "native-like",
15 | "routing",
16 | "router",
17 | "hybrid",
18 | "web app"
19 | ],
20 | "main": "dist/history-navigation-vue.js",
21 | "license": "MIT",
22 | "author": "hezedu",
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/hezedu/history-navigation-vue.git"
26 | },
27 | "scripts": {
28 | "start": "webpack-dev-server --progress --hot --color",
29 | "build": "npm run build_dev && npm run _build_pro && node script/after_webpack_build.js",
30 | "_build_pro": "webpack --node-env=production --color --progress --config webpack.build.config.js",
31 | "build_dev": "webpack --node-env=development --color --progress --config webpack.build.config.js",
32 | "build_ex": "webpack --node-env=production --color --progress --config webpack.config.js",
33 | "ssr_build": "webpack --color --node-env=production --progress --config ./ssr/webpack.config.js",
34 | "preview_docs": "node server/preview_docs.js",
35 | "preview_ex": "node server/preview_ex.js",
36 | "test": "cd test && npm run test_unit",
37 | "test_unit": "mocha test/unit",
38 | "test_e2e": "mocha test/e2e --timeout=5000"
39 | },
40 | "files": [
41 | "dist"
42 | ],
43 | "devDependencies": {
44 | "@babel/core": "^7.16.0",
45 | "@babel/eslint-parser": "^7.16.3",
46 | "@babel/plugin-proposal-class-properties": "^7.16.0",
47 | "@babel/plugin-proposal-decorators": "^7.16.4",
48 | "@babel/plugin-proposal-do-expressions": "^7.16.0",
49 | "@babel/plugin-proposal-export-default-from": "^7.16.0",
50 | "@babel/plugin-proposal-export-namespace-from": "^7.16.0",
51 | "@babel/plugin-proposal-function-bind": "^7.16.0",
52 | "@babel/plugin-proposal-function-sent": "^7.16.0",
53 | "@babel/plugin-proposal-json-strings": "^7.16.0",
54 | "@babel/plugin-proposal-logical-assignment-operators": "^7.16.0",
55 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0",
56 | "@babel/plugin-proposal-numeric-separator": "^7.16.0",
57 | "@babel/plugin-proposal-optional-chaining": "^7.16.0",
58 | "@babel/plugin-proposal-pipeline-operator": "^7.16.0",
59 | "@babel/plugin-proposal-throw-expressions": "^7.16.0",
60 | "@babel/plugin-syntax-dynamic-import": "^7.8.3",
61 | "@babel/plugin-syntax-import-meta": "^7.8.3",
62 | "@babel/plugin-transform-runtime": "^7.16.4",
63 | "@babel/preset-env": "^7.16.4",
64 | "@babel/runtime": "^7.16.3",
65 | "acorn": "^8.6.0",
66 | "autoprefixer": "^9.8.8",
67 | "babel-loader": "^8.2.3",
68 | "babel-plugin-transform-remove-console": "^6.9.4",
69 | "chromedriver": "^96.0.0",
70 | "css-loader": "^6.5.1",
71 | "css-minimizer-webpack-plugin": "^3.2.0",
72 | "eslint": "^8.3.0",
73 | "eslint-plugin-vue": "^8.1.1",
74 | "eslint-webpack-plugin": "^3.1.1",
75 | "express": "^4.17.1",
76 | "html-webpack-plugin": "^5.5.0",
77 | "mini-css-extract-plugin": "^2.4.5",
78 | "mocha": "^9.1.3",
79 | "postcss": "^8.4.4",
80 | "postcss-loader": "^6.2.1",
81 | "selenium-webdriver": "^4.1.0",
82 | "style-loader": "^3.3.1",
83 | "terser": "^5.10.0",
84 | "terser-webpack-plugin": "^5.2.5",
85 | "vue": "2.6.11",
86 | "vue-loader": "^15.9.8",
87 | "vue-server-renderer": "2.6.11",
88 | "vue-template-compiler": "2.6.11",
89 | "webpack": "^5.64.4",
90 | "webpack-cli": "^4.9.1",
91 | "webpack-dev-server": "^4.6.0",
92 | "webpack-merge": "^5.8.0",
93 | "webpack-node-externals": "^3.0.0"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | var autoprefixer = require('autoprefixer');
2 | module.exports = {
3 | plugins: [
4 | autoprefixer
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/script/after_webpack_build.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const pkg = require('../package.json');
4 | const zlib = require('zlib');
5 |
6 | const banner =
7 | `/*!
8 | * ${pkg.name} v${pkg.version}
9 | * (c) ${new Date().getFullYear()} hezedu
10 | * @license ${pkg.license}
11 | */
12 | `
13 | const dir = path.join(__dirname, '../dist');
14 | const names = fs.readdirSync(dir);
15 | const year = (new Date()).getFullYear()
16 | let totalContent = '';
17 | names.forEach(name => {
18 | let filename = path.join(dir, name);
19 | let content = fs.readFileSync(filename, 'utf-8');
20 | content = content.replace('__VERSION__', pkg.version);
21 | content = content.replace('__NOW_YEAR__', year);
22 | content = banner + content;
23 | if(name.lastIndexOf('.min') !== -1){
24 | totalContent = totalContent + content;
25 | }
26 | console.log(name + ' add commit banner ok');
27 | fs.writeFileSync(filename, content);
28 | });
29 |
30 | const zipped = zlib.gzipSync(totalContent);
31 | console.log('Pro minimize files total zipped size: ', (zipped.length / 1024).toFixed(2) + 'kb');
32 |
33 |
--------------------------------------------------------------------------------
/script/common.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const child_process = require('child_process');
3 | const del = require('fuckwinfsdel');
4 | const webpack = require('webpack');
5 | // const fs = require('fs');
6 | const webpackConf = require('../webpack.config');
7 | const clearDir = require('./get_config')().outputPath;
8 |
9 | module.exports = function(){
10 | // var build_sh = 'webpack --colors';
11 | console.log('开始清空:',clearDir, '...');
12 | del(clearDir, function(err){
13 | if(err){
14 | console.error(err);
15 | }
16 | console.log('开始build...');
17 | webpack(webpackConf, function(err, stats){
18 | if(err || stats.hasErrors()){
19 | // const json2 = stats.toJson();
20 | // fs.writeFileSync(path.join(__dirname, 'errout.json'), JSON.stringify(json2, null, '\t'));
21 | return console.log('build 失败', err, stats.toString({
22 | // copied from `'minimal'`
23 | all: false,
24 | modules: false,
25 | maxModules: 0,
26 | errors: true,
27 | warnings: true,
28 | // our additional options
29 | moduleTrace: false,
30 | errorDetails: false
31 | }));
32 | }
33 | // const json = stats.toJson();
34 | // fs.writeFileSync(path.join(__dirname, 'out.json'), JSON.stringify(json, null, '\t'));
35 | // console.log('json', Object.keys(json));
36 | console.log(stats.toString({
37 | // Add console colors
38 | colors: true,
39 | chunks: false,
40 | modules: false,
41 | children: false
42 | }));
43 | console.log('build 成功');
44 |
45 | });
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/script/get-static-map.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const cdnRootUrl = '/_NODE_MODULE_/';
4 | module.exports = function(isPro){
5 | const staticMap = Object.create(null);
6 | ['vue'].forEach(name => {
7 | const mPath = require.resolve(name);
8 | const dir = path.dirname(path.dirname(mPath));
9 | let pkg = fs.readFileSync(path.join(dir, 'package.json'));
10 | pkg = JSON.parse(pkg);
11 | const version = pkg.version;
12 | pkg = null;
13 | let fileName = name;
14 | if(name === 'vue'){
15 | fileName = 'vue.runtime';
16 | }
17 | if(isPro){
18 | fileName = fileName + '.min.js';
19 | } else {
20 | fileName = fileName + '.js';
21 | }
22 |
23 | staticMap[name] = {
24 | version,
25 | fileName,
26 | filePath: path.join(dir, 'dist/' + fileName),
27 | // url: '/static/' + name + '@' + version + '/dist/' + fileName
28 | url: cdnRootUrl + name + '@' + version + '/dist/' + fileName
29 | }
30 | });
31 |
32 | return staticMap;
33 | }
34 |
--------------------------------------------------------------------------------
/script/get_config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | function get(){
4 | const OUT_FOLDER = 'built';
5 | const isPro = process.env.NODE_ENV === 'production';
6 |
7 | let baseUrl = '';
8 | let indexDir;
9 | let bundleName;
10 | let publicPath;
11 | let outputPath;
12 |
13 | if(isPro){
14 | indexDir = path.join(__dirname, '../dist');
15 | bundleName = '[name]_[contenthash].min.js';
16 | publicPath = OUT_FOLDER;
17 | outputPath = path.join(indexDir, publicPath + '/');
18 | } else {
19 | indexDir = path.join(__dirname, '../');
20 | bundleName = '[name].js';
21 | publicPath = 'dev_dist/' + OUT_FOLDER;
22 | outputPath = path.join(indexDir, publicPath + '/');
23 | }
24 |
25 | publicPath = baseUrl + '/' + publicPath + '/';
26 |
27 | // if(indexDir[indexDir.length - 1] === path.sep){
28 | // indexDir = indexDir.substr(0, indexDir.length - 1);
29 | // }
30 | return {
31 | indexDir,
32 | bundleName,
33 | outputPath,
34 | publicPath
35 | }
36 | }
37 |
38 |
39 | module.exports = get;
--------------------------------------------------------------------------------
/script/pro.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'production';
2 | var bulid = require('./common');
3 | bulid();
4 |
--------------------------------------------------------------------------------
/server/preview.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const app = express();
4 | const config = require('../config');
5 | // var proxy = require('http-proxy-middleware');
6 | // app.use(express.static('/', path.join(__dirname, '../dist')));
7 | // app.use('/api', proxy({target: '', changeOrigin: true}));
8 | app.use('/', express.static(path.join(__dirname, '../dist')));
9 | const port = config.previewPort;
10 | app.listen(port, () => console.log(`Preview app listening on port ${port}!`))
--------------------------------------------------------------------------------
/server/preview_docs.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const config = require('../config');
4 |
5 | const app = express();
6 | app.use('/history-navigation-vue/', express.static(path.join(__dirname, '../docs')));
7 | const port = config.previewDocsPort;
8 | app.listen(port, () => console.log(`Preview app listening on port ${port}!`))
--------------------------------------------------------------------------------
/server/preview_ex.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const config = require('../config');
4 | const setup = require('./setup');
5 | const app = express();
6 | setup(app);
7 |
8 | // dev
9 | app.use('/dev_dist', express.static(path.join(__dirname, '../dev_dist')));
10 | app.use('/dev_favicon.png', express.static(path.join(__dirname, '../dev_favicon.png')));
11 | const port = config.previewExPort;
12 | app.listen(port, () => console.log(`Preview app listening on port ${port}!`))
--------------------------------------------------------------------------------
/server/setup.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 | const eStatic = require('express').static;
4 | const getStaticMap = require('../script/get-static-map');
5 | const staticMap = getStaticMap();
6 |
7 | function setup(app){
8 | app.get('/favicon.ico', (req, res) => {
9 | res.set({
10 | 'Cache-Control': 'public, max-age=91104000'
11 | });
12 | res.status(410).end('Gone');
13 | });
14 |
15 | let v;
16 | for(let i in staticMap){
17 | v = staticMap[i];
18 | app.use(v.url, eStatic(v.filePath));
19 | }
20 |
21 | app.get('*', (req, res, next) => {
22 | if(req.url.indexOf('/static') === 0 ||
23 | req.url.indexOf('/dev') === 0){
24 | next();
25 | } else {
26 | res.type('html').send( fs.readFileSync(path.join(__dirname, '../index.html')));
27 | }
28 | });
29 |
30 |
31 |
32 |
33 |
34 | };
35 |
36 | setup.staticMap = staticMap;
37 | module.exports = setup;
--------------------------------------------------------------------------------
/src/bundle.js:
--------------------------------------------------------------------------------
1 | import './css/style.css';
2 | // import './css/transition.css';
3 | import install from './install';
4 |
5 | export const plugin = {
6 | install
7 | }
8 |
9 | // export { fitVue$3 } from './fit_vue';
10 |
11 | export const version = '__VERSION__';
12 |
--------------------------------------------------------------------------------
/src/cmpt/common.js:
--------------------------------------------------------------------------------
1 | export function genPageProps(){
2 | return {
3 | path: {
4 | type: String
5 | },
6 |
7 | title: String,
8 |
9 | isTab: {
10 | type: Boolean,
11 | required: true
12 | },
13 |
14 | tabIndex: Number,
15 |
16 | stateKey: Number,
17 |
18 | route: {
19 | type: Object,
20 | required: true
21 | },
22 |
23 | isActive: {
24 | type: Boolean,
25 | required: true
26 | },
27 |
28 | isFirstLoad: {
29 | type: Boolean,
30 | required: true
31 | },
32 |
33 | stackId: Number
34 | }
35 | }
--------------------------------------------------------------------------------
/src/cmpt/default-not-found.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
History Navigation Vue
5 | Page Not Found
6 |
7 |
8 | Back
9 |
10 |
11 | Take me home
12 |
13 |
14 |
23 |
--------------------------------------------------------------------------------
/src/cmpt/navigation-controller.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
21 |
22 |
33 |
34 |
42 |
43 |
44 |
45 |
46 |
history-navigation-vue
47 | Sorry, Your browser doesn't support HTML history API.
48 |
49 |
50 |
51 |
52 |
107 |
--------------------------------------------------------------------------------
/src/cmpt/navigator.vue:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/src/cmpt/page.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/cmpt/page_main.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/cmpt/tab-bar-controller.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/cmpt/tab-bar.vue:
--------------------------------------------------------------------------------
1 |
58 |
59 |
60 |
61 |
67 |
68 | {{v.text}}
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/src/constant.js:
--------------------------------------------------------------------------------
1 | export const PAGE_E_SHOW_NAME = '_h_nav_page_show';
2 | export const PAGE_E_HIDE_NAME = '_h_nav_page_hide';
3 |
4 | export const cmptPageSuffix = 'h-nav-page-';
5 | export const notFoundPageKey = cmptPageSuffix + 'not-found';
6 |
7 | // export const DEF_IS_SET_HREF = true;
8 | export const DEF_URL_IS_HASH_MODE = true;
9 | export const DEF_NAVIGATOR_TRIGGER_EVENT = 'click';
10 | export const DEF_URL_BASE = '';
11 |
12 | export const DEF_TRANSITION = 'h-nav-transition';
13 |
14 | export const KEY_NAME = '_h_n_key';
15 | export const MODAL_CRUMBS_KEY_NAME = '_h_n_crumbs';
16 | export const MODAL_KEY_NAME = '_h_n_modal_key';
17 | export const BACK_TRA_PROP_KEY = '_h_n_b_tra';
18 |
19 | export const MODAL_BAE_KEY = '_H_NAV_BAE_MODAL_';
20 |
21 | export const DEF_PAGE_STYLE = undefined;
22 |
23 | // export const AGAIN_TO_EXIT_INTERVAL = 2000;
24 | // export const AGAIN_TO_EXIT_TIP = 'Press Back Again to Exit';
--------------------------------------------------------------------------------
/src/css/style.css:
--------------------------------------------------------------------------------
1 | .h-nav-ctrler{
2 | width: 100%;
3 | height: 100%;
4 | overflow: hidden;
5 | position: relative;
6 | }
7 |
8 | .h-nav-page, .h-nav-tab-page {
9 | position: absolute;
10 | left: 0;
11 | top: 0;
12 | height: 100%;
13 | width: 100%;
14 | }
15 |
16 | .h-nav-page-main {
17 | background-color: #fff;
18 | position: relative;
19 | -webkit-overflow-scrolling: touch;
20 | overflow: auto;
21 | width: 100%;
22 | height: 100%;
23 | }
24 |
25 | .h-nav-tabs-ctrler{
26 | position: absolute;
27 | left: 0;
28 | top: 0;
29 | bottom: 0;
30 | right: 0;
31 | overflow: hidden;
32 | }
33 |
34 |
35 |
36 | .h-nav-page-handle,
37 | .h-nav-tab-page-handle{
38 | width: 0!important;
39 | height: 0!important;
40 | border: 0!important;
41 | margin: 0!important;
42 | padding: 0!important;
43 | box-shadow: none!important;
44 | outline: none!important;
45 | position: static!important;
46 | }
47 |
48 | .h-nav-page-enter-active,
49 | .h-nav-page-leave-active,
50 | .h-nav-tab-page-enter-active,
51 | .h-nav-tab-page-leave-active{
52 | transition: inherit;
53 | animation: inherit;
54 | }
55 |
56 | .h-nav-tab-pages-wrap {
57 | transition: inherit;
58 | animation: inherit;
59 | position: static!important;
60 | transition-property: none;
61 | animation-name: none;
62 | }
63 |
64 | .h-nav-tab-page-handle{
65 | transition: inherit;
66 | animation: inherit;
67 | }
68 | .h-nav-tab-behavior-_inherit{
69 | transition: inherit!important;
70 | animation: inherit!important;
71 | }
72 |
73 | .h-nav-modal {
74 | position: absolute;
75 | top: 0;
76 | left: 0;
77 | width: 100%;
78 | height: 100%;
79 | color: #fff;
80 | box-sizing: border-box;
81 | /* background-color: rgba(0, 0, 0, .5); */
82 | }
83 |
84 | /* .h-nav-tab-page .h-nav-modal {
85 | z-index: 2000;
86 | } */
87 | .h-nav-page-has-modal {
88 | overflow: hidden;
89 | }
90 |
91 | .h-nav-behavior-push > .h-nav-transition,
92 | .h-nav-behavior-back > .h-nav-transition,
93 | .h-nav-behavior-replace > .h-nav-transition{
94 | transition: all .3s ease;
95 | }
96 |
97 | .h-nav-behavior-push > .h-nav-transition > .h-nav-page-enter,
98 | .h-nav-behavior-back > .h-nav-transition > .h-nav-page-leave-to {
99 | transform: translateX(100%);
100 | }
101 |
102 | .h-nav-behavior-replace > .h-nav-transition > .h-nav-page-enter {
103 | transform: translateY(100%);
104 | }
105 |
106 | /* ---------------- behavior push/back start ---------------- */
107 | /* .h-nav-behavior-push > .h-nav-transition,
108 | .h-nav-behavior-back > .h-nav-transition,
109 | .h-nav-behavior-replace > .h-nav-transition{
110 | transition: all .3s ease;
111 | }
112 |
113 | .h-nav-behavior-push > .h-nav-transition > .h-nav-page-enter,
114 | .h-nav-behavior-back > .h-nav-transition > .h-nav-page-leave-to,
115 | .h-nav-behavior-replace > .h-nav-transition > .h-nav-page-enter {
116 | transform: translateX(100%);
117 | } */
118 | /* ---------------- behavior push/back end ---------------- */
--------------------------------------------------------------------------------
/src/fit_vue.js:
--------------------------------------------------------------------------------
1 |
2 | export function uniteVue2(_Vue){
3 |
4 | return {
5 | proto: _Vue.prototype,
6 | nextTick: _Vue.nextTick,
7 | newComponent: function(Cmpt, newOpts) {
8 | const CmptExtend = _Vue.extend(Cmpt);
9 | return new CmptExtend(newOpts);
10 | },
11 | destroy: function(cmpt){
12 | cmpt.$destroy();
13 | },
14 | set: _Vue.set,
15 | delete: _Vue.delete
16 | }
17 | }
18 |
19 | /* FIT_VUE_3_SWITCH
20 | let _Vue3; // createApp, h, nextTick;
21 | export function fitVue3(_Vue){
22 | _Vue3 = _Vue;
23 | }
24 |
25 | export function uniteVue3(app){
26 | return {
27 | proto: app.config.globalProperties,
28 | nextTick: _Vue3.nextTick,
29 | newComponent: (Cmpt, newOpts) => {
30 | let app = _Vue3.createApp(Cmpt, newOpts.propsData);
31 | app.mount(newOpts.el);
32 | return app;
33 | },
34 | destroy: function(cmpt){
35 | cmpt.unmount();
36 | },
37 | set: function(obj, k, v){
38 | obj[k] = v;
39 | },
40 | delete: function(obj, k){
41 | delete obj[k];
42 | }
43 | }
44 | }
45 |
46 | export function vue3Reactive(obj){ // vue3 API
47 | return _Vue3.reactive(obj);
48 | }
49 |
50 | export function vue3FitEmitOnOff(self){
51 | const eMap = self.$options._tmp_e_map = Object.create(null);
52 | self.$on = function(key, handle){
53 | let list = eMap[key];
54 | if(!list){
55 | list = eMap[key] = [];
56 | }
57 | list.push(handle);
58 | }
59 |
60 | self.$off = function(key, handle){
61 | const list = eMap[key];
62 | if(list){
63 | const i = list.indexOf(handle);
64 | if(i !== -1){
65 | list.splice(i, 1);
66 | }
67 | }
68 | if(!list.length){
69 | delete eMap[key];
70 | }
71 | }
72 |
73 | self._uniteEmit = function(key) {
74 | const list = eMap[key];
75 | if(list) {
76 | const len = list.length;
77 | let i = 0;
78 | for(; i < len; i++){
79 | list[i]();
80 | }
81 | }
82 | }
83 | }
84 |
85 | */
--------------------------------------------------------------------------------
/src/h_nav/h_nav.js:
--------------------------------------------------------------------------------
1 |
2 | function H_NAV(opt){
3 | this._opt = Object.assign(Object.create(null), opt);
4 |
5 | if(!this._opt.native){
6 | if(typeof window === undefined){
7 | throw new Error(_wrap_error('window is undefined'));
8 | }
9 | this._navite = {
10 | window: window,
11 | history: window.history,
12 | location: window.location
13 | }
14 | } else {
15 | this._navite = Object.assign(Object.create(null), this._opt.native);
16 | delete this._opt.native;
17 | }
18 | this.init();
19 | }
20 |
21 | const PAGE_KEY = '_h_nav_page';
22 |
23 | H_NAV.prototype.init = function(){
24 | const state = this._navite.history.state || {};
25 | if(!state[PAGE_KEY]){
26 |
27 | }
28 | }
29 |
30 | function _wrap_error(msg){
31 | return 'Error: h_nav ' + msg;
32 | }
--------------------------------------------------------------------------------
/src/install.js:
--------------------------------------------------------------------------------
1 | import NavigationController from './cmpt/navigation-controller.vue';
2 | import DefaultNotFound from './cmpt/default-not-found.vue';
3 | import Navigator from './cmpt/navigator.vue';
4 | import navigator from './navigator/navigator';
5 | import { trimSlash } from './navigator/url';
6 | import ShowHideMixin from './mixin/show-hide-mixin';
7 | import { def, throwErr, getVueV, noop } from './util';
8 | import { uniteVue2} from './fit_vue';
9 | import {cmptPageSuffix,
10 | notFoundPageKey,
11 | DEF_NAVIGATOR_TRIGGER_EVENT,
12 | DEF_URL_BASE,
13 | DEF_URL_IS_HASH_MODE,
14 | DEF_TRANSITION,
15 | DEF_PAGE_STYLE } from './constant';
16 |
17 |
18 | const defNotFoundPage = {
19 | title: 'Not Found',
20 | component: DefaultNotFound
21 | }
22 |
23 | export default function install(_Vue, config) {
24 | const vueV = getVueV(_Vue);
25 | let uniteVue;
26 | if(vueV === 2){
27 | uniteVue = uniteVue2(_Vue);
28 | // } else if(vueV === 3){ // FIT_VUE_3_SWITCH
29 | // uniteVue = uniteVue$3(_Vue);
30 | // uniteVue.is3 = true;
31 | } else {
32 | throwErr('Unsupported version of Vue ' + vueV);
33 | }
34 |
35 |
36 | if(!Array.isArray(config.pages)){
37 | throwErr('config.pages is not Array.');
38 | }
39 |
40 | const pageMap = _formatPages(config.pages);
41 | let tabBar;
42 | if(config.tabBar){
43 | tabBar = _formatTabBar(config.tabBar, pageMap);
44 | }
45 |
46 | let notFoundPage = config.notFoundPage || defNotFoundPage;
47 | _Vue.component(notFoundPageKey, notFoundPage.component);
48 |
49 | notFoundPage = {
50 | path: null,
51 | title: notFoundPage.title,
52 | cmptKey: notFoundPageKey,
53 | isTab: false,
54 |
55 | trimedPath: null
56 | }
57 |
58 | let i, page;
59 | for(i in pageMap){
60 | page = pageMap[i];
61 | _Vue.component(page.cmptKey, page.component);
62 | }
63 |
64 | _Vue.component('NavigationController', NavigationController);
65 | _Vue.component('Navigator', Navigator);
66 |
67 | const globalOption = Object.create(null);
68 | def(globalOption, config, 'navigatorTriggerEvent', DEF_NAVIGATOR_TRIGGER_EVENT);
69 | def(globalOption, config, 'transition', DEF_TRANSITION);
70 | def(globalOption, config, 'pageStyle', DEF_PAGE_STYLE);
71 | def(globalOption, config, 'homePagePath', config.pages[0].path);
72 | const homePage = pageMap[trimSlash(globalOption.homePagePath)];
73 |
74 | if(!homePage){
75 | throwErr('Home page not found');
76 | }
77 |
78 | homePage.isHome = true;
79 |
80 | let BAE = null;
81 | if(config.backAgainToExit){
82 | BAE = Object.create(null);
83 | def(BAE, config.backAgainToExit, 'maxInterval', 2000);
84 | def(BAE, config.backAgainToExit, 'onFirstTrigger', noop);
85 | }
86 |
87 | const options = {
88 | global: globalOption,
89 | BAE,
90 | uniteVue,
91 | pageMap,
92 | cmptPageSuffix,
93 | notFoundPage,
94 | tabBar,
95 | onRouted: config.onRouted
96 | }
97 |
98 | def(options, config, 'urlBase', DEF_URL_BASE);
99 | def(options, config, 'urlIsHashMode', DEF_URL_IS_HASH_MODE);
100 |
101 | uniteVue.proto.$navigator = navigator(options);
102 | _Vue.mixin(ShowHideMixin(uniteVue.is3));
103 | }
104 |
105 | function _formatPages(pages){
106 |
107 | let map = Object.create(null);
108 | let i = 0, len = pages.length, page, tk, fpage;
109 | for(; i < len; i++){
110 | page = pages[i];
111 | tk = trimSlash(page.path);
112 | if(map[tk]){
113 | throwErr(`pageMap key: ${tk} is same as ${page.path}`);
114 | }
115 | fpage = Object.assign({}, page);
116 |
117 | Object.assign(fpage, {
118 | trimedPath: tk,
119 | isTab: false,
120 | isHome: false,
121 | cmptKey: cmptPageSuffix + i
122 | });
123 |
124 | map[tk] = fpage;
125 | }
126 | return map;
127 | }
128 |
129 |
130 | function _formatTabBar(tabBar, pageMap){
131 | const list = tabBar.list;
132 | let len = list.length;
133 | if(len < 2){
134 | throwErr(`tabBar list length must >= 2`);
135 | }
136 | let i = 0, tk, item;
137 |
138 | const map = Object.create(null);
139 | const newList = [];
140 | for(; i < len; i++){
141 | item = list[i];
142 | tk = trimSlash(item.pagePath);
143 | const page = pageMap[tk];
144 | if(!page || page.path !== item.pagePath){
145 | throwErr(`tabBar pagePath: ${i} is not found in pages`);
146 | }
147 | if(map[tk]){
148 | throwErr(`tabBar pagePath: ${tk} is same as ${i}`);
149 | }
150 | page.isTab = true;
151 | page.tabIndex = i;
152 | map[tk] = true;
153 | newList.push(Object.assign({}, item));
154 | }
155 | return {
156 | map,
157 | list: newList
158 | };
159 | }
160 |
161 |
162 |
--------------------------------------------------------------------------------
/src/mixin/show-hide-mixin.js:
--------------------------------------------------------------------------------
1 | import { PAGE_E_SHOW_NAME, PAGE_E_HIDE_NAME } from '../constant';
2 | export default function (isV3) {
3 | const key = isV3 ? 'unmounted' : 'destroyed';
4 | return {
5 | inject: {
6 | $page: {
7 | default: null
8 | }
9 | },
10 | created(){
11 | if((this.$options.onShow || this.$options.onHide) && this.$page){
12 | _handleOnShow(this);
13 | }
14 | },
15 | [key](){
16 | if(this.$options.__tmp_h_nav_show_hide_has){
17 | _handleOnHide(this);
18 | }
19 | }
20 | }
21 | }
22 |
23 | function _handleOnShow(self){
24 | const $opts = self.$options;
25 | $opts.__tmp_h_nav_show_hide_has = true;
26 | let handle;
27 | if($opts.onShow){
28 | handle = $opts.__tmp_h_nav_page_show_hanlde = () => {
29 | $opts.onShow.call(self);
30 | }
31 | self.$page.$on(PAGE_E_SHOW_NAME, handle);
32 | }
33 | if($opts.onHide){
34 | handle = $opts.__tmp_h_nav_page_hide_hanlde = () => {
35 | $opts.onHide.call(self);
36 | }
37 | self.$page.$on(PAGE_E_HIDE_NAME, handle);
38 | }
39 | }
40 |
41 | function _handleOnHide(self){
42 | const $opts = self.$options;
43 | let handle = $opts.__tmp_h_nav_page_show_hanlde;
44 | if(handle){
45 | self.$page.$off(PAGE_E_SHOW_NAME, handle);
46 | }
47 | handle = $opts.__tmp_h_nav_page_hide_hanlde;
48 | if(handle){
49 | self.$page.$off(PAGE_E_HIDE_NAME, handle);
50 | }
51 | }
--------------------------------------------------------------------------------
/src/navigator/history.js:
--------------------------------------------------------------------------------
1 | import { nativeWindow, nativeHistory, nativeLocation } from './native';
2 | import URL, { fullUrlParse } from './url';
3 | import { getCurrentStateKey, genStateKey, getCurrModaKey, getPreState, updatePreState, getCurrState } from './state-key';
4 | import { KEY_NAME, BACK_TRA_PROP_KEY } from '../constant';
5 | import { noop, throwErr } from '../util';
6 | import modalPart from './libs/modal';
7 | import BAEPart from './libs/bae';
8 | import BackPart from './libs/back';
9 | let isCreated = false;
10 |
11 | function History(opt){
12 | if(isCreated){
13 | throwErr('Only one instance can be generated.');
14 | }
15 | isCreated = true;
16 | this._global = opt.global;
17 | this._window = nativeWindow;
18 | this._history = nativeHistory;
19 | this._location = nativeLocation;
20 | this._isOmitForwardEvent = false;
21 | this.BAE = opt.BAE;
22 | // this._exitImmediately = true;
23 | // this.onExit = opt.onExit; // Chrome must touch the document once to work.
24 | this._tra = {className: this._global.transition};
25 | this.uniteVue = opt.uniteVue;
26 | this.pageMap = opt.pageMap;
27 | this.notFoundPage = opt.notFoundPage;
28 | if(opt.tabBar){
29 | this.tabMap = opt.tabBar.map;
30 | this.tabList = opt.tabBar.list;
31 | this.tabStackMap = Object.create(null);
32 | }
33 | this.stackMap = Object.create(null);
34 |
35 | this._stackItemId = 1;
36 | this.tabCtrlerStackId = 1;
37 | // this.isPageDestroyWhenBack = true;
38 | this.onRouted = opt.onRouted || noop;
39 |
40 | this.URL = new URL({isHashMode: opt.urlIsHashMode, base: opt.urlBase});
41 |
42 | this.behavior = {
43 | type: '',
44 | distance: 0,
45 | isPop: false
46 | }
47 |
48 | this.currentPage = {
49 | path: null,
50 | title: null,
51 | className: undefined,
52 |
53 | isHome: false,
54 | isTab: false,
55 | tabIndex: null,
56 | cmptKey: null,
57 |
58 | stackId: null,
59 |
60 | stateKey: null,
61 | modalList: [],
62 | isClean: false,
63 | route: {}
64 | }
65 | BackPart.init.apply(this);
66 | modalPart.init.apply(this);
67 | // this.fitVue$3(); // FIT_VUE_3_SWITCH
68 | }
69 |
70 | History.prototype.checkCompatibility = function(){
71 | if(this._history && this._history.pushState){
72 | return true;
73 | }
74 | return false;
75 | }
76 |
77 | History.prototype._bind = function(){
78 | this._popstateHandle = () => {
79 | this.handlePop();
80 | }
81 | this._window.addEventListener('popstate', this._popstateHandle);
82 |
83 | this._handleWinUnload = () => {
84 | this.handleWinUnload();
85 | }
86 | this._window.addEventListener('beforeunload', this._handleWinUnload);
87 | }
88 |
89 | History.prototype._onRouted = function(){
90 | const curr = this.currentPage;
91 | this.onRouted({
92 | title: curr.title,
93 | routeFullPath: curr.route.fullPath
94 | });
95 | this._autoBAE(curr.route.trimedPath);
96 | }
97 |
98 | History.prototype._genStackItemId = function(){
99 | this._stackItemId = this._stackItemId + 1;
100 | return 'stack_' + this._stackItemId;
101 | }
102 |
103 | History.prototype._isTabRoute = function(trimedPath){
104 | if(!this.tabMap){
105 | return false;
106 | }
107 | if(this.tabMap[trimedPath]){
108 | return true;
109 | }
110 | return false;
111 | }
112 |
113 |
114 | History.prototype._forMatInputArg = function(opt){
115 | let fullParse, tra;
116 | if(typeof opt === 'string'){
117 | fullParse = fullUrlParse(opt);
118 | } else {
119 | fullParse = fullUrlParse({
120 | path: opt.url,
121 | query: opt.query
122 | })
123 | tra = opt.transition;
124 | }
125 | return {
126 | fullParse,
127 | tra
128 | };
129 | }
130 |
131 |
132 | History.prototype._load = function(userUrl){
133 | console.log('load', this.BAE, this.pageMap);
134 | this._bind();
135 | const _userUrl = userUrl === undefined ?
136 | this.URL.getUrlByLocation() :
137 | userUrl;
138 | const currRoute = fullUrlParse(_userUrl);
139 | const key = getCurrentStateKey();
140 | if(key !== 1){
141 | if(this._isTabRoute(currRoute.trimedPath)){
142 | this._backToStartAndReplace(currRoute, 'loaded');
143 | return;
144 | }
145 | }
146 | this._replaceCurrPage(currRoute, 'loaded');
147 | }
148 |
149 | History.prototype._replaceCurrPage = function(){
150 | const modalCount = getCurrModaKey();
151 | if(modalCount){
152 | this._backAndApply(modalCount, '_replace', arguments);
153 | return;
154 | }
155 | this._replace.apply(this, arguments);
156 | }
157 |
158 | History.prototype._setTra = function(className){
159 | this._tra.className = className || this._global.transition;
160 | }
161 |
162 | History.prototype.push = function(userUrl){
163 | const { fullParse, tra } = this._forMatInputArg(userUrl);
164 | if(this._isTabRoute(fullParse.trimedPath)){
165 | console.error('Cannot push the tab url, please use switchTab');
166 | return;
167 | }
168 | this._setTra(tra);
169 | this._push(fullParse, tra);
170 | }
171 |
172 | History.prototype.replace = function(userUrl){
173 | const { fullParse, tra } = this._forMatInputArg(userUrl);
174 | if(this._isTabRoute(fullParse.trimedPath)){
175 | console.error('Cannot replace the tab url, please use switchTab');
176 | return;
177 | }
178 | this._setTra(tra);
179 | this._replaceCurrPage(fullParse);
180 | }
181 |
182 |
183 |
184 | History.prototype.switchTab = function(userUrl){
185 | // if(userUrl.indexOf('?') !== -1){
186 | // console.error('switchTab not support queryString');
187 | // }
188 | const { fullParse, tra } = this._forMatInputArg(userUrl);
189 | if(!this._isTabRoute(fullParse.trimedPath)){
190 | console.error(fullParse, ' is not tab url');
191 | return;
192 | }
193 |
194 | this._backToStartAndReplace(fullParse, 'switchtab', tra);
195 | }
196 |
197 | History.prototype.relaunch = function(userUrl){
198 | const { fullParse, tra } = this._forMatInputArg(userUrl);
199 | this.tabCtrlerStackId = this.tabCtrlerStackId + 1;
200 | this._backToStartAndReplace(fullParse, 'relaunch', tra);
201 | }
202 |
203 | History.prototype._setMapItem = function(key, route){
204 |
205 |
206 | let page = this.pageMap[route.trimedPath] || this.notFoundPage;
207 | const _page = {
208 |
209 | path: page.path,
210 | title: page.title,
211 | tabIndex: page.tabIndex,
212 | route,
213 | cmptKey: page.cmptKey,
214 | isHome: page.isHome,
215 | isTab: page.isTab,
216 | stateKey: key,
217 | className: page.className,
218 | style: page.style,
219 | modalList: [],
220 | isClean: false // when curr page leaveing, It doesn't work.
221 | }
222 |
223 | if(_page.isTab){
224 | _page.stackId = 'tab_stack_' + this.tabCtrlerStackId;
225 | this.uniteVue.set(this.tabStackMap, page.tabIndex, _page);
226 | } else {
227 | _page.stackId = this._genStackItemId();
228 | }
229 |
230 |
231 |
232 |
233 | // Object.assign(this.currentPage, _page);
234 | this.uniteVue.set(this.stackMap, key, _page);
235 | Object.assign(this.currentPage, _page);
236 | }
237 | History.prototype._getBackTra = function(){
238 | const H = this._history;
239 | if(H.state){
240 | return H.state[BACK_TRA_PROP_KEY];
241 | }
242 | }
243 |
244 | History.prototype._push = function(fullParse, tra){
245 | this._autoBAE();
246 | /*
247 | from [vue-router]
248 | try...catch the pushState call to get around Safari
249 | DOM Exception 18 where it limits to 100 pushState calls
250 | */
251 | // this._clearAf2ter();
252 | const oldTra = this._getBackTra();
253 |
254 | if(tra !== oldTra){
255 | let state = Object.assign({}, this._history.state);
256 | if(tra){
257 | state[BACK_TRA_PROP_KEY] = tra;
258 | } else {
259 | delete state[BACK_TRA_PROP_KEY];
260 | }
261 |
262 | this._history.replaceState(state, '');
263 | }
264 | const key = genStateKey();
265 |
266 |
267 | this._setModalCrumbsWhenChange();
268 | this._history.pushState({[KEY_NAME]: key}, '', this.URL.toLocationUrl(fullParse.fullPath));
269 |
270 | updatePreState();
271 |
272 | const newBehavior = {
273 | type: 'push',
274 | distance: 1,
275 | isPop: false
276 | }
277 | Object.assign(this.behavior, newBehavior);
278 |
279 | this._setMapItem(key, fullParse);
280 |
281 | this._onRouted();
282 | }
283 |
284 | // History.prototype._replaceCurrPage = function(fullParse, behavior, _dista2nce){
285 | // const isBAE = this._isBAEPage();
286 | // const isDistBAE = this._isBAEPageByTK(fullUrlParse);
287 | // let step = this.get2CurrModaKey();
288 | // if(isBAE && !isDistBAE){
289 | // step = step + 1;
290 | // }
291 | // if(step){
292 | // this._backAndApply(step, '_repl2ace', arguments);
293 | // return;
294 | // }
295 | // this._repl2ace.apply(this, arguments);
296 | // }
297 |
298 |
299 | History.prototype._replace = function(fullParse, behavior){
300 | const preKey = getPreState().key;
301 | const key = getCurrentStateKey();
302 | const distance = key - preKey;
303 | const newBehavior = {
304 | type: behavior || 'replace',
305 | distance,
306 | isPop: false
307 | }
308 | Object.assign(this.behavior, newBehavior);
309 | if(this.behavior.type !== 'switchtab' || distance){
310 | // unactive currentPage
311 | this.currentPage.stackId = 'unactive_' + this.currentPage.stackId;
312 | }
313 | const toUrl = this.URL.toLocationUrl(fullParse.fullPath);
314 | let state = this._history.state;
315 | if(!state || !state[KEY_NAME]){
316 | state = Object.assign({}, history.state);
317 | state[KEY_NAME] = key;
318 | }
319 |
320 | this._history.replaceState(state, '', toUrl);
321 | updatePreState();
322 |
323 | this.uniteVue.nextTick(() => {
324 | if(newBehavior.type === 'relaunch'){
325 | this._setAllCleaned();
326 | const oldKey = key - distance;
327 | this.stackMap[oldKey].isClean = false;
328 | this.uniteVue.nextTick(() => {
329 | this._clearAll();
330 | this._setMapItem(key, fullParse);
331 | this._onRouted();
332 | })
333 |
334 | } else {
335 | this._clearAfter();
336 | this._setMapItem(key, fullParse);
337 | this._onRouted();
338 | }
339 | });
340 | }
341 |
342 |
343 |
344 |
345 |
346 | History.prototype._setMapCleaned = function(map){
347 | for(let i in map){
348 | map[i].isClean = true;
349 | }
350 | }
351 | History.prototype._setAllCleaned = function(){
352 | if(this.tabStackMap){
353 | this._setMapCleaned(this.tabStackMap);
354 | }
355 | this._setMapCleaned(this.stackMap);
356 | }
357 |
358 |
359 |
360 | // History.prototype._backToStartAndRe2place = function(fullParse, behavior, tra){
361 | // const key = getCurrentStateKey();
362 | // if(key > 1){
363 | // this._whenPopInfo = {
364 | // fullParse,
365 | // behavior
366 | // };
367 | // this.back(key - 1, tra);
368 | // } else {
369 | // this._setTra(tra);
370 | // this._rep2lace(fullParse, behavior);
371 | // }
372 | // }
373 |
374 | History.prototype._clearAfter = function(){
375 | const key = getCurrentStateKey();
376 | const map = this.stackMap;
377 | let i, v;
378 | const arr = [];
379 | for (i in map) {
380 | v = map[i];
381 | if (v.stateKey > key) {
382 | v.isClean = true;
383 | arr.push(v);
384 | }
385 | }
386 | let len = arr.length;
387 | if(len){
388 | const last = arr.pop();
389 | last.isClean = false;
390 | this.uniteVue.delete(map, last.stateKey);
391 | len = arr.length;
392 | if(len){
393 | this.uniteVue.nextTick(() => {
394 | i = 0;
395 | for(; i < len; i++){
396 | v = arr[i];
397 | this.uniteVue.delete(map, v.stateKey);
398 | }
399 | })
400 | }
401 | }
402 | }
403 |
404 | History.prototype._clearMap = function(map){
405 | let i;
406 | for(i in map){
407 | this.uniteVue.delete(map, i);
408 | }
409 | }
410 |
411 | History.prototype._clearAll = function(){
412 | if(this.tabStackMap){
413 | this._clearMap(this.tabStackMap);
414 | }
415 | this._clearMap(this.stackMap);
416 | }
417 |
418 |
419 | History.prototype.handlePop = function(){
420 | if(this._isOmitForwardEvent){
421 | this._isOmitForwardEvent = false;
422 | return;
423 | }
424 | console.log('[handlePop]');
425 | let _backInfo = this._whenBackPopInfo;
426 | if(_backInfo){
427 | this[_backInfo.method].apply(this, _backInfo.args);
428 | // this._set2ModalCrumbsWhenChange();
429 | this._whenBackPopInfo = null;
430 | return;
431 | }
432 |
433 | const preState = getPreState();
434 | const preKey = preState.key;
435 |
436 | if(!this._history.state){ // The user manually modifies the browser address bar
437 | let _popPushKey = preKey + 1;
438 | this._history.replaceState({[KEY_NAME]: _popPushKey}, '');
439 | updatePreState();
440 | this._setTra('');
441 | this._replace(fullUrlParse(this.URL.getUrlByLocation()), '_popPush');
442 | this._setModalCrumbsWhenChange();
443 | return;
444 | }
445 |
446 | const total = this._getStepsTotal(preState);
447 |
448 | if(total > 0){
449 | console.log('forward', total);
450 | console.error('Forward is disabled by history-navigation-vue');
451 | this._isOmitForwardEvent = true;
452 | this.back(total);
453 | return;
454 | }
455 |
456 | const currState = getCurrState();
457 | const currKey = currState.key;
458 |
459 | const page = this.stackMap[currKey];
460 | if(!page && currState.modalKey){
461 | this.back(currState.modalKey);
462 | return;
463 | }
464 |
465 | this._autoRemoveModal();
466 |
467 | updatePreState();
468 |
469 | if(preKey === currKey) {
470 | return;
471 | }
472 |
473 | // if(preKey === currKey){
474 | // const modalKey = getCurrModaKey();
475 | // const preModalKey = this.getLastModalKeyByCrumbs(preKey);
476 | // if(modalKey > preModalKey){
477 | // this._isOmitForwardEvent = true;
478 | // this.back(modalKey - preModalKey);
479 | // return;
480 | // }
481 | // this.removeModal();
482 | // // this._set2ModalCrumbsWhenChange();
483 | // return;
484 | // }
485 |
486 | const compare = currKey - preKey;
487 |
488 | let backTra = this._whenPopTra;
489 | if(!backTra && compare === -1){
490 | backTra = this._getBackTra();
491 | // console.log('--------------- backTra ---------------', backTra);
492 | }
493 | this._setTra(backTra);
494 | this._whenPopTra = null;
495 |
496 |
497 | const newBehavior = {
498 | type: 'back',
499 | distance: compare,
500 | isPop: true
501 | }
502 | Object.assign(this.behavior, newBehavior);
503 |
504 | if(page){
505 | Object.assign(this.currentPage, page);
506 | } else {
507 | this._setMapItem(currKey, fullUrlParse(this.URL.getUrlByLocation()));
508 | }
509 | this.uniteVue.nextTick(() => {
510 | this._clearAfter();
511 | this._onRouted();
512 | })
513 | }
514 |
515 |
516 | History.prototype.destroy = function(){
517 | if(isCreated){
518 | if(this._popstateHandle){
519 | this._window.removeEventListener('popstate', this._popstateHandle);
520 | }
521 | if(this._handleWinUnload){
522 | this._window.removeEventListener('_handleWinUnload', this._handleWinUnload);
523 | }
524 | isCreated = false;
525 | }
526 | }
527 |
528 | History.prototype.handleWinUnload = function(){
529 | this._saveModalCrumbs();
530 | }
531 |
532 |
533 |
534 | Object.assign(History.prototype, modalPart.proto);
535 | Object.assign(History.prototype, BAEPart.proto);
536 | Object.assign(History.prototype, BackPart.proto);
537 |
538 | // History.prototype.fitVue$3 = function(){ // FIT_VUE_3_SWITCH
539 | // if(this.uniteVue.is3){
540 | // let v;
541 | // ['stackMap', 'behavior', 'currentPage', '_tra', 'tabList', 'tabStackMap'].forEach(k => {
542 | // v = this[k];
543 | // if(v){
544 | // this[k] = vue$3Reactive(v)
545 | // }
546 | // })
547 | // }
548 | // }
549 |
550 | export default History;
551 |
552 | // window.addEventListener('beforeunload', function(event){
553 | // console.log('beforeunload')
554 | // event.preventDefault();
555 | // event.returnValue = "Are you sure you want to exit?"
556 | // return 'beforeunload';
557 | // })
--------------------------------------------------------------------------------
/src/navigator/libs/back.js:
--------------------------------------------------------------------------------
1 | import { getCurrentStateKey, getCurrState } from '../state-key';
2 | import { _getTotalSteps } from '../../util';
3 | export default {
4 | init(){
5 | this._whenPopTra = null;
6 | this._whenPopInfo = null;
7 | this._whenBackPopInfo = null;
8 | },
9 | proto: {
10 | _getStepsTotal(distState){
11 | const currState = getCurrState();
12 | const { key, modalKey } = currState;
13 | const distKey = distState.key;
14 | const distModalKey = distState.modalKey;
15 | let count = key - distKey;
16 | if(count === 0){
17 | return modalKey - distModalKey;
18 | }
19 | const arr = this._modal_crumbs;
20 | let start, end;
21 | const isBack = count > 0;
22 | if(isBack){
23 | start = distState;
24 | end = currState;
25 | } else {
26 | start = currState;
27 | end = distState;
28 | }
29 | let total = _getTotalSteps(arr, start, end);
30 | if(!isBack){
31 | total = -total;
32 | }
33 | return total;
34 | },
35 | // _getTotalFromPreState(){
36 | // return this._getStepsTotal({key: 1, modalKey: 0});
37 | // },
38 | _backGetTo1Count(){
39 | return this._getStepsTotal({key: 1, modalKey: 0});
40 | },
41 | back(_steps, tra){
42 | // const key = getCurrentStateKey();
43 | // const modalCount = this.get2ModalStepsTotal();
44 | // console.log('modalCount', modalCount, key)
45 | // const total = key + modalCount;
46 | let steps = _steps || 1;
47 | if(steps < 1){
48 | return;
49 | }
50 | // if(typeof steps === 'number' && steps > 0){
51 | // if(total - steps < 1){
52 | // steps = total - 1;
53 | // }
54 | // }
55 | // console.log('[back] steps', steps);
56 | this._whenPopTra = tra;
57 | if(steps){
58 | this._history.go(-steps);
59 | }
60 | },
61 | backToPage(_steps, tra){
62 | const arr = this._modal_crumbs;
63 | console.log('[backToPage] _modal_crumbs', arr);
64 | if(!arr.length){
65 | this.back(_steps, tra);
66 | return;
67 | }
68 | const key = getCurrentStateKey();
69 | const distKey = key - _steps;
70 | let i = 0, v, count = 0;
71 | const max = arr.length;
72 | for(; i < max; i++){
73 | v = arr[i];
74 | if(v[0] > distKey){
75 | count = count + v[1];
76 | } else {
77 | break;
78 | }
79 | }
80 | this.back(_steps + count, tra);
81 |
82 | },
83 | _backAndApply(steps, method, args, tra){
84 | if(steps < 1){
85 | return;
86 | }
87 | this._whenBackPopInfo = {
88 | method,
89 | args
90 | }
91 | this.back(steps, tra);
92 | },
93 |
94 | _backToStartAndReplace(fullParse, behavior, tra){
95 | const total = this._backGetTo1Count();
96 | // console.log('_backToStartAndReplace', total, this._modal_crumbs);
97 | // return;
98 | if(total > 0){
99 | this._backAndApply(total, '_replace', [fullParse, behavior], tra);
100 | } else if(total === 0){
101 | this._setTra(tra);
102 | this._replace(fullParse, behavior);
103 | } else {
104 | console.error('_backToStartAndReplace not back', total);
105 | }
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/navigator/libs/bae.js:
--------------------------------------------------------------------------------
1 | import { getCurrentStateKey } from '../state-key';
2 | import { MODAL_BAE_KEY, MODAL_KEY_NAME } from '../../constant';
3 | export default {
4 | proto: {
5 | _isBAEPage(){
6 | if(this.BAE){
7 | const key = getCurrentStateKey();
8 | const page = this.stackMap[key];
9 | if(key === 1 && page && (page.isHome || page.isTab)){
10 | return true;
11 | }
12 | }
13 | return false;
14 | },
15 | _isBAEPageByTK(tk){
16 | if(this.BAE){
17 | const key = getCurrentStateKey();
18 | const page = this.pageMap[tk];
19 | if(key === 1 && page && (page.isHome || page.isTab)){
20 | return true;
21 | }
22 | }
23 | return false;
24 | },
25 | _isNeedBAE(){
26 | if(this._isBAEPage()){
27 | const state = this._history.state;
28 | if(state && !state[MODAL_KEY_NAME]){
29 | return true;
30 | }
31 | }
32 | return false;
33 | },
34 | _autoBAE(){
35 | const isN = this._isNeedBAE();
36 | console.log('_autoBAE', isN);
37 | if(isN){
38 | this.modal(MODAL_BAE_KEY);
39 | }
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/src/navigator/libs/modal.js:
--------------------------------------------------------------------------------
1 | import { getCurrentStateKey, getCurrModaKey, updatePreState } from '../state-key';
2 | import { KEY_NAME, MODAL_BAE_KEY, MODAL_CRUMBS_KEY_NAME, MODAL_KEY_NAME } from '../../constant';
3 | import { _cutOffAndPush } from '../../util';
4 | // import { nativeDocument } from '../native';
5 |
6 | export default {
7 | init(){
8 | this._modal_crumbs = null;
9 | this._initModalCrumbs();
10 | console.log('[_initModalCrumbs]', this._modal_crumbs);
11 | },
12 | proto: {
13 | modal({component, propsData, parent, success}){
14 | const isBAEModal = arguments[0] === MODAL_BAE_KEY;
15 | if(!isBAEModal){
16 | this._autoBAE();
17 | }
18 | const key = getCurrentStateKey();
19 | const page = this.stackMap[key];
20 | let modalKey = getCurrModaKey();
21 | modalKey = modalKey + 1;
22 | this._history.pushState({[KEY_NAME]: key, [MODAL_KEY_NAME]: modalKey}, '');
23 | this._setModalCrumbsWhenChange();
24 | updatePreState();
25 |
26 | const item = {
27 | key: modalKey,
28 | uid: _genModalKey()
29 | }
30 | page.modalList.push(item);
31 | if(isBAEModal){
32 | item.isBAE = true;
33 | return;
34 | }
35 | const id = 'h_nav_modal_' + item.uid;
36 | if(component){
37 | this.uniteVue.nextTick(() => {
38 | if(!item._isDestroy){
39 | // const Cmpt = this.uniteVue.extend(component);
40 | // const cmpt = new Cmpt({
41 | // el: '#' + id,
42 | // parent,
43 | // propsData
44 | // });
45 | const cmpt = this.uniteVue.newComponent(component, {
46 | el: '#' + id,
47 | parent,
48 | propsData
49 | });
50 | item._destoryCmpt = () => {
51 | this.uniteVue.destroy(cmpt);
52 | };
53 | success && success(cmpt);
54 | }
55 | });
56 | }
57 | return id;
58 | },
59 | removeModal(){
60 | const currKey = getCurrentStateKey();
61 | const modalKey = getCurrModaKey();
62 | const page = this.stackMap[currKey];
63 | let arr;
64 | if(page){
65 | arr = page.modalList.splice(modalKey);
66 | arr.forEach(item => {
67 | item._isDestroy = true;
68 | if(item._destoryCmpt){
69 | item._destoryCmpt();
70 | }
71 | })
72 | }
73 | if(modalKey === 0){
74 | if(this._isNeedBAE()){
75 | if(arr && arr.length > 1){
76 | console.log('[removeModal] step not 1', arr.length);
77 | this._autoBAE();
78 | } else {
79 | this.BAE.onFirstTrigger();
80 | setTimeout(() => {
81 | this._autoBAE();
82 | }, this.BAE.maxInterval);
83 | }
84 | }
85 | }
86 | },
87 | _autoRemoveModal(){
88 | const key = getCurrentStateKey();
89 | const page = this.stackMap[key];
90 | const modalKey = getCurrModaKey();
91 | if(page && page.modalList.length > modalKey){
92 | this.removeModal();
93 | }
94 | },
95 | _setModalCrumbsWhenChange(){
96 | const key = getCurrentStateKey();
97 | const modalKey = getCurrModaKey();
98 | this._setModalCrumbs(key, modalKey);
99 | },
100 | _initModalCrumbs(){
101 | const h = this._history;
102 | const state = Object.assign({}, h.state);
103 | const v = state[MODAL_CRUMBS_KEY_NAME];
104 | if(v){
105 | delete state[MODAL_CRUMBS_KEY_NAME];
106 | this._history.replaceState(state, '');
107 | this._modal_crumbs = _parse(v);
108 | } else {
109 | const modalKey = getCurrModaKey();
110 | if(modalKey){
111 | this._modal_crumbs = [[getCurrentStateKey(), modalKey]];
112 | } else {
113 | this._modal_crumbs = [];
114 | }
115 |
116 | }
117 | },
118 | _setModalCrumbs(stateKey, modalCount){
119 | const arr = this._modal_crumbs;
120 | _cutOffAndPush(arr, stateKey, modalCount);
121 | },
122 |
123 | _saveModalCrumbs(){
124 | console.log('_saveModalCrumbs');
125 | if(this._modal_crumbs.length){
126 | const h = this._history;
127 | const state = h.state || {};
128 | state[MODAL_CRUMBS_KEY_NAME] = _format(this._modal_crumbs);
129 | this._history.replaceState(state, '');
130 | }
131 | },
132 | getLastModalKeyByCrumbs(stateKey){
133 | const crumbs = this._modal_crumbs;
134 | const last = crumbs[crumbs.length - 1];
135 | if(last){
136 | if(last[0] === stateKey){
137 | return last[1];
138 | }
139 | }
140 | return 0;
141 | }
142 | }
143 | }
144 |
145 | function _parse(str){
146 | // STATE_KEY:MODAL_COUNT;...
147 | const arr = str.split(';');
148 | const result = [];
149 | let i = 0, v;
150 | const MAX = arr.length;
151 | for(; i < MAX; i++){
152 | v = arr[i].split(':');
153 | result.push([Number(v[0]), Number(v[1])]);
154 | }
155 | return result;
156 | }
157 |
158 | function _format(arr){
159 | return arr.map(function(v) {
160 | console.log('_format', v);
161 | return v.join(':');
162 | }).join(';');
163 | }
164 |
165 |
166 | let _modalKey = 0;
167 | function _genModalKey(){
168 | _modalKey = _modalKey + 1;
169 | return _modalKey;
170 | }
--------------------------------------------------------------------------------
/src/navigator/native.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | let nativeWindow, nativeHistory, nativeLocation;
4 | // , nativeRAF
5 | if(typeof window !== 'undefined'){
6 | nativeWindow = window;
7 | nativeHistory = window.history;
8 | nativeLocation = window.location;
9 | // nativeRAF = window.requestAnimationFrame || setTimeout;
10 | }
11 | // if(typeof document !== 'undefined'){
12 | // nativeDocument = document;
13 | // }
14 | // else {
15 | // // nativeRAF = function(cb){cb()};
16 | // // nativeRAF = setTimeout;
17 | // }
18 |
19 |
20 | export {nativeWindow, nativeHistory, nativeLocation}
21 |
--------------------------------------------------------------------------------
/src/navigator/navigator.js:
--------------------------------------------------------------------------------
1 | import History from './history';
2 |
3 | export default function(opt){
4 | const h = new History(opt);
5 | const obj = {
6 | _h: h,
7 | URL: h.URL,
8 | // vueIs3: h.uniteVue.is3, FIT_VUE_3_SWITCH
9 | GLOBAL_CONFIG: h._global
10 | };
11 |
12 | ['push', 'back', 'backToPage', 'replace', 'relaunch', 'switchTab', 'modal'].forEach(k => {
13 | _bindToOther(obj, k, h);
14 | });
15 |
16 | return obj;
17 |
18 | }
19 |
20 | function _bindToOther(obj, attr, h){
21 | obj[attr] = function(){
22 | h[attr].apply(h, arguments);
23 | }
24 | }
--------------------------------------------------------------------------------
/src/navigator/state-key.js:
--------------------------------------------------------------------------------
1 | import { nativeHistory } from './native';
2 | import { KEY_NAME, MODAL_KEY_NAME } from '../constant';
3 |
4 | export function getCurrentStateKey () {
5 | const state = nativeHistory.state;
6 | if (state && typeof state[KEY_NAME] === 'number') {
7 | return state[KEY_NAME];
8 | }
9 | return 1;
10 | }
11 |
12 | export function getCurrModaKey(){
13 | const state = nativeHistory.state;
14 | if(state && typeof state[MODAL_KEY_NAME] === 'number'){
15 | return state[MODAL_KEY_NAME];
16 | }
17 | return 0;
18 | }
19 |
20 | export function genStateKey () {
21 | return getCurrentStateKey() + 1;
22 | }
23 |
24 | export function getCurrState() {
25 | return {
26 | key: getCurrentStateKey(),
27 | modalKey: getCurrModaKey()
28 | };
29 | }
30 |
31 | let _preState = getCurrState();
32 |
33 | export function getPreState() {
34 | return Object.assign({}, _preState);
35 | }
36 |
37 | function _setPreState(state) {
38 | _preState = Object.assign({}, state);
39 | }
40 |
41 | export function updatePreState() {
42 | _setPreState(getCurrState());
43 | }
44 |
45 | // let _preKey = getCurrentStateKey();
46 | // export function get2PreStateKey () {
47 | // return _preKey;
48 | // }
49 |
50 | // export function set2PreStateKey (key) {
51 | // _preKey = key;
52 | // }
53 |
54 |
--------------------------------------------------------------------------------
/src/navigator/url.js:
--------------------------------------------------------------------------------
1 | import { nativeLocation } from './native';
2 | function URL({isHashMode = true, base = ''}){
3 |
4 | this.isHashMode = isHashMode;
5 |
6 | this._location = nativeLocation;
7 | if(isHashMode){
8 | this.hashBase = this._location.pathname + this._location.search + '#';
9 | } else {
10 | this.base = base;
11 | }
12 |
13 | }
14 |
15 | URL.prototype.getHashUrlByLocation = function(){
16 | let url = this._location.hash;
17 | if(url.length > 0){
18 | url = url.substr(1);
19 | }
20 | return url;
21 | }
22 |
23 | URL.prototype.getUrlByLocation = function(){
24 | if(this.isHashMode){
25 | return this.getHashUrlByLocation();
26 | }
27 | let pathname = this._location.pathname;
28 | let i = pathname.indexOf(this.base);
29 | if(i === 0){
30 | pathname = pathname.substr(this.base.length);
31 | }
32 | return pathname + this._location.search;
33 | }
34 |
35 | URL.prototype.getRouteByLocation = function(){
36 | return urlParse(this.getUrlByLocation());
37 | }
38 |
39 | URL.prototype.toLocationUrl = function(fullPath){
40 | return this.isHashMode ? (this.hashBase + fullPath) : (this.base + fullPath);
41 | }
42 |
43 |
44 | export function urlParse(url){
45 | let i = url.indexOf('?');
46 | let path, qsString;
47 | if(i !== -1){
48 | path = url.substr(0, i);
49 | qsString = url.substr(i + 1);
50 | } else {
51 | path = url;
52 | qsString = '';
53 | }
54 | return {
55 | path,
56 | query: queryParse(qsString)
57 | }
58 | }
59 | /* path, fullPath, trimedPath, query */
60 | export function fullUrlParse(userUrl){
61 | let route, fullPath, trimedPath;
62 | if(typeof userUrl === 'string'){
63 | route = urlParse(userUrl);
64 | fullPath = userUrl;
65 | } else {
66 | route = trimRoute(userUrl);
67 | fullPath = _urlStringify(route);
68 | }
69 | trimedPath = trimSlash(route.path);
70 | return Object.assign({
71 | fullPath,
72 | trimedPath
73 | }, route);
74 | }
75 |
76 | function trimRoute(route){
77 | if(route.path.indexOf('?') === -1){
78 | return route;
79 | }
80 | let _route = urlParse(route.path);
81 | Object.assign(_route.query, route.query);
82 | return _route;
83 | }
84 |
85 | export function urlStringify(route){
86 | return _urlStringify(trimRoute(route));
87 | }
88 |
89 | function _urlStringify(route){
90 | let queryStr = queryStringify(route.query);
91 | if(queryStr) {
92 | queryStr = '?' + queryStr;
93 | }
94 | return route.path + queryStr;
95 | }
96 |
97 | function queryParse(qsString){
98 | const map = Object.create(null);
99 | if(!qsString || (typeof qsString === 'string' && qsString.length === 0)){
100 | return map;
101 | }
102 | const arr = qsString.split('&');
103 | let i = 0, len = arr.length, v;
104 | for(; i < len; i++){
105 | v = arr[i];
106 | v = v.split('=');
107 | map[v[0]] = decodeURIComponent(v[1]);
108 | }
109 | return map;
110 | }
111 |
112 | function queryStringify(obj){
113 | if(!obj){
114 | return '';
115 | }
116 | let arr = [];
117 | Object.keys(obj).forEach(k => {
118 | arr.push(k + '=' + encodeURIComponent(obj[k]));
119 | });
120 | return arr.join('&');
121 | }
122 |
123 | // function pathJoin(str1, str2){
124 | // return trimSlash(str1 + '/' + str2);
125 | // }
126 |
127 | export function trimSlash(pathStr){
128 | let arr = pathStr.split('/');
129 | arr = arr.filter(v => v !== '');
130 | return arr.join('/');
131 | }
132 |
133 | // function _joinBase(base, fullPath){
134 | // if(fullPath[0] === '/'){
135 | // return base + fullPath;
136 | // }
137 | // return base + '/' + fullPath;
138 | // }
139 |
140 | export default URL;
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | export function noop() {}
2 |
3 | export function def(dest, src, key, defValue){
4 | let v = src[key];
5 | dest[key] = v === undefined ? defValue : v;
6 | }
7 |
8 | // export function defNew(defObj, src){
9 | // const dest = Object.create(null);
10 | // const keys = Object.keys(defObj);
11 | // const len = keys.length;
12 | // let i = 0, k;
13 | // for(; i < len; i++){
14 | // k = keys[i];
15 | // def(dest, src, k, defObj[k]);
16 | // }
17 | // return dest;
18 | // }
19 |
20 | export function throwErr(msg){
21 | throw new Error(`history-navigation-vue: ${msg}`);
22 | }
23 |
24 | export function getVueV(_Vue){
25 | let v = _Vue.version;
26 | v = v.substr(0, v.indexOf('.'));
27 | return Number(v);
28 | }
29 |
30 |
31 | export function _cutOffAndPush(arr, stateKey, modalCount){
32 | let i = 0;
33 | const len = arr.length;
34 | let v;
35 | for(; i < len; i++){
36 | v = arr[i];
37 | if(v[0] >= stateKey){
38 | break;
39 | }
40 | }
41 | arr.splice(i, arr.length);
42 | if(modalCount){
43 | arr.push([stateKey, modalCount]);
44 | }
45 | }
46 |
47 | export function _getModalSteps(arr, startState, endState){
48 | console.log('startState', startState, 'endState', endState);
49 | let i = _findCrumbsIndex(arr, startState.key);
50 |
51 | const endIndex = _findCrumbsIndex(arr, endState.key);
52 | let count = 0;
53 | const startCrumb = arr[i];
54 | if(startCrumb && startCrumb[0] === startState.key){
55 | count = arr[i][1] - startState.modalKey;
56 | }
57 | count = count + endState.modalKey;
58 | i = i + 1;
59 | for(; i < endIndex; i++){
60 | count = count + arr[i][1];
61 | }
62 | return count;
63 | }
64 |
65 | export function _getTotalSteps(arr, startState, endState){
66 | const modals = _getModalSteps(arr, startState, endState);
67 | return modals + (endState.key - startState.key);
68 | }
69 |
70 | export function _findCrumbsIndex(arr, stateKey){
71 | let i = 0;
72 | const len = arr.length;
73 | for(; i < len; i++){
74 | if(arr[i][0] >= stateKey){
75 | break;
76 | }
77 | }
78 | return i;
79 | }
--------------------------------------------------------------------------------
/ssr/conf.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | module.exports = {
3 | ssrBundlePath: __dirname
4 | }
--------------------------------------------------------------------------------
/ssr/front-end/app.js:
--------------------------------------------------------------------------------
1 | // app.js
2 | import Vue from 'vue'
3 | import _createApp from '../../examples/create_app'
4 | export function createApp () {
5 |
6 | const app = _createApp(Vue, {
7 | urlIsHashMode: true
8 | });
9 |
10 | // 返回 app 和 router
11 | return { app }
12 | }
--------------------------------------------------------------------------------
/ssr/front-end/entry-server.js:
--------------------------------------------------------------------------------
1 | // entry-server.js
2 | import { createApp } from './app';
3 |
4 | export default context => {
5 | // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise,
6 | // 以便服务器能够等待所有的内容在渲染前,
7 | // 就已经准备就绪。
8 | return new Promise((resolve, reject) => {
9 | const app = createApp();
10 | // 设置服务器端 router 的位置
11 | app.$navigator.replace(context.url);
12 |
13 | resolve(app);
14 | })
15 | }
--------------------------------------------------------------------------------
/ssr/ssr-build.js:
--------------------------------------------------------------------------------
1 | // https://ssr.vuejs.org/zh/
2 |
3 | var child_process = require('child_process');
4 | const conf = require('../config');
5 | const fs = require('fs');
6 | const path = require('path');
7 | const proDistPath = path.join(__dirname, '../dist');
8 | const fsExt = require('fs-extra');
9 | const contextConfig = require('./ssr-context-config');
10 |
11 | module.exports = function (callback) {
12 | console.log('=============== ssr build 开始 ===============\n');
13 | console.log('+++++++ 创建 ssr bundle... +++++++');
14 | child_process.execSync('npm run ssr_build');
15 | console.log('+++++++ 创建 ssr bundle 成功!+++++++\n');
16 | callback();
17 |
18 | const render = require('./ssr-render');
19 |
20 | const routeMap = conf.routes.map;
21 | const routeKeys = Object.keys(routeMap);
22 | function loop (i) {
23 | if (i < routeKeys.length) {
24 | const key = routeKeys[i];
25 | const context = Object.assign({}, routeMap[key]);
26 | if(contextConfig[key]){
27 | Object.assign(context, contextConfig[key]);
28 | }
29 | // 不能为 undefined
30 | if ( context.ssrHeadScript === undefined) {
31 | context.ssrHeadScript = '';
32 | }
33 |
34 | context.title = context.title + conf.routes.titleSuffix;
35 | context.url = key;
36 | render(context, (html) => {
37 | const name = context.url.replace(/^\//, '');
38 |
39 | const distPath = path.join(proDistPath, name);
40 | fsExt.mkdirpSync(distPath);
41 | const filePath = path.join(distPath, 'index.html');
42 | fs.writeFileSync(filePath, html);
43 | const stat = fs.statSync(filePath);
44 | console.log(name + '/index.html build 成功!', B2KB(stat.size));
45 | loop (i + 1);
46 | })
47 | } else {
48 | console.log('\n=============== ssr build 结束 ===============\n');
49 | }
50 | }
51 |
52 | loop (0);
53 | }
54 |
55 |
56 | function B2KB(size){
57 | return (size / 1024).toFixed(2) + 'KB';
58 | }
59 |
--------------------------------------------------------------------------------
/ssr/ssr-context-config.js:
--------------------------------------------------------------------------------
1 | // 只存在后端的数据。开发时需要 npm run build 后,在 npm run preview 查看。
2 |
3 | module.exports = {
4 | '/recall': {
5 | ssrHeadScript: ``
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ssr/ssr-render.js:
--------------------------------------------------------------------------------
1 | // server.js
2 | const fs = require('fs');
3 | const path = require('path');
4 | const conf = require('./conf');
5 | const serverBundle = require(path.join(conf.ssrBundlePath, 'vue-ssr-server-bundle.json'));
6 | const { createBundleRenderer } = require('vue-server-renderer');
7 | // const template = fs.readFileSync(path.join(__dirname, '../dist/index.html'), 'utf-8');
8 | const renderer = createBundleRenderer(serverBundle, {
9 | runInNewContext: false, // 推荐
10 | // template // (可选)页面模板
11 | })
12 |
13 | module.exports = function render(context, callback) {
14 | // 这里无需传入一个应用程序,因为在执行 bundle 时已经自动创建过。
15 | // 现在我们的服务器与应用程序已经解耦!
16 | renderer.renderToString(context, (err, html) => {
17 | // 处理异常……
18 | if (err) {
19 | throw err;
20 | }
21 | callback(html);
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/ssr/webpack.config.js:
--------------------------------------------------------------------------------
1 | const {merge} = require('webpack-merge')
2 | const nodeExternals = require('webpack-node-externals')
3 | const baseConfig = require('../webpack.config.js')
4 | const VueSSRServerPlugin = require('vue-server-renderer/server-plugin')
5 | const path = require('path');
6 | const conf = require('./conf');
7 | module.exports = merge(baseConfig, {
8 | // 将 entry 指向应用程序的 server entry 文件
9 | entry: path.join(__dirname, 'front-end/entry-server.js'),
10 |
11 | // 这允许 webpack 以 Node 适用方式(Node-appropriate fashion)处理动态导入(dynamic import),
12 | // 并且还会在编译 Vue 组件时,
13 | // 告知 `vue-loader` 输送面向服务器代码(server-oriented code)。
14 | target: 'node',
15 |
16 | // 对 bundle renderer 提供 source map 支持
17 | devtool: 'source-map',
18 |
19 | // 此处告知 server bundle 使用 Node 风格导出模块(Node-style exports)
20 | output: {
21 | libraryTarget: 'commonjs2',
22 | path: conf.ssrBundlePath
23 | },
24 |
25 | // https://webpack.js.org/configuration/externals/#function
26 | // https://github.com/liady/webpack-node-externals
27 | // 外置化应用程序依赖模块。可以使服务器构建速度更快,
28 | // 并生成较小的 bundle 文件。
29 | externals: nodeExternals({
30 | // 不要外置化 webpack 需要处理的依赖模块。
31 | // 你可以在这里添加更多的文件类型。例如,未处理 *.vue 原始文件,
32 | // 你还应该将修改 `global`(例如 polyfill)的依赖模块列入白名单
33 | allowlist: [/\.css$/, /\.vue$/]
34 | }),
35 |
36 | // 这是将服务器的整个输出
37 | // 构建为单个 JSON 文件的插件。
38 | // 默认文件名为 `vue-ssr-server-bundle.json`
39 | plugins: [
40 | new VueSSRServerPlugin()
41 | ]
42 | })
--------------------------------------------------------------------------------
/test/e2e/.placeholder:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hezedu/history-navigation-vue/b68b1c59abb8dd4419d01cfc16ffc12b1cc9f311/test/e2e/.placeholder
--------------------------------------------------------------------------------
/test/e2e/history.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | require('chromedriver');
3 | const webDriver = require('selenium-webdriver');
4 | const ROOT_URL = 'http://localhost:8080/history-navigation-vue/examples/';
5 |
6 | const { By } = webDriver;
7 | const driver = new webDriver.Builder()
8 | .forBrowser('chrome')
9 | .build();
10 |
11 | describe('History', function() {
12 | describe('#Hello World', function() {
13 | it('_h_n_key eq 1 when load', function(done) {
14 |
15 | driver.get(ROOT_URL + 'two-pages.html').then(() => {
16 | driver.executeScript("return history.state;").then((state) => {
17 | assert.deepEqual(state, {_h_n_key: 1});
18 | });
19 | });
20 | done();
21 | });
22 | it('_h_n_key eq 2 after push', function(done) {
23 |
24 | driver.findElement(By.tagName('a')).click().then(() => {
25 | // setTimeout(() => {
26 | driver.executeScript("return history.state;").then((state) => {
27 | assert.deepEqual(state, {_h_n_key: 2});
28 | done();
29 | });
30 | // })
31 |
32 | });
33 |
34 | });
35 | it('_h_n_key eq 1 after back', function(done) {
36 | driver.navigate().back().then(() => {
37 | // setTimeout(() => {
38 | driver.executeScript("return history.state;").then((state) => {
39 | assert.deepEqual(state, {_h_n_key: 1});
40 | done();
41 | });
42 | // }, 1000);
43 |
44 | })
45 | });
46 | });
47 |
48 | after(() => {
49 | driver.quit();
50 | });
51 | });
--------------------------------------------------------------------------------
/test/unit/util.js:
--------------------------------------------------------------------------------
1 | var assert = require('assert');
2 | const vm = require('vm');
3 | const fs = require('fs');
4 | let source = fs.readFileSync('../src/util.js', 'utf-8');
5 | source = source.replace(/export function ([\s\S]+?)\(/g, (mstr) => {
6 | const name = mstr.substring(mstr.lastIndexOf(' '), mstr.length - 1);
7 | return `function ${name}(`;
8 |
9 | })
10 | eval(source); // _cutOffAndPush
11 | describe('Util', function() {
12 |
13 | describe('#_getTotalSteps', function() {
14 | it('total should eq 1', function() {
15 | const count = _getTotalSteps([], {key: 1, modalKey: 0}, {key: 2, modalKey: 0});
16 | assert.equal(count, 1);
17 | });
18 | });
19 |
20 | describe('#_getModalSteps', function() {
21 | it('step should eq 0', function() {
22 | const count = _getModalSteps([], {key: 1, modalKey: 0}, {key: 2, modalKey: 0});
23 | assert.equal(count, 0);
24 | });
25 | it('step should eq 1', function() {
26 | const count = _getModalSteps([[1, 1]], {key: 1, modalKey: 0}, {key: 2, modalKey: 0});
27 | assert.equal(count, 1);
28 | });
29 | it('step should eq 2', function() {
30 | const count = _getModalSteps([[1, 1]], {key: 1, modalKey: 0}, {key: 2, modalKey: 1});
31 | assert.equal(count, 2);
32 | });
33 | it('step should eq 2 too', function() {
34 | const count = _getModalSteps([[1, 1], [2, 2]], {key: 1, modalKey: 0}, {key: 2, modalKey: 1});
35 | assert.equal(count, 2);
36 | });
37 | });
38 | describe('#_findCrumbsIndex', function() {
39 | it('index should eq 0', function() {
40 | const index = _findCrumbsIndex([], 1);
41 | assert.equal(index, 0);
42 | });
43 | it('index should eq 1', function() {
44 | const index = _findCrumbsIndex([[0], [2]], 1);
45 | assert.equal(index, 1);
46 | });
47 | it('index should eq 1', function() {
48 | const index = _findCrumbsIndex([[0], [1]], 1);
49 | assert.equal(index, 1);
50 | });
51 | });
52 | describe('#_cutOffAndPush(arr, stateKey, modalCount)', function() {
53 |
54 | it('array length should eq 0', function() {
55 | const arr = [];
56 | _cutOffAndPush(arr, 1, 0)
57 | assert.equal(arr.length, 0);
58 | });
59 | it('array length should eq 1', function() {
60 | const arr = [];
61 | _cutOffAndPush(arr, 1, 1);
62 | assert.deepEqual(arr, [[1, 1]]);
63 | });
64 | it('array length should eq 0', function() {
65 | const arr = [[1, 1]];
66 | _cutOffAndPush(arr, 1, 0)
67 | assert.equal(arr.length, 0);
68 | });
69 |
70 | it('array length should eq 1', function() {
71 | const arr = [[1, 1], [2, 1], [3, 1]];
72 | _cutOffAndPush(arr, 2, 0);
73 |
74 | assert.deepEqual(arr, [[1, 1]]);
75 | });
76 | it('array length should eq 3', function() {
77 | const arr = [[1, 1], [2, 1]];
78 | _cutOffAndPush(arr, 3, 1);
79 | assert.deepEqual(arr.length, 3);
80 | });
81 | });
82 | });
--------------------------------------------------------------------------------
/webpack.build.config.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | const isPro = process.argv.indexOf('--node-env=production') !== -1;
4 |
5 | process.env.NODE_ENV = 'production';
6 | const NODE_ENV = process.env.NODE_ENV;
7 | const ESLintPlugin = require('eslint-webpack-plugin');
8 | const path = require('path');
9 | const webpack = require('webpack');
10 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
11 | const TerserPlugin = require("terser-webpack-plugin");
12 | const OptimizeCSSAssetsPlugin = require("css-minimizer-webpack-plugin");
13 | const VueLoaderPlugin = require('vue-loader/lib/plugin');
14 |
15 |
16 |
17 | const bundleName = 'history-navigation-vue';
18 | const outputPath = path.join(__dirname, './dist');
19 | const filename = isPro ? '[name].min' : '[name]';
20 | var optimization;
21 |
22 | var cssRule = {
23 | test: /(\.css$)/,
24 | //use: ["css-loader", "postcss-loader", "sass-loader"]
25 | use: [
26 | {
27 | loader: MiniCssExtractPlugin.loader
28 | },
29 | 'css-loader',
30 | 'postcss-loader',
31 | // {
32 | // loader: 'sass-loader',
33 | // options: {
34 | // outputStyle: 'expanded'
35 | // }
36 | // }
37 | ]
38 | };
39 |
40 |
41 |
42 | // ***************************** plugins *****************************
43 | var plugins = [
44 | new ESLintPlugin({
45 | extensions: ['js', 'vue']
46 | }),
47 | new VueLoaderPlugin(),
48 | new webpack.DefinePlugin({
49 | 'process.env': {
50 | NODE_ENV: JSON.stringify(NODE_ENV)
51 | }
52 | }),
53 | ]
54 | var rules = [
55 | {
56 | test: /\.js$/,
57 | loader: 'babel-loader',
58 | exclude: /node_modules/
59 | },
60 | {
61 | test: /\.vue$/,
62 | loader: 'vue-loader'
63 | },
64 | cssRule
65 | ]
66 | // ***************************** 环境适配 *****************************
67 | optimization = {
68 | minimize: isPro,
69 | minimizer: [
70 | new TerserPlugin(
71 | // {
72 | // extractComments: {
73 | // condition: true,
74 | // filename: 'bundleName',
75 | // banner: (commentsFile) => {
76 | // return `My custom banner about license information ${commentsFile}`;
77 | // },
78 | // }
79 | // }
80 | ),
81 | new OptimizeCSSAssetsPlugin({})
82 | ]
83 | }
84 |
85 | plugins = plugins.concat([
86 | new MiniCssExtractPlugin({
87 | // Options similar to the same options in webpackOptions.output
88 | // both options are optional
89 | filename: filename + ".css"
90 | // chunkFilename: "chunk_[name]_[contenthash].css"
91 | }),
92 |
93 | new webpack.LoaderOptionsPlugin({
94 | minimize: true
95 | })
96 |
97 | ]);
98 |
99 | // -------- pre lint --------
100 | // rules.unshift({
101 | // enforce: "pre",
102 | // test: /(\.js|\.vue)$/,
103 | // include: [path.resolve(__dirname, "src")],
104 | // loader: "eslint-loader"
105 | // })
106 |
107 | // ***************************** webpackConf *****************************
108 | const webpackConf = {
109 | mode: NODE_ENV,
110 | optimization,
111 | context: path.join(__dirname, './src'),
112 | entry: {
113 | [bundleName]: "./bundle.js"
114 | },
115 | output: {
116 | path: outputPath,
117 | filename: filename + '.js',
118 | library: 'historyNavigationVue',
119 | libraryTarget: 'umd'
120 | },
121 | module: {
122 | rules
123 | },
124 | resolve: {
125 | extensions: ['.js']
126 | },
127 | plugins: plugins,
128 | performance: {
129 | hints: false
130 | }
131 | };
132 |
133 | if(!isPro){
134 |
135 | const fs = require('fs');
136 | _emptyDirSync(path.join(__dirname, 'dist'));
137 | function _emptyDirSync(dir){
138 | const names = fs.readdirSync(dir);
139 | names.forEach(name => {
140 | fs.unlinkSync(path.join(dir, name));
141 | })
142 | }
143 | } else {
144 | console.log('build minimize files...');
145 | }
146 |
147 | module.exports = webpackConf;
148 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
5 | const TerserPlugin = require("terser-webpack-plugin");
6 | const OptimizeCSSAssetsPlugin = require("css-minimizer-webpack-plugin");
7 | const VueLoaderPlugin = require('vue-loader/lib/plugin');
8 | const setup = require('./server/setup');
9 | const config = require('./config');
10 |
11 | const NODE_ENV = process.env.NODE_ENV || 'development';
12 |
13 | const isPro = NODE_ENV === 'production';
14 | const {indexDir,
15 | bundleName,
16 | outputPath,
17 | publicPath} = require('./script/get_config')();
18 |
19 | // console.log('indexDir', indexDir);
20 | // console.log('bundleName', bundleName);
21 | // console.log('outputPath', outputPath);
22 | // console.log('publicPath', publicPath);
23 |
24 | var optimization;
25 |
26 | var cssRule;
27 | if(!isPro){ //使用 命令weblack
28 | cssRule = {
29 | test: /(\.css$)/,
30 | use: ['style-loader', 'css-loader', 'postcss-loader'],
31 | // include: path.join(__dirname, './src')
32 | }
33 |
34 | }else{
35 | cssRule = {
36 | test: /(\.css$)/,
37 | //use: ["css-loader", "postcss-loader", "sass-loader"]
38 | use: [
39 | {
40 | loader: MiniCssExtractPlugin.loader
41 | },
42 |
43 | "css-loader", 'postcss-loader'
44 | ]
45 | }
46 |
47 | }
48 |
49 |
50 |
51 | // ***************************** plugins *****************************
52 | var plugins = [
53 | new VueLoaderPlugin(),
54 | new webpack.DefinePlugin({
55 | 'process.env': {
56 | NODE_ENV: JSON.stringify(NODE_ENV)
57 | }
58 | }),
59 |
60 | // create index.html
61 | new HtmlWebpackPlugin({
62 | filename: path.join(indexDir + '/index.html'),
63 | template: path.join(__dirname, '/examples/index.ejs'),
64 | staticMap: setup.staticMap,
65 | data: {
66 | title: config.title
67 | }
68 | })
69 | ]
70 | var rules = [
71 | {
72 | test: /\.js$/,
73 | loader: 'babel-loader',
74 | exclude: /node_modules|dist/
75 | },
76 | {
77 | test: /\.vue$/,
78 | loader: 'vue-loader'
79 | },
80 | cssRule
81 | ]
82 | // ***************************** 环境适配 *****************************
83 | if (isPro) {
84 | optimization = {
85 | minimizer: [
86 | new TerserPlugin(),
87 | new OptimizeCSSAssetsPlugin({})
88 | ]
89 | }
90 |
91 | plugins = plugins.concat([//正式环境下压缩
92 | new MiniCssExtractPlugin({
93 | // Options similar to the same options in webpackOptions.output
94 | // both options are optional
95 | filename: "[name]_[contenthash].css",
96 | chunkFilename: "chunk_[name]_[contenthash].css"
97 | }),
98 |
99 | // new webpack.optimize.TerserPlugin({
100 | // compress: {
101 | // warnings: false,
102 | // },
103 | // sourceMap: false,
104 | // output: {
105 | // comments: false,
106 | // }
107 | // }),
108 |
109 | new webpack.LoaderOptionsPlugin({
110 | minimize: true
111 | })
112 |
113 | ]);
114 |
115 | // -------- pre lint --------
116 | // rules.unshift({
117 | // enforce: "pre",
118 | // test: /(\.js|\.vue)$/,
119 | // include: [path.resolve(__dirname, "src")],
120 | // loader: "eslint-loader"
121 | // })
122 |
123 | };
124 |
125 | // ***************************** config *****************************
126 | const webpackConf = {
127 | mode: NODE_ENV,
128 | optimization,
129 | context: path.join(__dirname, './examples'),
130 | entry: {
131 | z_app: "./app.js"
132 | },
133 | output: {
134 | path: outputPath,
135 | publicPath,
136 | filename: bundleName,
137 | chunkFilename: 'chunk_' + bundleName
138 | },
139 | module: {
140 | rules
141 | },
142 | resolve: {
143 | extensions: ['.js'],
144 | // alias: {
145 | // '__ROOT__' : path.join(__dirname, './src')
146 | // }
147 | },
148 | plugins: plugins,
149 | devServer: {
150 | port: config.port,
151 | onBeforeSetupMiddleware({app}){
152 | setup(app)
153 | },
154 | host: '0.0.0.0',
155 | static: {
156 | directory: indexDir,
157 | watch: false
158 | },
159 | // contentBase: indexDir,
160 | hot: true,
161 | liveReload: false,
162 | // proxy: {
163 | // '/api': {
164 | // target: '',
165 | // changeOrigin: true
166 | // }
167 | // }
168 | },
169 | performance: {
170 | hints: false
171 | }
172 | };
173 |
174 | module.exports = webpackConf;
175 |
--------------------------------------------------------------------------------