UNPKG

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