UNPKG

46.7 kBJavaScriptView Raw
1import { createBrowserHistory, createHashHistory, Action, parsePath } from 'history';
2import { Current, incrementId, eventCenter, createPageConfig, hooks, stringify, requestAnimationFrame } from '@tarojs/runtime';
3import MobileDetect from 'mobile-detect';
4import queryString from 'query-string';
5import { defineCustomElementTaroTabbar } from '@tarojs/components/dist/components';
6import { initTabBarApis } from '@tarojs/taro';
7import UniversalRouter from 'universal-router';
8
9/******************************************************************************
10Copyright (c) Microsoft Corporation.
11
12Permission to use, copy, modify, and/or distribute this software for any
13purpose with or without fee is hereby granted.
14
15THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
16REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
17AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
18INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
19LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
20OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
21PERFORMANCE OF THIS SOFTWARE.
22***************************************************************************** */
23
24function __awaiter(thisArg, _arguments, P, generator) {
25 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
26 return new (P || (P = Promise))(function (resolve, reject) {
27 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
28 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
29 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
30 step((generator = generator.apply(thisArg, _arguments || [])).next());
31 });
32}
33
34// export const removeLeadingSlash = (str = '') => str.replace(/^\.?\//, '')
35// export const removeTrailingSearch = (str = '') => str.replace(/\?[\s\S]*$/, '')
36const addLeadingSlash = (url = '') => (url.charAt(0) === '/' ? url : '/' + url);
37const hasBasename = (path = '', prefix = '') => new RegExp('^' + prefix + '(\\/|\\?|#|$)', 'i').test(path) || path === prefix;
38const stripBasename = (path = '', prefix = '') => hasBasename(path, prefix) ? path.substring(prefix.length) : path;
39const stripTrailing = (str = '') => str.replace(/[?#][\s\S]*$/, '');
40const getHomePage = (path = '', basename = '', customRoutes = {}, entryPagePath = '') => {
41 var _a;
42 const routePath = addLeadingSlash(stripBasename(path, basename));
43 const alias = ((_a = Object.entries(customRoutes).find(([key]) => key === routePath)) === null || _a === void 0 ? void 0 : _a[1]) || routePath;
44 return entryPagePath || (typeof alias === 'string' ? alias : alias[0]) || basename;
45};
46const getCurrentPage = (routerMode = 'hash', basename = '/') => {
47 const pagePath = routerMode === 'hash'
48 ? location.hash.slice(1).split('?')[0]
49 : location.pathname;
50 return addLeadingSlash(stripBasename(pagePath, basename));
51};
52class RoutesAlias {
53 constructor() {
54 this.conf = [];
55 this.getConfig = (url = '') => {
56 const customRoute = this.conf.filter((arr) => {
57 return arr.includes(url);
58 });
59 return customRoute[0];
60 };
61 this.getOrigin = (url = '') => {
62 var _a;
63 return ((_a = this.getConfig(url)) === null || _a === void 0 ? void 0 : _a[0]) || url;
64 };
65 this.getAlias = (url = '') => {
66 var _a;
67 return ((_a = this.getConfig(url)) === null || _a === void 0 ? void 0 : _a[1]) || url;
68 };
69 this.getAll = (url = '') => {
70 return this.conf
71 .filter((arr) => arr.includes(url))
72 .reduceRight((p, a) => {
73 p.unshift(a[1]);
74 return p;
75 }, []);
76 };
77 }
78 set(customRoutes = {}) {
79 for (let key in customRoutes) {
80 const path = customRoutes[key];
81 key = addLeadingSlash(key);
82 if (typeof path === 'string') {
83 this.conf.push([key, addLeadingSlash(path)]);
84 }
85 else if ((path === null || path === void 0 ? void 0 : path.length) > 0) {
86 this.conf.push(...path.map(p => [key, addLeadingSlash(p)]));
87 }
88 }
89 }
90}
91const routesAlias = new RoutesAlias();
92
93class RouterConfig {
94 static set config(e) {
95 this.__config = e;
96 }
97 static get config() {
98 return this.__config;
99 }
100 static get pages() {
101 return this.config.pages || [];
102 }
103 static get router() {
104 return this.config.router || {};
105 }
106 static get mode() {
107 return this.router.mode || 'hash';
108 }
109 static get customRoutes() { return this.router.customRoutes || {}; }
110 static isPage(url = '') {
111 return this.pages.findIndex(e => addLeadingSlash(e) === url) !== -1;
112 }
113}
114
115let history;
116let basename = '/';
117class MpaHistory {
118 constructor() {
119 this.back = window.history.back;
120 this.forward = window.history.forward;
121 this.pushState = this.eventState('pushState');
122 this.replaceState = this.eventState('replaceState');
123 }
124 get location() {
125 return {
126 pathname: window.location.pathname,
127 search: window.location.search,
128 hash: window.location.hash,
129 key: `${window.history.length}`,
130 state: window.history.state
131 };
132 }
133 createHref(_to) {
134 throw new Error('Method not implemented.');
135 }
136 parseUrl(to) {
137 let url = to.pathname || '';
138 if (RouterConfig.isPage(url)) {
139 url += '.html';
140 }
141 if (to.search) {
142 url += `?${to.search}`;
143 }
144 if (to.hash) {
145 url += `#${to.hash}`;
146 }
147 return url;
148 }
149 push(to, _state = {}) {
150 window.location.assign(this.parseUrl(to));
151 // this.pushState(_state, '', this.parseUrl(to))
152 }
153 replace(to, _state = {}) {
154 window.location.replace(this.parseUrl(to));
155 // this.replaceState(_state, '', this.parseUrl(to))
156 }
157 go(delta) {
158 window.history.go(delta);
159 }
160 listen(listener) {
161 function callback(e) {
162 if (e.action === 'pushState') {
163 listener({ action: Action.Push, location: this.location });
164 }
165 else if (e.action === 'replaceState') {
166 listener({ action: Action.Replace, location: this.location });
167 }
168 else {
169 // NOTE: 这里包括 back、forward、go 三种可能,并非是 POP 事件
170 listener({ action: Action.Pop, location: this.location });
171 }
172 }
173 window.addEventListener('popstate', callback);
174 return () => {
175 window.removeEventListener('popstate', callback);
176 };
177 }
178 block(_blocker) {
179 throw new Error('Method not implemented.');
180 }
181 eventState(action) {
182 return (data, unused, url) => {
183 const wrapper = window.history[action](data, unused, url);
184 const evt = new Event(action);
185 evt.action = action;
186 evt.state = data;
187 evt.unused = unused;
188 evt.url = url;
189 window.dispatchEvent(evt);
190 return wrapper;
191 };
192 }
193}
194function setHistoryMode(mode, base = '/') {
195 const options = {
196 window
197 };
198 basename = base;
199 if (mode === 'browser') {
200 history = createBrowserHistory(options);
201 }
202 else if (mode === 'multi') {
203 history = new MpaHistory();
204 }
205 else {
206 // default is hash
207 history = createHashHistory(options);
208 }
209}
210function prependBasename(url = '') {
211 return basename.replace(/\/$/, '') + '/' + url.replace(/^\//, '');
212}
213
214class Stacks {
215 constructor() {
216 this.stacks = [];
217 this.backDelta = 0;
218 this.tabs = {};
219 this.methodName = '';
220 }
221 set delta(delta) {
222 if (delta > 0) {
223 this.backDelta = delta;
224 }
225 else if (this.backDelta > 0) {
226 --this.backDelta;
227 }
228 else {
229 this.backDelta = 0;
230 }
231 }
232 get delta() {
233 return this.backDelta;
234 }
235 set method(methodName) {
236 this.methodName = methodName;
237 }
238 get method() {
239 return this.methodName;
240 }
241 get length() {
242 return this.stacks.length;
243 }
244 get last() {
245 return this.stacks[this.length - 1];
246 }
247 get() {
248 return this.stacks;
249 }
250 getItem(index) {
251 return this.stacks[index];
252 }
253 getLastIndex(pathname, stateWith = 1) {
254 const list = [...this.stacks].reverse();
255 return list.findIndex((page, i) => { var _a; return i >= stateWith && ((_a = page.path) === null || _a === void 0 ? void 0 : _a.replace(/\?.*/g, '')) === pathname; });
256 }
257 getDelta(pathname) {
258 if (this.backDelta >= 1) {
259 return this.backDelta;
260 }
261 const index = this.getLastIndex(pathname);
262 // NOTE: 此处为了修复浏览器后退多级页面,在大量重复路由状况下可能出现判断错误的情况 (增强判断能力只能考虑在 query 中新增参数来判断,暂时搁置)
263 return index > 0 ? index : 1;
264 }
265 getPrevIndex(pathname, stateWith = 1) {
266 const lastIndex = this.getLastIndex(pathname, stateWith);
267 if (lastIndex < 0) {
268 return -1;
269 }
270 return this.length - 1 - lastIndex;
271 }
272 pop() {
273 return this.stacks.pop();
274 }
275 push(page) {
276 return this.stacks.push(page);
277 }
278 getTabs() {
279 return this.tabs;
280 }
281 pushTab(path) {
282 this.tabs[path] = this.last;
283 this.pop();
284 }
285 popTab(path) {
286 this.push(this.tabs[path]);
287 delete this.tabs[path];
288 }
289 removeTab(path) {
290 delete this.tabs[path];
291 }
292}
293const stacks = new Stacks();
294
295function processNavigateUrl(option) {
296 var _a;
297 const pathPieces = parsePath(option.url);
298 // 处理相对路径
299 if ((_a = pathPieces.pathname) === null || _a === void 0 ? void 0 : _a.includes('./')) {
300 const parts = routesAlias.getOrigin(history.location.pathname).split('/');
301 parts.pop();
302 pathPieces.pathname.split('/').forEach((item) => {
303 if (item === '.') {
304 return;
305 }
306 item === '..' ? parts.pop() : parts.push(item);
307 });
308 pathPieces.pathname = parts.join('/');
309 }
310 // 处理自定义路由
311 pathPieces.pathname = routesAlias.getAlias(addLeadingSlash(pathPieces.pathname));
312 // 处理 basename
313 pathPieces.pathname = prependBasename(pathPieces.pathname);
314 // hack fix history v5 bug: https://github.com/remix-run/history/issues/814
315 if (!pathPieces.search)
316 pathPieces.search = '';
317 return pathPieces;
318}
319function navigate(option, method) {
320 return __awaiter(this, void 0, void 0, function* () {
321 return new Promise((resolve, reject) => {
322 stacks.method = method;
323 const { success, complete, fail } = option;
324 const unListen = history.listen(() => {
325 const res = { errMsg: `${method}:ok` };
326 success === null || success === void 0 ? void 0 : success(res);
327 complete === null || complete === void 0 ? void 0 : complete(res);
328 resolve(res);
329 unListen();
330 });
331 try {
332 if ('url' in option) {
333 const pathPieces = processNavigateUrl(option);
334 const state = { timestamp: Date.now() };
335 if (method === 'navigateTo') {
336 history.push(pathPieces, state);
337 }
338 else if (method === 'redirectTo' || method === 'switchTab') {
339 history.replace(pathPieces, state);
340 }
341 else if (method === 'reLaunch') {
342 stacks.delta = stacks.length;
343 history.replace(pathPieces, state);
344 }
345 }
346 else if (method === 'navigateBack') {
347 stacks.delta = option.delta;
348 history.go(-option.delta);
349 }
350 }
351 catch (error) {
352 const res = { errMsg: `${method}:fail ${error.message || error}` };
353 fail === null || fail === void 0 ? void 0 : fail(res);
354 complete === null || complete === void 0 ? void 0 : complete(res);
355 reject(res);
356 }
357 });
358 });
359}
360function navigateTo(option) {
361 return navigate(option, 'navigateTo');
362}
363function redirectTo(option) {
364 return navigate(option, 'redirectTo');
365}
366function navigateBack(option = { delta: 1 }) {
367 if (!option.delta || option.delta < 1) {
368 option.delta = 1;
369 }
370 return navigate(option, 'navigateBack');
371}
372function switchTab(option) {
373 return navigate(option, 'switchTab');
374}
375function reLaunch(option) {
376 return navigate(option, 'reLaunch');
377}
378function getCurrentPages() {
379 if (process.env.NODE_ENV !== 'production' && RouterConfig.mode === 'multi') {
380 console.warn('多页面路由模式不支持使用 getCurrentPages 方法!');
381 }
382 const pages = stacks.get();
383 return pages.map(e => { var _a; return (Object.assign(Object.assign({}, e), { route: ((_a = e.path) === null || _a === void 0 ? void 0 : _a.replace(/\?.*/g, '')) || '' })); });
384}
385
386let md;
387let preTitle = document.title;
388let isLoadDdEntry = false;
389function getMobileDetect() {
390 if (!md) {
391 md = new MobileDetect(navigator.userAgent);
392 }
393 return md;
394}
395function setTitle(title) {
396 return __awaiter(this, void 0, void 0, function* () {
397 if (preTitle === title)
398 return title;
399 document.title = title;
400 preTitle = title;
401 if (process.env.SUPPORT_DINGTALK_NAVIGATE !== 'disabled' && isDingTalk()) {
402 if (!isLoadDdEntry) {
403 isLoadDdEntry = true;
404 require('dingtalk-jsapi/platform');
405 }
406 const setDingTitle = require('dingtalk-jsapi/api/biz/navigation/setTitle').default;
407 setDingTitle({ title });
408 }
409 return title;
410 });
411}
412function isDingTalk() {
413 const md = getMobileDetect();
414 return md.match(/DingTalk/ig);
415}
416
417let pageResizeFn;
418function bindPageResize(page) {
419 pageResizeFn && window.removeEventListener('resize', pageResizeFn);
420 pageResizeFn = function () {
421 page.onResize && page.onResize({
422 size: {
423 windowHeight: window.innerHeight,
424 windowWidth: window.innerWidth
425 }
426 });
427 };
428 window.addEventListener('resize', pageResizeFn, false);
429}
430
431const pageScrollFn = {};
432let pageDOM = window;
433function bindPageScroll(page, pageEl, distance = 50) {
434 var _a;
435 const pagePath = (page ? page === null || page === void 0 ? void 0 : page.path : (_a = Current.router) === null || _a === void 0 ? void 0 : _a.path);
436 pageScrollFn[pagePath] && pageEl.removeEventListener('scroll', pageScrollFn[pagePath]);
437 pageDOM = pageEl;
438 let isReachBottom = false;
439 pageScrollFn[pagePath] = function () {
440 var _a;
441 (_a = page.onPageScroll) === null || _a === void 0 ? void 0 : _a.call(page, {
442 scrollTop: pageDOM instanceof Window ? window.scrollY : pageDOM.scrollTop
443 });
444 if (isReachBottom && getOffset() > distance) {
445 isReachBottom = false;
446 }
447 if (page.onReachBottom &&
448 !isReachBottom &&
449 getOffset() < distance) {
450 isReachBottom = true;
451 page.onReachBottom();
452 }
453 };
454 pageDOM.addEventListener('scroll', pageScrollFn[pagePath], false);
455}
456function getOffset() {
457 if (pageDOM instanceof Window) {
458 return document.documentElement.scrollHeight - window.scrollY - window.innerHeight;
459 }
460 else {
461 return pageDOM.scrollHeight - pageDOM.scrollTop - pageDOM.clientHeight;
462 }
463}
464
465// @ts-nocheck
466function initTabbar(config) {
467 if (config.tabBar == null) {
468 return;
469 }
470 // TODO: custom-tab-bar
471 defineCustomElementTaroTabbar();
472 const tabbar = document.createElement('taro-tabbar');
473 const homePage = config.entryPagePath || (config.pages ? config.pages[0] : '');
474 tabbar.conf = config.tabBar;
475 tabbar.conf.homePage = history.location.pathname === '/' ? homePage : history.location.pathname;
476 const routerConfig = config.router;
477 tabbar.conf.mode = routerConfig && routerConfig.mode ? routerConfig.mode : 'hash';
478 if (routerConfig.customRoutes) {
479 tabbar.conf.custom = true;
480 tabbar.conf.customRoutes = routerConfig.customRoutes;
481 }
482 else {
483 tabbar.conf.custom = false;
484 tabbar.conf.customRoutes = {};
485 }
486 if (typeof routerConfig.basename !== 'undefined') {
487 tabbar.conf.basename = routerConfig.basename;
488 }
489 const container = document.getElementById('container');
490 container === null || container === void 0 ? void 0 : container.appendChild(tabbar);
491 initTabBarApis(config);
492}
493
494/* eslint-disable dot-notation */
495class MultiPageHandler {
496 constructor(config) {
497 this.config = config;
498 this.mount();
499 }
500 get appId() { return this.config.appId || 'app'; }
501 get router() { return this.config.router || {}; }
502 get routerMode() { return this.router.mode || 'hash'; }
503 get customRoutes() { return this.router.customRoutes || {}; }
504 get tabBarList() { var _a; return ((_a = this.config.tabBar) === null || _a === void 0 ? void 0 : _a.list) || []; }
505 get PullDownRefresh() { return this.config.PullDownRefresh; }
506 set pathname(p) { this.router.pathname = p; }
507 get pathname() { return this.router.pathname; }
508 get basename() { return this.router.basename || ''; }
509 get pageConfig() { return this.config.route; }
510 get isTabBar() {
511 var _a;
512 const routePath = addLeadingSlash(stripBasename(this.pathname, this.basename));
513 const pagePath = ((_a = Object.entries(this.customRoutes).find(([, target]) => {
514 if (typeof target === 'string') {
515 return target === routePath;
516 }
517 else if ((target === null || target === void 0 ? void 0 : target.length) > 0) {
518 return target.includes(routePath);
519 }
520 return false;
521 })) === null || _a === void 0 ? void 0 : _a[0]) || routePath;
522 return !!pagePath && this.tabBarList.some(t => t.pagePath === pagePath);
523 }
524 get search() { return location.search.substr(1); }
525 getQuery(search = '', options = {}) {
526 search = search ? `${search}&${this.search}` : this.search;
527 const query = search
528 ? queryString.parse(search)
529 : {};
530 return Object.assign(Object.assign({}, query), options);
531 }
532 mount() {
533 setHistoryMode(this.routerMode, this.router.basename);
534 const appId = this.appId;
535 let app = document.getElementById(appId);
536 let isPosition = true;
537 if (!app) {
538 app = document.createElement('div');
539 app.id = appId;
540 isPosition = false;
541 }
542 const appWrapper = (app === null || app === void 0 ? void 0 : app.parentNode) || (app === null || app === void 0 ? void 0 : app.parentElement) || document.body;
543 app.classList.add('taro_router');
544 if (this.tabBarList.length > 1) {
545 const container = document.createElement('div');
546 container.classList.add('taro-tabbar__container');
547 container.id = 'container';
548 const panel = document.createElement('div');
549 panel.classList.add('taro-tabbar__panel');
550 panel.appendChild(app.cloneNode(true));
551 container.appendChild(panel);
552 if (!isPosition) {
553 appWrapper.appendChild(container);
554 }
555 else {
556 appWrapper.replaceChild(container, app);
557 }
558 initTabbar(this.config);
559 }
560 else {
561 if (!isPosition)
562 appWrapper.appendChild(app);
563 }
564 }
565 onReady(page, onLoad = true) {
566 var _a;
567 const pageEl = this.getPageContainer(page);
568 if (pageEl && !(pageEl === null || pageEl === void 0 ? void 0 : pageEl['__isReady'])) {
569 const el = pageEl.firstElementChild;
570 (_a = el === null || el === void 0 ? void 0 : el['componentOnReady']) === null || _a === void 0 ? void 0 : _a.call(el);
571 onLoad && (pageEl['__page'] = page);
572 }
573 }
574 load(page, pageConfig = {}) {
575 var _a;
576 if (!page)
577 return;
578 (_a = page.onLoad) === null || _a === void 0 ? void 0 : _a.call(page, this.getQuery('', page.options), () => {
579 var _a;
580 const pageEl = this.getPageContainer(page);
581 this.isTabBar && (pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.add('taro_tabbar_page'));
582 this.onReady(page, true);
583 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
584 this.bindPageEvents(page, pageEl, pageConfig);
585 });
586 }
587 getPageContainer(page) {
588 var _a;
589 const path = page ? page === null || page === void 0 ? void 0 : page.path : (_a = Current.page) === null || _a === void 0 ? void 0 : _a.path;
590 const id = path === null || path === void 0 ? void 0 : path.replace(/([^a-z0-9\u00a0-\uffff_-])/ig, '\\$1');
591 if (page) {
592 return document.querySelector(`.taro_page#${id}`);
593 }
594 const el = (id
595 ? document.querySelector(`.taro_page#${id}`)
596 : document.querySelector('.taro_page') ||
597 document.querySelector('.taro_router'));
598 return el || window;
599 }
600 bindPageEvents(page, pageEl, config = {}) {
601 var _a;
602 if (!pageEl) {
603 pageEl = this.getPageContainer();
604 }
605 const distance = config.onReachBottomDistance || ((_a = this.config.window) === null || _a === void 0 ? void 0 : _a.onReachBottomDistance) || 50;
606 bindPageScroll(page, pageEl, distance);
607 bindPageResize(page);
608 }
609}
610
611const createStampId$1 = incrementId();
612const launchStampId$1 = createStampId$1();
613// TODO 支持多路由 (APP 生命周期仅触发一次)
614/** Note: 关于多页面应用
615 * - 需要配置路由映射(根目录跳转、404 页面……)
616 * - app.onPageNotFound 事件不支持
617 * - 应用生命周期可能多次触发
618 * - TabBar 会多次加载
619 * - 不支持路由动画
620 */
621function createMultiRouter(app, config, framework) {
622 var _a, _b, _c, _d, _e, _f;
623 return __awaiter(this, void 0, void 0, function* () {
624 if (typeof app.onUnhandledRejection === 'function') {
625 window.addEventListener('unhandledrejection', app.onUnhandledRejection);
626 }
627 RouterConfig.config = config;
628 const handler = new MultiPageHandler(config);
629 const launchParam = {
630 path: config.pageName,
631 query: handler.getQuery(launchStampId$1),
632 scene: 0,
633 shareTicket: '',
634 referrerInfo: {}
635 };
636 eventCenter.trigger('__taroRouterLaunch', launchParam);
637 (_a = app.onLaunch) === null || _a === void 0 ? void 0 : _a.call(app, launchParam);
638 app.onError && window.addEventListener('error', e => { var _a; return (_a = app.onError) === null || _a === void 0 ? void 0 : _a.call(app, e.message); });
639 const pathName = config.pageName;
640 const pageConfig = handler.pageConfig;
641 eventCenter.trigger('__taroRouterChange', {
642 toLocation: {
643 path: pathName
644 }
645 });
646 let element;
647 try {
648 element = yield ((_b = pageConfig.load) === null || _b === void 0 ? void 0 : _b.call(pageConfig));
649 if (element instanceof Array) {
650 element = element[0];
651 }
652 }
653 catch (error) {
654 throw new Error(error);
655 }
656 if (!element)
657 return;
658 let enablePullDownRefresh = ((_c = config === null || config === void 0 ? void 0 : config.window) === null || _c === void 0 ? void 0 : _c.enablePullDownRefresh) || false;
659 if (pageConfig) {
660 setTitle((_d = pageConfig.navigationBarTitleText) !== null && _d !== void 0 ? _d : document.title);
661 if (typeof pageConfig.enablePullDownRefresh === 'boolean') {
662 enablePullDownRefresh = pageConfig.enablePullDownRefresh;
663 }
664 }
665 const el = (_e = element.default) !== null && _e !== void 0 ? _e : element;
666 const loadConfig = Object.assign({}, pageConfig);
667 delete loadConfig['path'];
668 delete loadConfig['load'];
669 const page = createPageConfig(enablePullDownRefresh ? hooks.call('createPullDownComponent', el, location.pathname, framework, handler.PullDownRefresh) : el, pathName + stringify(launchParam), {}, loadConfig);
670 handler.load(page, pageConfig);
671 (_f = app.onShow) === null || _f === void 0 ? void 0 : _f.call(app, launchParam);
672 });
673}
674
675/**
676 * 插入页面动画需要的样式
677 */
678function loadAnimateStyle(ms = 300) {
679 const css = `
680.taro_router .taro_page {
681 position: absolute;
682 left: 0;
683 top: 0;
684 width: 100%;
685 height: 100%;
686 background-color: #fff;
687 transform: translate(100%, 0);
688 transition: transform ${ms}ms;
689 z-index: 0;
690}
691
692.taro_router .taro_page.taro_tabbar_page,
693.taro_router .taro_page.taro_page_show.taro_page_stationed {
694 transform: none;
695}
696
697.taro_router .taro_page.taro_page_show {
698 transform: translate(0, 0);
699}`;
700 const style = document.createElement('style');
701 style.innerHTML = css;
702 document.getElementsByTagName('head')[0].appendChild(style);
703}
704
705/* eslint-disable dot-notation */
706function setDisplay(el, type = '') {
707 if (el) {
708 el.style.display = type;
709 }
710}
711class PageHandler {
712 constructor(config) {
713 this.defaultAnimation = { duration: 300, delay: 50 };
714 this.config = config;
715 this.homePage = getHomePage(this.routes[0].path, this.basename, this.customRoutes, this.config.entryPagePath);
716 this.mount();
717 }
718 get currentPage() {
719 const routePath = getCurrentPage(this.routerMode, this.basename);
720 return routePath === '/' ? this.homePage : routePath;
721 }
722 get appId() { return this.config.appId || 'app'; }
723 get router() { return this.config.router || {}; }
724 get routerMode() { return this.router.mode || 'hash'; }
725 get customRoutes() { return this.router.customRoutes || {}; }
726 get routes() { return this.config.routes || []; }
727 get tabBarList() { var _a; return ((_a = this.config.tabBar) === null || _a === void 0 ? void 0 : _a.list) || []; }
728 get PullDownRefresh() { return this.config.PullDownRefresh; }
729 get animation() { var _a, _b; return (_b = (_a = this.config) === null || _a === void 0 ? void 0 : _a.animation) !== null && _b !== void 0 ? _b : this.defaultAnimation; }
730 get animationDelay() {
731 var _a;
732 return (typeof this.animation === 'object'
733 ? this.animation.delay
734 : this.animation
735 ? (_a = this.defaultAnimation) === null || _a === void 0 ? void 0 : _a.delay
736 : 0) || 0;
737 }
738 get animationDuration() {
739 var _a;
740 return (typeof this.animation === 'object'
741 ? this.animation.duration
742 : this.animation
743 ? (_a = this.defaultAnimation) === null || _a === void 0 ? void 0 : _a.duration
744 : 0) || 0;
745 }
746 set pathname(p) { this.router.pathname = p; }
747 get pathname() { return this.router.pathname; }
748 get basename() { return this.router.basename || ''; }
749 get pageConfig() {
750 const routePath = addLeadingSlash(stripBasename(this.pathname, this.basename));
751 const homePage = addLeadingSlash(this.homePage);
752 return this.routes.find(r => {
753 var _a;
754 const pagePath = addLeadingSlash(r.path);
755 return [pagePath, homePage].includes(routePath) || ((_a = routesAlias.getConfig(pagePath)) === null || _a === void 0 ? void 0 : _a.includes(routePath));
756 });
757 }
758 isTabBar(pathname) {
759 var _a;
760 const routePath = addLeadingSlash(stripBasename(pathname, this.basename)).split('?')[0];
761 const pagePath = ((_a = Object.entries(this.customRoutes).find(([, target]) => {
762 if (typeof target === 'string') {
763 return target === routePath;
764 }
765 else if ((target === null || target === void 0 ? void 0 : target.length) > 0) {
766 return target.includes(routePath);
767 }
768 return false;
769 })) === null || _a === void 0 ? void 0 : _a[0]) || routePath;
770 return !!pagePath && this.tabBarList.some(t => stripTrailing(t.pagePath) === pagePath);
771 }
772 isSamePage(page) {
773 const routePath = stripBasename(this.pathname, this.basename);
774 const pagePath = stripBasename(page === null || page === void 0 ? void 0 : page.path, this.basename);
775 return pagePath.startsWith(routePath + '?');
776 }
777 get search() {
778 let search = '?';
779 if (this.routerMode === 'hash') {
780 const idx = location.hash.indexOf('?');
781 if (idx > -1) {
782 search = location.hash.slice(idx);
783 }
784 }
785 else {
786 search = location.search;
787 }
788 return search.substring(1);
789 }
790 getQuery(stamp = '', search = '', options = {}) {
791 search = search ? `${search}&${this.search}` : this.search;
792 const query = search
793 ? queryString.parse(search, { decode: false })
794 : {};
795 query.stamp = stamp;
796 return Object.assign(Object.assign({}, query), options);
797 }
798 mount() {
799 setHistoryMode(this.routerMode, this.router.basename);
800 this.animation && loadAnimateStyle(this.animationDuration);
801 const appId = this.appId;
802 let app = document.getElementById(appId);
803 let isPosition = true;
804 if (!app) {
805 app = document.createElement('div');
806 app.id = appId;
807 isPosition = false;
808 }
809 const appWrapper = (app === null || app === void 0 ? void 0 : app.parentNode) || (app === null || app === void 0 ? void 0 : app.parentElement) || document.body;
810 app.classList.add('taro_router');
811 if (this.tabBarList.length > 1) {
812 const container = document.createElement('div');
813 container.classList.add('taro-tabbar__container');
814 container.id = 'container';
815 const panel = document.createElement('div');
816 panel.classList.add('taro-tabbar__panel');
817 panel.appendChild(app.cloneNode(true));
818 container.appendChild(panel);
819 if (!isPosition) {
820 appWrapper.appendChild(container);
821 }
822 else {
823 appWrapper.replaceChild(container, app);
824 }
825 initTabbar(this.config);
826 }
827 else {
828 if (!isPosition)
829 appWrapper.appendChild(app);
830 }
831 }
832 onReady(page, onLoad = true) {
833 var _a;
834 const pageEl = this.getPageContainer(page);
835 if (pageEl && !(pageEl === null || pageEl === void 0 ? void 0 : pageEl['__isReady'])) {
836 const el = pageEl.firstElementChild;
837 const componentOnReady = el === null || el === void 0 ? void 0 : el['componentOnReady'];
838 if (componentOnReady) {
839 componentOnReady === null || componentOnReady === void 0 ? void 0 : componentOnReady().then(() => {
840 requestAnimationFrame(() => {
841 var _a;
842 (_a = page.onReady) === null || _a === void 0 ? void 0 : _a.call(page);
843 pageEl['__isReady'] = true;
844 });
845 });
846 }
847 else {
848 (_a = page.onReady) === null || _a === void 0 ? void 0 : _a.call(page);
849 pageEl['__isReady'] = true;
850 }
851 onLoad && (pageEl['__page'] = page);
852 }
853 }
854 load(page, pageConfig = {}, stampId, pageNo = 0) {
855 var _a, _b;
856 if (!page)
857 return;
858 // NOTE: 页面栈推入太晚可能导致 getCurrentPages 无法获取到当前页面实例
859 stacks.push(page);
860 const param = this.getQuery(stampId, '', page.options);
861 let pageEl = this.getPageContainer(page);
862 if (pageEl) {
863 setDisplay(pageEl);
864 this.isTabBar(this.pathname) && pageEl.classList.add('taro_tabbar_page');
865 this.addAnimation(pageEl, pageNo === 0);
866 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
867 this.bindPageEvents(page, pageEl, pageConfig);
868 }
869 else {
870 (_b = page.onLoad) === null || _b === void 0 ? void 0 : _b.call(page, param, () => {
871 var _a;
872 pageEl = this.getPageContainer(page);
873 this.isTabBar(this.pathname) && (pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.add('taro_tabbar_page'));
874 this.addAnimation(pageEl, pageNo === 0);
875 this.onReady(page, true);
876 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
877 this.bindPageEvents(page, pageEl, pageConfig);
878 });
879 }
880 }
881 unload(page, delta = 1, top = false) {
882 var _a, _b, _c;
883 if (!page)
884 return;
885 stacks.delta = --delta;
886 stacks.pop();
887 if (this.animation && top) {
888 if (this.unloadTimer) {
889 clearTimeout(this.unloadTimer);
890 (_b = (_a = this.lastUnloadPage) === null || _a === void 0 ? void 0 : _a.onUnload) === null || _b === void 0 ? void 0 : _b.call(_a);
891 this.unloadTimer = null;
892 }
893 this.lastUnloadPage = page;
894 const pageEl = this.getPageContainer(page);
895 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_stationed');
896 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_show');
897 this.unloadTimer = setTimeout(() => {
898 var _a, _b;
899 this.unloadTimer = null;
900 (_b = (_a = this.lastUnloadPage) === null || _a === void 0 ? void 0 : _a.onUnload) === null || _b === void 0 ? void 0 : _b.call(_a);
901 }, this.animationDuration);
902 }
903 else {
904 const pageEl = this.getPageContainer(page);
905 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_stationed');
906 pageEl === null || pageEl === void 0 ? void 0 : pageEl.classList.remove('taro_page_show');
907 (_c = page === null || page === void 0 ? void 0 : page.onUnload) === null || _c === void 0 ? void 0 : _c.call(page);
908 }
909 if (delta >= 1)
910 this.unload(stacks.last, delta);
911 }
912 show(page, pageConfig = {}, pageNo = 0) {
913 var _a, _b;
914 if (!page)
915 return;
916 const param = this.getQuery(page['$taroParams']['stamp'], '', page.options);
917 let pageEl = this.getPageContainer(page);
918 if (pageEl) {
919 setDisplay(pageEl);
920 this.addAnimation(pageEl, pageNo === 0);
921 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
922 this.bindPageEvents(page, pageEl, pageConfig);
923 }
924 else {
925 (_b = page.onLoad) === null || _b === void 0 ? void 0 : _b.call(page, param, () => {
926 var _a;
927 pageEl = this.getPageContainer(page);
928 this.addAnimation(pageEl, pageNo === 0);
929 this.onReady(page, false);
930 (_a = page.onShow) === null || _a === void 0 ? void 0 : _a.call(page);
931 this.bindPageEvents(page, pageEl, pageConfig);
932 });
933 }
934 }
935 hide(page) {
936 var _a;
937 if (!page)
938 return;
939 // NOTE: 修复多页并发问题,此处可能因为路由跳转过快,执行时页面可能还没有创建成功
940 const pageEl = this.getPageContainer(page);
941 if (pageEl) {
942 if (this.hideTimer) {
943 clearTimeout(this.hideTimer);
944 this.hideTimer = null;
945 setDisplay(this.lastHidePage, 'none');
946 }
947 this.lastHidePage = pageEl;
948 this.hideTimer = setTimeout(() => {
949 this.hideTimer = null;
950 setDisplay(this.lastHidePage, 'none');
951 }, this.animationDuration + this.animationDelay);
952 (_a = page.onHide) === null || _a === void 0 ? void 0 : _a.call(page);
953 }
954 else {
955 setTimeout(() => this.hide(page), 0);
956 }
957 }
958 addAnimation(pageEl, first = false) {
959 if (!pageEl)
960 return;
961 if (this.animation && !first) {
962 setTimeout(() => {
963 pageEl.classList.add('taro_page_show');
964 setTimeout(() => {
965 pageEl.classList.add('taro_page_stationed');
966 }, this.animationDuration);
967 }, this.animationDelay);
968 }
969 else {
970 pageEl.classList.add('taro_page_show');
971 pageEl.classList.add('taro_page_stationed');
972 }
973 }
974 getPageContainer(page) {
975 var _a;
976 const path = page ? page === null || page === void 0 ? void 0 : page.path : (_a = Current.page) === null || _a === void 0 ? void 0 : _a.path;
977 const id = path === null || path === void 0 ? void 0 : path.replace(/([^a-z0-9\u00a0-\uffff_-])/ig, '\\$1');
978 if (page) {
979 return document.querySelector(`.taro_page#${id}`);
980 }
981 const el = (id
982 ? document.querySelector(`.taro_page#${id}`)
983 : document.querySelector('.taro_page') ||
984 document.querySelector('.taro_router'));
985 return el || window;
986 }
987 bindPageEvents(page, pageEl, config = {}) {
988 var _a;
989 if (!pageEl) {
990 pageEl = this.getPageContainer();
991 }
992 const distance = config.onReachBottomDistance || ((_a = this.config.window) === null || _a === void 0 ? void 0 : _a.onReachBottomDistance) || 50;
993 bindPageScroll(page, pageEl, distance);
994 bindPageResize(page);
995 }
996}
997
998const createStampId = incrementId();
999let launchStampId = createStampId();
1000function createRouter(app, config, framework) {
1001 var _a, _b;
1002 if (typeof app.onUnhandledRejection === 'function') {
1003 window.addEventListener('unhandledrejection', app.onUnhandledRejection);
1004 }
1005 RouterConfig.config = config;
1006 const handler = new PageHandler(config);
1007 routesAlias.set(handler.router.customRoutes);
1008 const basename = handler.router.basename;
1009 const routes = handler.routes.map(route => {
1010 const routePath = addLeadingSlash(route.path);
1011 const paths = routesAlias.getAll(routePath);
1012 return {
1013 path: paths.length < 1 ? routePath : paths,
1014 action: route.load
1015 };
1016 });
1017 const router = new UniversalRouter(routes, { baseUrl: basename || '' });
1018 const launchParam = {
1019 path: handler.currentPage,
1020 query: handler.getQuery(launchStampId),
1021 scene: 0,
1022 shareTicket: '',
1023 referrerInfo: {}
1024 };
1025 eventCenter.trigger('__taroRouterLaunch', launchParam);
1026 (_a = app.onLaunch) === null || _a === void 0 ? void 0 : _a.call(app, launchParam);
1027 app.onError && window.addEventListener('error', e => { var _a; return (_a = app.onError) === null || _a === void 0 ? void 0 : _a.call(app, e.message); });
1028 const render = ({ location, action }) => __awaiter(this, void 0, void 0, function* () {
1029 var _c, _d, _e, _f, _g;
1030 handler.pathname = decodeURI(location.pathname);
1031 eventCenter.trigger('__taroRouterChange', {
1032 toLocation: {
1033 path: handler.pathname
1034 }
1035 });
1036 let element, params;
1037 try {
1038 const result = yield router.resolve(handler.router.forcePath || handler.pathname);
1039 [element, , params] = yield Promise.all(result);
1040 }
1041 catch (error) {
1042 if (error.status === 404) {
1043 const notFoundEvent = {
1044 isEntryPage: stacks.length === 0,
1045 path: handler.pathname,
1046 query: handler.getQuery(createStampId()),
1047 };
1048 (_c = app.onPageNotFound) === null || _c === void 0 ? void 0 : _c.call(app, notFoundEvent);
1049 eventCenter.trigger('__taroRouterNotFound', notFoundEvent);
1050 }
1051 else if (/Loading hot update .* failed./.test(error.message)) {
1052 // NOTE: webpack5 与 prebundle 搭配使用时,开发环境下初次启动时偶发错误,由于 HMR 加载 chunk hash 错误,导致热更新失败
1053 window.location.reload();
1054 }
1055 else {
1056 throw new Error(error);
1057 }
1058 }
1059 if (!element)
1060 return;
1061 const pageConfig = handler.pageConfig;
1062 let enablePullDownRefresh = ((_d = config === null || config === void 0 ? void 0 : config.window) === null || _d === void 0 ? void 0 : _d.enablePullDownRefresh) || false;
1063 if (pageConfig) {
1064 setTitle((_e = pageConfig.navigationBarTitleText) !== null && _e !== void 0 ? _e : document.title);
1065 if (typeof pageConfig.enablePullDownRefresh === 'boolean') {
1066 enablePullDownRefresh = pageConfig.enablePullDownRefresh;
1067 }
1068 }
1069 const currentPage = Current.page;
1070 const pathname = handler.pathname;
1071 const methodName = (_f = stacks.method) !== null && _f !== void 0 ? _f : '';
1072 const cacheTabs = stacks.getTabs();
1073 let shouldLoad = false;
1074 stacks.method = '';
1075 if (methodName === 'reLaunch') {
1076 handler.unload(currentPage, stacks.length);
1077 // NOTE: 同时卸载缓存在tabs里面的页面实例
1078 for (const key in cacheTabs) {
1079 if (cacheTabs[key]) {
1080 handler.unload(cacheTabs[key]);
1081 stacks.removeTab(key);
1082 }
1083 }
1084 shouldLoad = true;
1085 }
1086 else if (currentPage && handler.isTabBar(handler.pathname)) {
1087 if (handler.isSamePage(currentPage))
1088 return;
1089 if (handler.isTabBar(currentPage.path)) {
1090 handler.hide(currentPage);
1091 stacks.pushTab(currentPage.path.split('?')[0]);
1092 }
1093 else if (stacks.length > 0) {
1094 const firstIns = stacks.getItem(0);
1095 if (handler.isTabBar(firstIns.path)) {
1096 handler.unload(currentPage, stacks.length - 1);
1097 stacks.pushTab(firstIns.path.split('?')[0]);
1098 }
1099 else {
1100 handler.unload(currentPage, stacks.length);
1101 }
1102 }
1103 if (cacheTabs[handler.pathname]) {
1104 stacks.popTab(handler.pathname);
1105 return handler.show(stacks.getItem(0), pageConfig, 0);
1106 }
1107 shouldLoad = true;
1108 }
1109 else if (action === 'POP') {
1110 // NOTE: 浏览器事件退后多次时,该事件只会被触发一次
1111 const prevIndex = stacks.getPrevIndex(pathname);
1112 const delta = stacks.getDelta(pathname);
1113 // NOTE: Safari 内核浏览器在非应用页面返回上一页时,会触发额外的 POP 事件,此处需避免当前页面被错误卸载
1114 if (currentPage !== stacks.getItem(prevIndex)) {
1115 handler.unload(currentPage, delta, prevIndex > -1);
1116 if (prevIndex > -1) {
1117 handler.show(stacks.getItem(prevIndex), pageConfig, prevIndex);
1118 }
1119 else {
1120 shouldLoad = true;
1121 }
1122 }
1123 }
1124 else if (action === 'REPLACE') {
1125 const delta = stacks.getDelta(pathname);
1126 // NOTE: 页面路由记录并不会清空,只是移除掉缓存的 stack 以及页面
1127 handler.unload(currentPage, delta);
1128 shouldLoad = true;
1129 }
1130 else if (action === 'PUSH') {
1131 handler.hide(currentPage);
1132 shouldLoad = true;
1133 }
1134 if (shouldLoad || stacks.length < 1) {
1135 const el = (_g = element.default) !== null && _g !== void 0 ? _g : element;
1136 const loadConfig = Object.assign({}, pageConfig);
1137 const stacksIndex = stacks.length;
1138 delete loadConfig['path'];
1139 delete loadConfig['load'];
1140 let pageStampId = '';
1141 if (launchStampId) {
1142 pageStampId = launchStampId;
1143 launchStampId = '';
1144 }
1145 else {
1146 pageStampId = createStampId();
1147 }
1148 const page = createPageConfig(enablePullDownRefresh ? hooks.call('createPullDownComponent', el, location.pathname, framework, handler.PullDownRefresh) : el, pathname + stringify(handler.getQuery(pageStampId)), {}, loadConfig);
1149 if (params)
1150 page.options = params;
1151 handler.load(page, pageConfig, pageStampId, stacksIndex);
1152 }
1153 eventCenter.trigger('__afterTaroRouterChange', {
1154 toLocation: {
1155 path: handler.pathname
1156 }
1157 });
1158 });
1159 const routePath = addLeadingSlash(stripBasename(history.location.pathname, handler.basename));
1160 if (routePath === '/') {
1161 history.replace(prependBasename(handler.homePage + history.location.search));
1162 }
1163 render({ location: history.location, action: Action.Push });
1164 (_b = app.onShow) === null || _b === void 0 ? void 0 : _b.call(app, launchParam);
1165 return history.listen(render);
1166}
1167
1168export { createMultiRouter, createRouter, getCurrentPages, history, navigateBack, navigateTo, reLaunch, redirectTo, switchTab };
1169//# sourceMappingURL=index.esm.js.map