UNPKG

@hippy/vue-router

Version:

Official router for hippy-vue

1,580 lines (1,505 loc) 68.9 kB
/*! * @hippy/vue-router v3.3.2 * (Using Vue v2.6.14 and Hippy-Vue v3.3.2) * Build at: Fri Nov 08 2024 20:45:13 GMT+0800 (中国标准时间) * * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2024 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* */ Object.freeze({}); /** * Make a map and return a function for checking if a key * is in that map. */ function makeMap(str, expectsLowerCase) { const map = Object.create(null); const list = str.split(','); for (let i = 0; i < list.length; i++) { map[list[i]] = true; } return expectsLowerCase ? val => map[val.toLowerCase()] : val => map[val]; } /** * Check if a tag is a built-in tag. */ makeMap('slot,component', true); /** * Check if an attribute is a reserved attribute. */ makeMap('key,ref,slot,slot-scope,is'); /** * Ensure a function is called only once. */ function once(fn) { let called = false; return function () { if (!called) { called = true; fn.apply(this, arguments); } }; } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Hippy debug address */ `http://127.0.0.1:${process.env.PORT}/`; /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ let _Vue; function setVue(Vue) { _Vue = Vue; } function getVue() { return _Vue; } /** * Better function checking */ function isFunction(func) { return Object.prototype.toString.call(func) === '[object Function]'; } function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var defineProperty = {exports: {}}; var toPropertyKey = {exports: {}}; var _typeof = {exports: {}}; (function (module) { function _typeof(o) { "@babel/helpers - typeof"; return (module.exports = _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, module.exports.__esModule = true, module.exports["default"] = module.exports), _typeof(o); } module.exports = _typeof, module.exports.__esModule = true, module.exports["default"] = module.exports; })(_typeof); var toPrimitive = {exports: {}}; (function (module) { var _typeof$1 = _typeof.exports["default"]; function toPrimitive(t, r) { if ("object" != _typeof$1(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof$1(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } module.exports = toPrimitive, module.exports.__esModule = true, module.exports["default"] = module.exports; })(toPrimitive); (function (module) { var _typeof$1 = _typeof.exports["default"]; var toPrimitive$1 = toPrimitive.exports; function toPropertyKey(t) { var i = toPrimitive$1(t, "string"); return "symbol" == _typeof$1(i) ? i : i + ""; } module.exports = toPropertyKey, module.exports.__esModule = true, module.exports["default"] = module.exports; })(toPropertyKey); (function (module) { var toPropertyKey$1 = toPropertyKey.exports; function _defineProperty(e, r, t) { return (r = toPropertyKey$1(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } module.exports = _defineProperty, module.exports.__esModule = true, module.exports["default"] = module.exports; })(defineProperty); var _defineProperty = /*@__PURE__*/getDefaultExportFromCjs(defineProperty.exports); /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable no-console */ function assert(condition, message) { if (!condition) { throw new Error(`[hippy-vue-router] ${message}`); } } function warn(condition, message) { if (process.env.NODE_ENV !== 'production' && !condition) { if (typeof console !== 'undefined') { console.warn(`[hippy-vue-router] ${message}`); } } } function isError(err) { return Object.prototype.toString.call(err).indexOf('Error') > -1; } function ownKeys$2(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread$2(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$2(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$2(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function resolveProps(route, config) { switch (typeof config) { case 'undefined': return null; case 'object': return config; case 'function': return config(route); case 'boolean': return config ? route.params : undefined; default: if (process.env.NODE_ENV !== 'production') { warn(false, `props in "${route.path}" is a ${typeof config}, ` + 'expecting an object, function or boolean.'); } return null; } } var View = { name: 'RouterView', functional: true, props: { name: { type: String, default: 'default' } }, render(_, { props, children, parent, data }) { // used by devtools to display a router-view badge data.routerView = true; // directly use parent context's createElement() function // so that components rendered by router-view can resolve named slots const h = parent.$createElement; const { name } = props; const route = parent.$route; const cache = parent._routerViewCache || (parent._routerViewCache = {}); // determine current view depth, also check to see if the tree // has been toggled inactive but kept-alive. let depth = 0; let inactive = false; while (parent && parent._routerRoot !== parent) { if (parent.$vnode && parent.$vnode.data.routerView) { depth += 1; } if (parent._inactive) { inactive = true; } parent = parent.$parent; } data.routerViewDepth = depth; // render previous view if the tree is inactive and kept-alive if (inactive) { return h(cache[name], data, children); } const matched = route.matched[depth]; // render empty node if no matched route if (!matched) { cache[name] = null; return h(); } const component = matched.components[name]; cache[name] = component; // attach instance registration hook // this will be called in the instance's injected lifecycle hooks data.registerRouteInstance = (vm, val) => { // val could be undefined for unregistration const current = matched.instances[name]; if (val && current !== vm || !val && current === vm) { matched.instances[name] = val; } }; // also register instance in prepatch hook // in case the same component instance is reused across different routes if (!data.hook) { data.hook = {}; } data.hook.prepatch = (__, vnode) => { matched.instances[name] = vnode.componentInstance; }; // resolve props let propsToPass = resolveProps(route, matched.props && matched.props[name]); data.props = propsToPass; if (propsToPass) { // clone to prevent mutation propsToPass = _objectSpread$2({}, propsToPass); data.props = propsToPass; // pass non-declared props as attrs const attrs = data.attrs || {}; Object.keys(propsToPass).forEach(key => { if (!component.props || !(key in component.props)) { attrs[key] = propsToPass[key]; delete propsToPass[key]; } }); } return h(component, data, children); } }; /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const encodeReserveRE = /[!'()*]/g; const encodeReserveReplacer = c => `%${c.charCodeAt(0).toString(16)}`; const commaRE = /%2C/g; // fixed encodeURIComponent which is more conformant to RFC3986: // - escapes [!'()*] // - preserve commas const encode = str => encodeURIComponent(str).replace(encodeReserveRE, encodeReserveReplacer).replace(commaRE, ','); const decode = decodeURIComponent; function parseQuery(query) { const res = {}; query = query.trim().replace(/^(\?|#|&)/, ''); if (!query) { return res; } query.split('&').forEach(param => { const parts = param.replace(/\+/g, ' ').split('='); const key = decode(parts.shift()); const val = parts.length > 0 ? decode(parts.join('=')) : null; if (res[key] === undefined) { res[key] = val; } else if (Array.isArray(res[key])) { res[key].push(val); } else { res[key] = [res[key], val]; } }); return res; } function resolveQuery(query, extraQuery = {}, _parseQuery) { const parse = _parseQuery || parseQuery; let parsedQuery; try { parsedQuery = parse(query || ''); } catch (e) { if (process.env.NODE_ENV !== 'production') { warn(false, e.message); } parsedQuery = {}; } Object.keys(extraQuery).forEach(key => { parsedQuery[key] = extraQuery[key]; }); return parsedQuery; } function stringifyQuery(obj) { const res = obj ? Object.keys(obj).map(key => { const val = obj[key]; if (val === undefined) { return ''; } if (val === null) { return encode(key); } if (Array.isArray(val)) { const result = []; val.forEach(val2 => { if (val2 === undefined) { return; } if (val2 === null) { result.push(encode(key)); } else { result.push(`${encode(key)}=${encode(val2)}`); } }); return result.join('&'); } return `${encode(key)}=${encode(val)}`; }).filter(x => x.length > 0).join('&') : null; return res ? `?${res}` : ''; } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const trailingSlashRE = /\/?$/; function clone(value) { if (Array.isArray(value)) { return value.map(clone); } if (value && typeof value === 'object') { const res = {}; Object.keys(value).forEach(key => { res[key] = clone(value[key]); }); return res; } return value; } function formatMatch(record) { const res = []; while (record) { res.unshift(record); record = record.parent; } return res; } function getFullPath({ path, query = {}, hash = '' }, _stringifyQuery) { const stringify = _stringifyQuery || stringifyQuery; return (path || '/') + stringify(query) + hash; } function isObjectEqual(a = {}, b = {}) { // handle null value #1566 if (!a || !b) return a === b; const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (aKeys.length !== bKeys.length) { return false; } return aKeys.every(key => { const aVal = a[key]; const bVal = b[key]; // check nested equality if (typeof aVal === 'object' && typeof bVal === 'object') { return isObjectEqual(aVal, bVal); } return String(aVal) === String(bVal); }); } function createRoute(record, location, redirectedFrom, router) { let stringifyQueryStr; if (router) { ({ stringifyQuery: stringifyQueryStr } = router.options); } let query = location.query || {}; try { query = clone(query); } catch (e) { // pass } const route = { name: location.name || record && record.name, meta: record && record.meta || {}, path: location.path || '/', hash: location.hash || '', query, params: location.params || {}, fullPath: getFullPath(location, stringifyQueryStr), matched: record ? formatMatch(record) : [] }; if (redirectedFrom) { route.redirectedFrom = getFullPath(redirectedFrom, stringifyQueryStr); } return Object.freeze(route); } function queryIncludes(current, target) { for (const key in target) { if (!(key in current)) { return false; } } return true; } // the starting route that represents the initial state const START = createRoute(null, { path: '/' }); function isSameRoute(a, b) { if (b === START) { return a === b; } if (!b) { return false; } if (a.path && b.path) { return a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && a.hash === b.hash && isObjectEqual(a.query, b.query); } if (a.name && b.name) { return a.name === b.name && a.hash === b.hash && isObjectEqual(a.query, b.query) && isObjectEqual(a.params, b.params); } return false; } function isIncludedRoute(current, target) { return current.path.replace(trailingSlashRE, '/').indexOf(target.path.replace(trailingSlashRE, '/')) === 0 && (!target.hash || current.hash === target.hash) && queryIncludes(current.query, target.query); } function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // work around weird flow bug const toTypes = [String, Object]; const eventTypes = [String, Array]; function guardEvent(e) { // don't redirect with control keys if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return false; // don't redirect when preventDefault called if (e.defaultPrevented) return false; // don't redirect on right click if (e.button !== undefined && e.button !== 0) return false; // don't redirect if `target="_blank"` if (e.currentTarget && e.currentTarget.getAttribute) { const target = e.currentTarget.getAttribute('target'); if (/\b_blank\b/i.test(target)) return false; } // this may be a Weex event which doesn't have this method if (e.preventDefault) { e.preventDefault(); } return true; } function findAnchor(children) { if (!children) { return null; } return children.find(child => { if (child.tag === 'a') { return true; } if (child.children) { const c = findAnchor(child.children); return !!c; } return false; }); } var Link = { name: 'RouterLink', props: { to: { type: toTypes, required: true }, tag: { type: String, default: 'a' }, exact: Boolean, append: Boolean, replace: Boolean, activeClass: String, exactActiveClass: String, event: { type: eventTypes, default: 'click' } }, render(h) { const router = this.$router; const current = this.$route; const { location, route, href } = router.resolve(this.to, current, this.append); const classes = {}; const globalActiveClass = router.options.linkActiveClass; const globalExactActiveClass = router.options.linkExactActiveClass; // Support global empty active class // eslint-disable-next-line eqeqeq const activeClassFallback = globalActiveClass == null ? 'router-link-active' : globalActiveClass; // eslint-disable-next-line eqeqeq const exactActiveClassFallback = globalExactActiveClass == null ? 'router-link-exact-active' : globalExactActiveClass; // eslint-disable-next-line eqeqeq const activeClass = this.activeClass == null ? activeClassFallback : this.activeClass; // eslint-disable-next-line eqeqeq const exactActiveClass = this.exactActiveClass == null ? exactActiveClassFallback : this.exactActiveClass; const compareTarget = location.path ? createRoute(null, location, null, router) : route; classes[exactActiveClass] = isSameRoute(current, compareTarget); classes[activeClass] = this.exact ? classes[exactActiveClass] : isIncludedRoute(current, compareTarget); const handler = e => { if (guardEvent(e)) { if (this.replace) { router.replace(location); } else { router.push(location); } } }; const on = { click: guardEvent }; if (Array.isArray(this.event)) { this.event.forEach(e => { on[e] = handler; }); } else { on[this.event] = handler; } const data = { class: classes }; if (this.tag === 'a') { data.on = on; data.attrs = { href }; } else { // find the first <a> child and apply listener and href const a = findAnchor(this.$slots.default); if (a) { // in case the <a> is a static node a.isStatic = false; const aData = _objectSpread$1({}, a.data); a.data = aData; aData.on = on; const aAttrs = _objectSpread$1({}, a.data.attrs); a.data.attrs = aAttrs; aAttrs.href = href; } else { // doesn't have <a> child, apply listener to self data.on = on; } } return h(this.tag, data, this.$slots.default); } }; /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function install(Vue) { if (install.installed && getVue() === Vue) return; install.installed = true; setVue(Vue); const isDef = v => v !== undefined; const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode; if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal); } }; Vue.mixin({ beforeCreate() { if (isDef(this.$options.router)) { this._routerRoot = this; this._router = this.$options.router; this._router.init(this, Vue); Vue.util.defineReactive(this, '_route', this._router.history.current); } else { this._routerRoot = this.$parent && this.$parent._routerRoot || this; } registerInstance(this, this); }, destroyed() { registerInstance(this); } }); Object.defineProperty(Vue.prototype, '$router', { get() { return this._routerRoot._router; } }); Object.defineProperty(Vue.prototype, '$route', { get() { return this._routerRoot._route; } }); Vue.component('RouterView', View); Vue.component('RouterLink', Link); const strats = Vue.config.optionMergeStrategies; // use the same hook merging strategy for route hooks strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created; } var pathToRegexp$1 = {exports: {}}; var isarray$1 = Array.isArray || function (arr) { return Object.prototype.toString.call(arr) == '[object Array]'; }; var isarray = isarray$1; /** * Expose `pathToRegexp`. */ pathToRegexp$1.exports = pathToRegexp; pathToRegexp$1.exports.parse = parse; pathToRegexp$1.exports.compile = compile; pathToRegexp$1.exports.tokensToFunction = tokensToFunction; pathToRegexp$1.exports.tokensToRegExp = tokensToRegExp; /** * The main path matching regexp utility. * * @type {RegExp} */ var PATH_REGEXP = new RegExp([ // Match escaped characters that would otherwise appear in future matches. // This allows the user to escape special characters that won't transform. '(\\\\.)', // Match Express-style parameters and un-named parameters with a prefix // and optional suffixes. Matches appear as: // // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))'].join('|'), 'g'); /** * Parse a string for the raw tokens. * * @param {string} str * @param {Object=} options * @return {!Array} */ function parse(str, options) { var tokens = []; var key = 0; var index = 0; var path = ''; var defaultDelimiter = options && options.delimiter || '/'; var res; while ((res = PATH_REGEXP.exec(str)) != null) { var m = res[0]; var escaped = res[1]; var offset = res.index; path += str.slice(index, offset); index = offset + m.length; // Ignore already escaped sequences. if (escaped) { path += escaped[1]; continue; } var next = str[index]; var prefix = res[2]; var name = res[3]; var capture = res[4]; var group = res[5]; var modifier = res[6]; var asterisk = res[7]; // Push the current path onto the tokens. if (path) { tokens.push(path); path = ''; } var partial = prefix != null && next != null && next !== prefix; var repeat = modifier === '+' || modifier === '*'; var optional = modifier === '?' || modifier === '*'; var delimiter = res[2] || defaultDelimiter; var pattern = capture || group; tokens.push({ name: name || key++, prefix: prefix || '', delimiter: delimiter, optional: optional, repeat: repeat, partial: partial, asterisk: !!asterisk, pattern: pattern ? escapeGroup(pattern) : asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?' }); } // Match any characters still remaining. if (index < str.length) { path += str.substr(index); } // If the path exists, push it onto the end. if (path) { tokens.push(path); } return tokens; } /** * Compile a string to a template function for the path. * * @param {string} str * @param {Object=} options * @return {!function(Object=, Object=)} */ function compile(str, options) { return tokensToFunction(parse(str, options), options); } /** * Prettier encoding of URI path segments. * * @param {string} * @return {string} */ function encodeURIComponentPretty(str) { return encodeURI(str).replace(/[\/?#]/g, function (c) { return '%' + c.charCodeAt(0).toString(16).toUpperCase(); }); } /** * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. * * @param {string} * @return {string} */ function encodeAsterisk(str) { return encodeURI(str).replace(/[?#]/g, function (c) { return '%' + c.charCodeAt(0).toString(16).toUpperCase(); }); } /** * Expose a method for transforming tokens into the path function. */ function tokensToFunction(tokens, options) { // Compile all the tokens into regexps. var matches = new Array(tokens.length); // Compile all the patterns before compilation. for (var i = 0; i < tokens.length; i++) { if (typeof tokens[i] === 'object') { matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$', flags(options)); } } return function (obj, opts) { var path = ''; var data = obj || {}; var options = opts || {}; var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent; for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (typeof token === 'string') { path += token; continue; } var value = data[token.name]; var segment; if (value == null) { if (token.optional) { // Prepend partial segment prefixes. if (token.partial) { path += token.prefix; } continue; } else { throw new TypeError('Expected "' + token.name + '" to be defined'); } } if (isarray(value)) { if (!token.repeat) { throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`'); } if (value.length === 0) { if (token.optional) { continue; } else { throw new TypeError('Expected "' + token.name + '" to not be empty'); } } for (var j = 0; j < value.length; j++) { segment = encode(value[j]); if (!matches[i].test(segment)) { throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`'); } path += (j === 0 ? token.prefix : token.delimiter) + segment; } continue; } segment = token.asterisk ? encodeAsterisk(value) : encode(value); if (!matches[i].test(segment)) { throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"'); } path += token.prefix + segment; } return path; }; } /** * Escape a regular expression string. * * @param {string} str * @return {string} */ function escapeString(str) { return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1'); } /** * Escape the capturing group by escaping special characters and meaning. * * @param {string} group * @return {string} */ function escapeGroup(group) { return group.replace(/([=!:$\/()])/g, '\\$1'); } /** * Attach the keys as a property of the regexp. * * @param {!RegExp} re * @param {Array} keys * @return {!RegExp} */ function attachKeys(re, keys) { re.keys = keys; return re; } /** * Get the flags for a regexp from the options. * * @param {Object} options * @return {string} */ function flags(options) { return options && options.sensitive ? '' : 'i'; } /** * Pull out keys from a regexp. * * @param {!RegExp} path * @param {!Array} keys * @return {!RegExp} */ function regexpToRegexp(path, keys) { // Use a negative lookahead to match only capturing groups. var groups = path.source.match(/\((?!\?)/g); if (groups) { for (var i = 0; i < groups.length; i++) { keys.push({ name: i, prefix: null, delimiter: null, optional: false, repeat: false, partial: false, asterisk: false, pattern: null }); } } return attachKeys(path, keys); } /** * Transform an array into a regexp. * * @param {!Array} path * @param {Array} keys * @param {!Object} options * @return {!RegExp} */ function arrayToRegexp(path, keys, options) { var parts = []; for (var i = 0; i < path.length; i++) { parts.push(pathToRegexp(path[i], keys, options).source); } var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)); return attachKeys(regexp, keys); } /** * Create a path regexp from string input. * * @param {string} path * @param {!Array} keys * @param {!Object} options * @return {!RegExp} */ function stringToRegexp(path, keys, options) { return tokensToRegExp(parse(path, options), keys, options); } /** * Expose a function for taking tokens and returning a RegExp. * * @param {!Array} tokens * @param {(Array|Object)=} keys * @param {Object=} options * @return {!RegExp} */ function tokensToRegExp(tokens, keys, options) { if (!isarray(keys)) { options = /** @type {!Object} */keys || options; keys = []; } options = options || {}; var strict = options.strict; var end = options.end !== false; var route = ''; // Iterate over the tokens and create our regexp string. for (var i = 0; i < tokens.length; i++) { var token = tokens[i]; if (typeof token === 'string') { route += escapeString(token); } else { var prefix = escapeString(token.prefix); var capture = '(?:' + token.pattern + ')'; keys.push(token); if (token.repeat) { capture += '(?:' + prefix + capture + ')*'; } if (token.optional) { if (!token.partial) { capture = '(?:' + prefix + '(' + capture + '))?'; } else { capture = prefix + '(' + capture + ')?'; } } else { capture = prefix + '(' + capture + ')'; } route += capture; } } var delimiter = escapeString(options.delimiter || '/'); var endsWithDelimiter = route.slice(-delimiter.length) === delimiter; // In non-strict mode we allow a slash at the end of match. If the path to // match already ends with a slash, we remove it for consistency. The slash // is valid at the end of a path match, not in the middle. This is important // in non-ending mode, where "/test/" shouldn't match "/test//route". if (!strict) { route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'; } if (end) { route += '$'; } else { // In non-ending mode, we need the capturing groups to match as much as // possible by using a positive lookahead to the end or next path segment. route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'; } return attachKeys(new RegExp('^' + route, flags(options)), keys); } /** * Normalize the given path string, returning a regular expression. * * An empty array can be passed in for the keys, which will hold the * placeholder key descriptions. For example, using `/user/:id`, `keys` will * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. * * @param {(string|RegExp|Array)} path * @param {(Array|Object)=} keys * @param {Object=} options * @return {!RegExp} */ function pathToRegexp(path, keys, options) { if (!isarray(keys)) { options = /** @type {!Object} */keys || options; keys = []; } options = options || {}; if (path instanceof RegExp) { return regexpToRegexp(path, /** @type {!Array} */keys); } if (isarray(path)) { return arrayToRegexp( /** @type {!Array} */path, /** @type {!Array} */keys, options); } return stringToRegexp( /** @type {string} */path, /** @type {!Array} */keys, options); } var Regexp = pathToRegexp$1.exports; /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const regexpCompileCache = Object.create(null); function fillParams(path, params, routeMsg) { try { const filler = regexpCompileCache[path] || (regexpCompileCache[path] = Regexp.compile(path)); return filler(params || {}, { pretty: true }); } catch (e) { if (process.env.NODE_ENV !== 'production') { warn(false, `missing param for ${routeMsg}: ${e.message}`); } return ''; } } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* eslint-disable no-param-reassign */ function resolvePath(relative, base, append) { const firstChar = relative.charAt(0); if (firstChar === '/') { return relative; } if (firstChar === '?' || firstChar === '#') { return base + relative; } const stack = base.split('/'); // remove trailing segment if: // - not appending // - appending to trailing slash (last segment is empty) if (!append || !stack[stack.length - 1]) { stack.pop(); } // resolve relative path const segments = relative.replace(/^\//, '').split('/'); for (let i = 0; i < segments.length; i += 1) { const segment = segments[i]; if (segment === '..') { stack.pop(); } else if (segment !== '.') { stack.push(segment); } } // ensure leading slash if (stack[0] !== '') { stack.unshift(''); } return stack.join('/'); } function parsePath(path) { let hash = ''; let query = ''; const hashIndex = path.indexOf('#'); if (hashIndex >= 0) { hash = path.slice(hashIndex); path = path.slice(0, hashIndex); } const queryIndex = path.indexOf('?'); if (queryIndex >= 0) { query = path.slice(queryIndex + 1); path = path.slice(0, queryIndex); } return { path, query, hash }; } function cleanPath(path) { return path.replace(/\/\//g, '/'); } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function normalizeLocation(raw, current, append, router) { let next = typeof raw === 'string' ? { path: raw } : raw; // named target if (next.name || next._normalized) { return next; } // relative params if (!next.path && next.params && current) { next = _objectSpread({}, next); next._normalized = true; const params = _objectSpread(_objectSpread({}, current.params), next.params); if (current.name) { next.name = current.name; next.params = params; } else if (current.matched.length) { const rawPath = current.matched[current.matched.length - 1].path; next.path = fillParams(rawPath, params, `path ${current.path}`); } else if (process.env.NODE_ENV !== 'production') { warn(false, 'relative params navigation requires a current route.'); } return next; } const parsedPath = parsePath(next.path || ''); const basePath = current && current.path || '/'; const path = parsedPath.path ? resolvePath(parsedPath.path, basePath, append || next.append) : basePath; const query = resolveQuery(parsedPath.query, next.query, router && router.options.parseQuery); let hash = next.hash || parsedPath.hash; if (hash && hash.charAt(0) !== '#') { hash = `#${hash}`; } return { _normalized: true, path, query, hash }; } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function compileRouteRegex(path, pathToRegexpOptions) { const regex = Regexp(path, [], pathToRegexpOptions); if (process.env.NODE_ENV !== 'production') { const keys = Object.create(null); regex.keys.forEach(key => { warn(!keys[key.name], `Duplicate param keys in route with path: "${path}"`); keys[key.name] = true; }); } return regex; } function normalizePath(path, parent, strict) { if (!strict) path = path.replace(/\/$/, ''); if (path[0] === '/') return path; // eslint-disable-next-line eqeqeq if (parent == null) return path; return cleanPath(`${parent.path}/${path}`); } // #lizard forgives function addRouteRecord(pathList, pathMap, nameMap, route, parent, matchAs) { const { path, name } = route; if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line eqeqeq assert(path != null, '"path" is required in a route configuration.'); assert(typeof route.component !== 'string', `route config "component" for path: ${String(path || name)} cannot be a ` + 'string id. Use an actual component instead.'); } const pathToRegexpOptions = route.pathToRegexpOptions || {}; const normalizedPath = normalizePath(path, parent, pathToRegexpOptions.strict); if (typeof route.caseSensitive === 'boolean') { pathToRegexpOptions.sensitive = route.caseSensitive; } const record = { path: normalizedPath, regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), components: route.components || { default: route.component }, instances: {}, name, parent, matchAs, redirect: route.redirect, beforeEnter: route.beforeEnter, meta: route.meta || {}, // eslint-disable-next-line eqeqeq props: route.props == null ? {} : route.components ? route.props : { default: route.props } }; if (route.children) { // Warn if route is named, does not redirect and has a default child route. // If users navigate to this route by name, the default child will // not be rendered (GH Issue #629) if (process.env.NODE_ENV !== 'production') { if (route.name && !route.redirect && route.children.some(child => /^\/?$/.test(child.path))) { warn(false, `Named Route '${route.name}' has a default child route. ` + `When navigating to this named route (:to="{name: '${route.name}'"), ` + 'the default child route will not be rendered. Remove the name from ' + 'this route and use the name of the default child route for named ' + 'links instead.'); } } route.children.forEach(child => { const childMatchAs = matchAs ? cleanPath(`${matchAs}/${child.path}`) : undefined; addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs); }); } if (route.alias !== undefined) { const aliases = Array.isArray(route.alias) ? route.alias : [route.alias]; aliases.forEach(alias => { const aliasRoute = { path: alias, children: route.children }; addRouteRecord(pathList, pathMap, nameMap, aliasRoute, parent, record.path || '/' // matchAs ); }); } if (!pathMap[record.path]) { pathList.push(record.path); pathMap[record.path] = record; } if (name) { if (!nameMap[name]) { nameMap[name] = record; } else if (process.env.NODE_ENV !== 'production' && !matchAs) { warn(false, 'Duplicate named routes definition: ' + `{ name: "${name}", path: "${record.path}" }`); } } } function createRouteMap(routes, oldPathList, oldPathMap, oldNameMap) { // the path list is used to control path matching priority const pathList = oldPathList || []; // $flow-disable-line const pathMap = oldPathMap || Object.create(null); // $flow-disable-line const nameMap = oldNameMap || Object.create(null); routes.forEach(route => { addRouteRecord(pathList, pathMap, nameMap, route); }); // ensure wildcard routes are always at the end for (let i = 0, l = pathList.length; i < l; i += 1) { if (pathList[i] === '*') { pathList.push(pathList.splice(i, 1)[0]); l -= 1; i += 1; } } return { pathList, pathMap, nameMap }; } /* * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function createMatcher(routes, router) { const { pathList, pathMap, nameMap } = createRouteMap(routes); function addRoutes(rs) { createRouteMap(rs, pathList, pathMap, nameMap); } function match(raw, currentRoute, redirectedFrom) { const location = normalizeLocation(raw, currentRoute, false, router); const { name } = location; if (name) { const record = nameMap[name]; if (process.env.NODE_ENV !== 'production') { warn(record, `Route with name '${name}' does not exist`); } if (!record) return _createRoute(null, location); const paramNames = record.regex.keys.filter(key => !key.optional).map(key => key.name); if (typeof location.params !== 'object') { location.params = {}; } if (currentRoute && typeof currentRoute.params === 'object') { Object.keys(currentRoute.params).forEach(key => { if (!(key in location.params) && paramNames.indexOf(key) > -1) { location.params[key] = currentRoute.params[key]; } }); } if (record) { location.path = fillParams(record.path, location.params, `named route "${name}"`); return _createRoute(record, location, redirectedFrom); } } else if (location.path) { location.params = {}; for (let i = 0; i < pathList.length; i += 1) { const path = pathList[i]; const record = pathMap[path]; if (matchRoute(record.regex, location.path, location.params)) { return _createRoute(record, location, redirectedFrom); } } } // no match return _createRoute(null, location); } function redirect(record, location) { const originalRedirect = record.redirect; let redirect = typeof originalRedirect === 'function' ? originalRedirect(createRoute(record, location, null, router)) : originalRedirect; if (typeof redirect === 'string') { redirect = { path: redirect }; } if (!redirect || typeof redirect !== 'object') { if (process.env.NODE_ENV !== 'production') { warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`); } return _createRoute(null, location); } const re = redirect; const { name, path } = re; let { query, hash, params } = location; query = Object.prototype.hasOwnProperty.call(re, 'query') ? re.query : query; hash = Object.prototype.hasOwnProperty.call(re, 'hash') ? re.hash : hash; params = Object.prototype.hasOwnProperty.call(re, 'params') ? re.params : params; if (name) { // resolved named direct const targetRecord = nameMap[name]; if (process.env.NODE_ENV !== 'production') { assert(targetRecord, `redirect failed: named route "${name}" not found.`); } return match({ _normalized: true, name, query, hash, params }, undefined, location); } if (path) { // 1. resolve relative redirect const rawPath = resolveRecordPath(path, record); // 2. resolve params const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`); // 3. rematch with existing query and hash return match({ _normalized: true, path: resolvedPath, query, hash }, undefined, location); } if (process.env.NODE_ENV !== 'production') { warn(false, `invalid redirect option: ${JSON.stringify(redirect)}`); } return _createRoute(null, location); } function alias(record, location, matchAs) { const aliasedPath = fillParams(matchAs, location.params, `aliased route with path "${matchAs}"`); const aliasedMatch = match({ _normalized: true, path: aliasedPath }); if (aliasedMatch) { const { matched } = aliasedMatch; const aliasedRecord = matched[matched.length - 1]; location.params = aliasedMatch.params; return _createRoute(aliasedRecord, location); } return _createRoute(null, location); } function _createRoute(record, location, redirectedFrom) { if (record && record.redirect) { return redirect(record, redirectedFrom || location); } if (record && record.matchAs) {