Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Feature: history api for using react(vue-)router in miniprogram #12497

Merged
merged 10 commits into from
Oct 30, 2022
5 changes: 4 additions & 1 deletion packages/taro-mini-runner/src/webpack/build.conf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,10 @@ export default (appPath: string, mode, config: Partial<IBuildConfig>): any => {
cancelAnimationFrame: ['@tarojs/runtime', 'cancelAnimationFrame'],
Element: ['@tarojs/runtime', 'TaroElement'],
SVGElement: ['@tarojs/runtime', 'SVGElement'],
MutationObserver: ['@tarojs/runtime', 'MutationObserver']
MutationObserver: ['@tarojs/runtime', 'MutationObserver'],
history: ['@tarojs/runtime', 'history'],
AdvancedCat marked this conversation as resolved.
Show resolved Hide resolved
location: ['@tarojs/runtime', 'location'],
URLSearchParams: ['@tarojs/runtime', 'URLSearchParams'],
})

const isCssoEnabled = !((csso && csso.enable === false))
Expand Down
3 changes: 3 additions & 0 deletions packages/taro-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@
"dependencies": {
"@tarojs/shared": "workspace:*",
"lodash-es": "4.17.21"
},
"devDependencies": {
AdvancedCat marked this conversation as resolved.
Show resolved Hide resolved
"tslib": "^2.4.0"
}
}
128 changes: 128 additions & 0 deletions packages/taro-runtime/src/bom/URLSearchParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { isArray } from '@tarojs/shared'

const findReg = /[!'()~]|%20|%00/g
const plusReg = /\+/g
const replaceCharMap = {
'!': '%21',
"'": '%27',
'(': '%28',
')': '%29',
'~': '%7E',
'%20': '+',
'%00': '\x00',
}

function replacer (match: string) {
return replaceCharMap[match]
}

function appendTo (dict: Record<string, string[]>, name: string, value: string) {
const res = isArray(value) ? value.join(',') : value
if (name in dict) dict[name].push(res)
else dict[name] = [res]
}

function addEach (value: string, key: string) {
appendTo(this, key, value)
}

function decode (str: string) {
return decodeURIComponent(str.replace(plusReg, ' '))
}

function encode (str: string) {
return encodeURIComponent(str).replace(findReg, replacer)
}

export class URLSearchParams {
#dict = Object.create(null)

constructor (query) {
if (!query) return

const dict = this.#dict

if (typeof query === 'string') {
if (query.charAt(0) === '?') {
query = query.slice(1)
}
for (let pairs = query.split('&'), i = 0, length = pairs.length; i < length; i++) {
const value = pairs[i]
const index = value.indexOf('=')
if (index > -1) {
appendTo(dict, decode(value.slice(0, index)), decode(value.slice(index + 1)))
} else if (value.length) {
appendTo(dict, decode(value), '')
}
}
} else {
if (isArray(query)) {
for (let i = 0, length = query.length; i < length; i++) {
const value = query[i]
appendTo(dict, value[0], value[1])
}
} else if (query.forEach) {
query.forEach(addEach, dict)
} else {
for (const key in query) {
appendTo(dict, key, query[key])
}
}
}
}

append (name: string, value: string) {
appendTo(this.#dict, name, value)
}

delete (name: string) {
delete this.#dict[name]
}

get (name: string) {
const dict = this.#dict
return name in dict ? dict[name][0] : null
}

getAll (name: string) {
const dict = this.#dict
return name in dict ? dict[name].slice(0) : []
}

has (name: string) {
return name in this.#dict
}

keys (){
return Object.keys(this.#dict)
}

set (name: string, value: string) {
this.#dict[name] = ['' + value]
}

forEach (callback, thisArg) {
const dict = this.#dict
Object.getOwnPropertyNames(dict).forEach(function (name) {
dict[name].forEach(function (value: string) {
callback.call(thisArg, value, name, this)
}, this)
}, this)
}

toJSON () {
return {}
}

toString () {
const dict = this.#dict
const query: any[] = []
for (const key in dict) {
const name = encode(key)
for (let i = 0, value = dict[key]; i < value.length; i++) {
query.push(name + '=' + encode(value[i]))
}
}
return query.join('&')
}
}
151 changes: 151 additions & 0 deletions packages/taro-runtime/src/bom/history.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { isNumber,isString } from '@tarojs/shared'

import { CONTEXT_ACTIONS } from '../constants'
import { Events } from '../emitter/emitter'
import { RuntimeCache } from '../utils/cache'
import type * as LocationType from './location'

export interface HistoryState {
state: Record<string, any> | null
title: string
url: string
}

type Options = {
window: any
}
type HistoryContext = {
location: LocationType.Location
stack: HistoryState[]
cur: number
}
const cache = new RuntimeCache<HistoryContext>('history')

export class History extends Events {
/* private property */
#location: LocationType.Location
#stack: HistoryState[] = []
#cur = 0

#window: any

constructor (location: LocationType.Location, options: Options) {
super()

this.#window = options.window
this.#location = location

this.#location.on('__record_history__', (href: string) => {
this.#cur++
this.#stack = this.#stack.slice(0, this.#cur)
this.#stack.push({
state: null,
title: '',
url: href
})
}, null)

this.#location.on('__reset_history__', (href: string) => {
this.#reset(href)
}, null)

// 切换上下文行为

this.on(CONTEXT_ACTIONS.INIT, () => {
this.#reset()
}, null)

this.on(CONTEXT_ACTIONS.RESTORE, (pageId: string) => {
cache.set(pageId, {
location: this.#location,
stack: this.#stack,
cur: this.#cur
})
}, null)

this.on(CONTEXT_ACTIONS.RECOVER, (pageId: string) => {
if (cache.has(pageId)) {
const ctx = cache.get(pageId)!
this.#location = ctx.location
this.#stack = ctx.stack
this.#cur = ctx.cur
}
}, null)

this.on(CONTEXT_ACTIONS.DESTORY, (pageId: string) => {
cache.delete(pageId)
}, null)

this.#reset()
}

#reset (href = '') {
this.#stack = [
{
state: null,
title: '',
url: href || this.#location.href
}
]
this.#cur = 0
}

/* public property */
get length () {
return this.#stack.length
}

get state () {
return this.#stack[this.#cur]
}

/* public method */
go (delta: number) {
if (!isNumber(delta) || isNaN(delta)) return

let targetIdx = this.#cur + delta
targetIdx = Math.min(Math.max(targetIdx, 0), this.length - 1)

this.#cur = targetIdx

this.#location.trigger('__set_href_without_history__', this.#stack[this.#cur].url)
this.#window.trigger('popstate', this.#stack[this.#cur])
}

back () {
this.go(-1)
}

forward () {
this.go(1)
}

pushState (state: any, title: string, url: string) {
if (!url || !isString(url)) return
this.#stack = this.#stack.slice(0, this.#cur + 1)
this.#stack.push({
state,
title,
url
})
this.#cur = this.length - 1

this.#location.trigger('__set_href_without_history__', url)
}

replaceState (state: any, title: string, url: string) {
if (!url || !isString(url)) return
this.#stack[this.#cur] = {
state,
title,
url
}

this.#location.trigger('__set_href_without_history__', url)
}

// For debug
get cache () {
return cache
}
}
Loading