diff --git a/packages/platform-mp/src/lib/bundler/config.ts b/packages/platform-mp/src/lib/bundler/config.ts index 89e340641..870d6440f 100644 --- a/packages/platform-mp/src/lib/bundler/config.ts +++ b/packages/platform-mp/src/lib/bundler/config.ts @@ -1,6 +1,7 @@ import path from 'path'; -import { IApi, APIHooks } from '@shuvi/types'; +import { IApi, APIHooks, Runtime } from '@shuvi/types'; import { BUNDLER_TARGET_SERVER } from '@shuvi/shared/lib/constants'; +import { rankRouteBranches } from '@shuvi/router'; import { PACKAGE_NAME } from '../constants'; import fs from 'fs'; import BuildAssetsPlugin from './plugins/build-assets-plugin'; @@ -9,6 +10,7 @@ import DomEnvPlugin from './plugins/dom-env-plugin'; import modifyStyle from './modifyStyle'; import { resolveAppFile, + resolveRouterFile, resolveDep, resolveLib, PACKAGE_RESOLVED @@ -46,42 +48,26 @@ function withExts(file: string, extensions: string[]): string[] { } export interface AppConfig extends TaroAppConfig { + entryPagePath?: string; darkMode: boolean; } export interface PageConfigs { [name: string]: Config; } + export function installPlatform(api: IApi) { const appConfigFile = api.helpers.fileSnippets.findFirstExistedFile([ ...withExts(api.resolveUserFile('app.config'), moduleFileExtensions) ]); const appConfig: AppConfig = appConfigFile ? readConfig(appConfigFile) : {}; + if (isEmptyObject(appConfig)) { throw new Error('缺少 app 全局配置文件,请检查!'); } - const appPages = appConfig.pages; - if (!appPages || !appPages.length) { - throw new Error('全局配置缺少 pages 字段,请检查!'); - } + + let appPages: string[] = []; const pageConfigs: PageConfigs = {}; - (appConfig.pages || []).forEach(page => { - const pageFile = api.resolveUserFile(`${page}`); - const pageConfigFile = api.helpers.fileSnippets.findFirstExistedFile( - withExts(api.resolveUserFile(`${page}.config`), moduleFileExtensions) - ); - const pageConfig = pageConfigFile ? readConfig(pageConfigFile) : {}; - pageConfigs[page] = pageConfig; - api.addAppFile({ - name: `${page}.js`, - content: () => ` - import { createPageConfig } from '@tarojs/runtime'; - import pageComponent from '${pageFile}' - const pageConfig = ${JSON.stringify(pageConfig)} - const inst = Page(createPageConfig(pageComponent, '${page}', {root:{cn:[]}}, pageConfig || {})) - ` - }); - }); const { themeLocation, darkmode: darkMode } = appConfig; let themeFilePath: string = ''; @@ -94,24 +80,154 @@ export function installPlatform(api: IApi) { api.addAppPolyfill(resolveDep('react-app-polyfill/ie11')); api.addAppPolyfill(resolveDep('react-app-polyfill/stable')); api.addAppExport(resolveAppFile('App'), '{ default as App }'); - api.addAppExport(resolveAppFile('head/head'), '{default as Head}'); - api.addAppExport(resolveAppFile('dynamic'), '{default as dynamic}'); + // api.addAppExport(resolveAppFile('head/head'), '{default as Head}'); + // api.addAppExport(resolveAppFile('dynamic'), '{default as dynamic}'); api.addAppExport( resolveLib('@shuvi/router-react'), - '{ useParams, useRouter, useCurrentRoute, Link, RouterView, withRouter }' + '{ useParams, useRouter, useCurrentRoute, RouterView, withRouter }' ); api.addEntryCode(`require('${PACKAGE_NAME}/lib/runtime')`); + api.addAppService(resolveRouterFile('lib', 'index'), '*', 'router-mp.js'); + let pageFiles: any[]; + let mpPathToRoutesDone: any; + const PromiseRoutes: Promise = new Promise(resolve => { + mpPathToRoutesDone = resolve; + }); + // this hooks works before webpack bundler + // orders can make sure appConfig.page and pageConfigs has correctly value + api.tap('app:routes', { + name: 'mpPathToRoutes', + fn: async routes => { + type IUserRouteHandlerWithoutChildren = Omit< + Runtime.IUserRouteConfig, + 'children' + >; + // map url to component + let routesMap: [string, string][] = []; + const routesName = new Set(); + // flatten routes remove children + function flattenRoutes( + apiRoutes: Runtime.IUserRouteConfig[], + branches: IUserRouteHandlerWithoutChildren[] = [], + parentPath = '' + ): IUserRouteHandlerWithoutChildren[] { + apiRoutes.forEach(route => { + const { children, component } = route; + let tempPath = path.join(parentPath, route.path); + + if (children) { + flattenRoutes(children, branches, tempPath); + } + if (component) { + branches.push({ + path: tempPath, + component + }); + } + }); + return branches; + } + + function removeConfigPathAddMpPath( + routes: IUserRouteHandlerWithoutChildren[] + ) { + for (let i = routes.length - 1; i >= 0; i--) { + const route = routes[i]; + const { component } = route; + if (component) { + // remove config path, eg: miniprogram/src/pages/index/index.config.js + if (/.*\.config\.\w+$/.test(component)) { + routes.splice(i, 1); + } else { + let tempMpPath = component; + if (tempMpPath.startsWith(api.paths.pagesDir)) { + // ensure path relate to pagesDir + tempMpPath = path.relative(api.paths.pagesDir, tempMpPath); + // Remove the file extension from the end + tempMpPath = tempMpPath.replace(/\.\w+$/, ''); + } + // ensure path starts with pages + if (!tempMpPath.startsWith('pages')) { + tempMpPath = path.join('pages', tempMpPath); + } + if (route.path !== tempMpPath) { + // generate routesMap + routesMap.push([route.path, tempMpPath]); + route.path = tempMpPath; + } + routesName.add(route.path); + } + } + } + } + routes = flattenRoutes(routes); + removeConfigPathAddMpPath(routes); + let rankRoutes = routesMap.map(r => [r[0], r] as [string, typeof r]); + rankRoutes = rankRouteBranches(rankRoutes); + routesMap = rankRoutes.map(apiRoute => apiRoute[1]); + await api.addAppFile({ + name: 'routesMap.js', + content: () => `export default ${JSON.stringify(routesMap)}` + }); + + // make sure entryPagePath first postion on appPages + const entryPagePath = appConfig.entryPagePath; + if (entryPagePath && routesName.has(entryPagePath)) { + routesName.delete(entryPagePath); + routesName.add(entryPagePath); + } + appPages = [...routesName].reverse(); + appConfig.pages = appPages; + if (!appPages || !appPages.length) { + throw new Error('shuvi config routes property pages config error'); + } + for (const page of appPages) { + const pageFile = api.resolveUserFile(`${page}`); + const pageConfigFile = api.helpers.fileSnippets.findFirstExistedFile( + withExts(api.resolveUserFile(`${page}.config`), moduleFileExtensions) + ); + const pageConfig = pageConfigFile ? readConfig(pageConfigFile) : {}; + pageConfigs[page] = pageConfig; + await api.addAppFile({ + name: `${page}.js`, + content: () => ` + import * as React from 'react'; + import { createPageConfig } from '@tarojs/runtime'; + import { addGlobalRoutes, getGlobalRoutes, MpRouter } from '@shuvi/services/router-mp'; + import pageComponent from '${pageFile}'; + const pageConfig = ${JSON.stringify(pageConfig)}; + const pageName = '${page}'; + addGlobalRoutes(pageName, pageComponent); + function MpRouterWrapper(){ + return ( + + + ) + }; + const component = MpRouterWrapper; + const inst = Page(createPageConfig(component, pageName, {root:{cn:[]}}, pageConfig || {})) + ` + }); + } + mpPathToRoutesDone(); + return []; // routes file no use, remove it + } + }); + api.tap('bundler:configTarget', { name: 'platform-mp', - fn: (config, { name }) => { + fn: async (config, { name }) => { + await PromiseRoutes; if (name === BUNDLER_TARGET_SERVER) { config.set('entry', '@shuvi/util/lib/noop'); return config; } - if (!pageFiles) { pageFiles = getAllFiles(api.resolveAppFile('files', 'pages')); } diff --git a/packages/platform-mp/src/lib/constants.ts b/packages/platform-mp/src/lib/constants.ts index 7728c5780..f97d84bb3 100644 --- a/packages/platform-mp/src/lib/constants.ts +++ b/packages/platform-mp/src/lib/constants.ts @@ -1 +1,2 @@ export const PACKAGE_NAME = '@shuvi/platform-mp'; +export const PACKAGE_ROUTER = '@shuvi/router-mp'; diff --git a/packages/platform-mp/src/lib/paths.ts b/packages/platform-mp/src/lib/paths.ts index da9b64564..212d5e9b7 100644 --- a/packages/platform-mp/src/lib/paths.ts +++ b/packages/platform-mp/src/lib/paths.ts @@ -1,5 +1,5 @@ import { resolve, dirname, join } from 'path'; -import { PACKAGE_NAME } from './constants'; +import { PACKAGE_NAME, PACKAGE_ROUTER } from './constants'; export const resolveDep = (module: string) => require.resolve(module); @@ -10,3 +10,6 @@ export const PACKAGE_RESOLVED = resolveLib(PACKAGE_NAME); export const resolveAppFile = (...paths: string[]) => `${resolve(PACKAGE_RESOLVED, 'shuvi-app', ...paths)}`; + +export const resolveRouterFile = (...paths: string[]) => + `${resolve(resolveLib(PACKAGE_ROUTER), ...paths)}`; diff --git a/packages/router-mp/package.json b/packages/router-mp/package.json new file mode 100644 index 000000000..8ed6ff9b6 --- /dev/null +++ b/packages/router-mp/package.json @@ -0,0 +1,37 @@ +{ + "name": "@shuvi/router-mp", + "version": "0.0.1-rc.32", + "repository": { + "type": "git", + "url": "git+https://github.com/shuvijs/shuvi.git", + "directory": "packages/router-mp" + }, + "author": "liximomo", + "license": "MIT", + "main": "lib/index.js", + "module": "esm/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib" + ], + "scripts": { + "dev": "run-p watch:*", + "watch:esm": "tsc -p tsconfig.build.esm.json -w", + "watch:cjs": "tsc -p tsconfig.build.cjs.json -w", + "prebuild": "rimraf lib esm", + "build": "run-p build:*", + "build:esm": "tsc -p tsconfig.build.esm.json", + "build:cjs": "tsc -p tsconfig.build.cjs.json" + }, + "engines": { + "node": ">= 12.0.0" + }, + "dependencies": { + "@shuvi/router-react": "^0.0.1-rc.32", + "@shuvi/router": "^0.0.1-rc.32", + "@shuvi/types": "^0.0.1-rc.32" + }, + "devDependencies": { + "@types/react": "^16.9.43" + } +} diff --git a/packages/router-mp/src/Link.tsx b/packages/router-mp/src/Link.tsx new file mode 100644 index 000000000..4e3a8d364 --- /dev/null +++ b/packages/router-mp/src/Link.tsx @@ -0,0 +1,77 @@ +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { + useResolvedPath, + useCurrentRoute, + useNavigate +} from '@shuvi/router-react'; +import { pathToString, State, PathRecord } from '@shuvi/router'; +import { View } from '@tarojs/components'; +import { __DEV__ } from './constants'; + +function isModifiedEvent(event: React.MouseEvent) { + return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); +} + +/** + * The public API for rendering a history-aware . + */ +export const Link = React.forwardRef( + function LinkWithRef( + { onClick, replace: replaceProp = false, state, target, to, ...rest }, + ref + ) { + let navigate = useNavigate(); + const location = useCurrentRoute(); + let path = useResolvedPath(to); + + function handleClick(event: React.MouseEvent) { + if (onClick) onClick(event); + if ( + !event.defaultPrevented && // onClick prevented default + !isModifiedEvent(event) // Ignore clicks with modifier keys + ) { + event.preventDefault(); + + // If the URL hasn't changed, a regular will do a replace instead of + // a push, so do the same here. + let replace = + !!replaceProp || + pathToString(location) === pathToString(path) || + !target || + target === '_self'; + + navigate(to, { replace, state }); + } + } + return ( + // @ts-ignore + + ); + } +); + +export interface LinkProps + extends Omit, 'href'> { + replace?: boolean; + state?: State; + to: PathRecord; +} + +if (__DEV__) { + Link.displayName = 'MpLink'; + Link.propTypes = { + onClick: PropTypes.func, + replace: PropTypes.bool, + state: PropTypes.object, + target: PropTypes.string, + to: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.shape({ + pathname: PropTypes.string, + search: PropTypes.string, + hash: PropTypes.string + }) + ]).isRequired + }; +} diff --git a/packages/router-mp/src/constants.ts b/packages/router-mp/src/constants.ts new file mode 100644 index 000000000..ed6031d95 --- /dev/null +++ b/packages/router-mp/src/constants.ts @@ -0,0 +1 @@ +export const __DEV__ = process.env.NODE_ENV !== 'production'; diff --git a/packages/router-mp/src/globalRoutes.ts b/packages/router-mp/src/globalRoutes.ts new file mode 100644 index 000000000..de4dc4af7 --- /dev/null +++ b/packages/router-mp/src/globalRoutes.ts @@ -0,0 +1,37 @@ +import { IRouteRecord } from '@shuvi/router'; + +const _globalRoutes: IRouteRecord[] = []; + +export function getGlobalRoutes() { + return _globalRoutes; +} + +export function addGlobalRoutes( + routePath: string, + routeComponent: React.ReactElement +) { + for (let i = 0; i < _globalRoutes.length; i++) { + const { path, component } = _globalRoutes[i]; + if (path === routePath && component === routeComponent) { + return _globalRoutes; + } + } + _globalRoutes.push({ + path: routePath, + component: routeComponent + }); + return _globalRoutes; +} + +export function delGlobalRoutes( + routePath: string, + routeComponent: React.ReactElement +) { + for (let i = _globalRoutes.length - 1; i <= 0; i--) { + const { path, component } = _globalRoutes[i]; + if (path === routePath && component === routeComponent) { + _globalRoutes.splice(i, 1); + } + } + return _globalRoutes; +} diff --git a/packages/router-mp/src/index.ts b/packages/router-mp/src/index.ts new file mode 100644 index 000000000..ceb5efe79 --- /dev/null +++ b/packages/router-mp/src/index.ts @@ -0,0 +1,4 @@ +export { Link } from './Link'; +export { MpRouter } from './mpRouter'; +export * from './navigateApis'; +export * from './globalRoutes'; diff --git a/packages/router-mp/src/mpHistory.ts b/packages/router-mp/src/mpHistory.ts new file mode 100644 index 000000000..496b975be --- /dev/null +++ b/packages/router-mp/src/mpHistory.ts @@ -0,0 +1,129 @@ +import { + PathRecord, + Location, + Blocker, + MemoryHistoryOptions, + ResolvedPath, + createLocation, + resolvePath, + pathToString +} from '@shuvi/router'; +import invariant from '@shuvi/utils/lib/invariant'; +import { navigateTo, redirectTo, navigateBack } from './navigateApis'; +import BaseHisotry, { + PushOptions, + ACTION_PUSH, + TransitionOptions +} from '@shuvi/router/lib/history/base'; + +export class MpHistory extends BaseHisotry { + private _entries: Location[] = []; + + constructor({ + initialEntries = ['/'], + initialIndex = 0 + }: MemoryHistoryOptions = {}) { + super(); + this._entries = initialEntries.map(entry => { + let location = createLocation({ + pathname: '/', + search: '', + ...(typeof entry === 'string' ? resolvePath(entry) : entry), + hash: '' // no support hash + }); + + invariant( + location.pathname.charAt(0) === '/', + `Relative pathnames are not supported in createMpHistory({ initialEntries }) (invalid entry: ${JSON.stringify( + entry + )})` + ); + + return location; + }); + this._index = initialIndex; + this.location = this._entries[this._index]; + } + + setup() { + // do nothing + } + + push(to: PathRecord, { state, redirectedFrom }: PushOptions = {}) { + // open in new window + const { href } = this.resolve(to, this.location.pathname); + return navigateTo({ url: href }); + } + + replace(to: PathRecord, { state, redirectedFrom }: PushOptions = {}) { + // replace in current window + const { href } = this.resolve(to, this.location.pathname); + return redirectTo({ url: href }); + } + + go(delta: number): void { + invariant(delta < 0, 'delta should be negative integer'); + navigateBack({ + delta + }); + // check transition + if (this._blockers.length) { + this._blockers.call({ + retry: () => { + this.go(delta); + } + }); + return; + } + } + + transitionTo( + to: PathRecord, + { + onTransition, + onAbort, + action = ACTION_PUSH, + state = null, + redirectedFrom + }: TransitionOptions + ) { + const { path } = this.resolve(to, this.location.pathname); + const nextLocation = createLocation(path, { state, redirectedFrom }); + this.doTransision( + to, + () => { + onTransition({ + location: nextLocation, + state: { + usr: nextLocation.state, + key: nextLocation.key, + idx: this._index + 1 + }, + url: this.resolve(nextLocation).href + }); + }, + onAbort + ); + } + + block(blocker: Blocker): () => void { + return this._blockers.push(blocker); + } + + resolve(to: PathRecord, from?: string): ResolvedPath { + const toPath = resolvePath(to, from); + return { + path: toPath, + href: pathToString(toPath) + }; + } + + protected getIndexAndLocation(): [number, Location] { + const index = this._index; + return [index, this._entries[index]]; + } +} + +export function createMpHistory(options: MemoryHistoryOptions = {}): MpHistory { + return new MpHistory(options); +} diff --git a/packages/router-mp/src/mpRouter.tsx b/packages/router-mp/src/mpRouter.tsx new file mode 100644 index 000000000..48513bfe8 --- /dev/null +++ b/packages/router-mp/src/mpRouter.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import qs from 'query-string'; +import PropTypes from 'prop-types'; +import { Current as TaroCurrent } from '@tarojs/runtime'; +import { InitialEntry, createRouter, IRouter } from '@shuvi/router'; +import { createMpHistory } from './mpHistory'; +import { Router, RouterView } from '@shuvi/router-react'; +import { IRouteRecord } from '@shuvi/router-react'; +import { __DEV__ } from './constants'; + +export interface IMpRouterProps { + basename?: string; + children?: React.ReactNode; + routes?: IRouteRecord[]; + initialEntries?: InitialEntry[]; +} + +/** + * A that just stores one entries in memory. + */ +export function MpRouter({ + basename = '/', + routes, + initialEntries +}: IMpRouterProps): React.ReactElement { + let routerRef = React.useRef(); + + if (routerRef.current == null) { + routerRef.current = createRouter({ + basename, + routes: routes || [], + history: createMpHistory({ initialEntries, initialIndex: 0 }) + }); + + // mp params storage at query property __params + + // @ts-ignore + const current = routerRef.current._current; + // @ts-ignore + if (current && TaroCurrent.page.$taroParams) { + // @ts-ignore + const query = TaroCurrent.page.$taroParams; + // remove taro $taroTimestamp + if (query.$taroTimestamp) { + delete query.$taroTimestamp; + } + if (query.__params) { + // move __params to params, rewrite search property + current.params = { + ...current.params, + ...JSON.parse(query.__params as string) + }; + delete query.__params; + if (query.__pathname) { + current.pathname = query.__pathname as string; + delete query.__pathname; + } + current.search = Object.keys(query).length + ? `?${qs.stringify(query)}` + : ''; + } + current.query = query; + } + } + + return ( + + + + ); +} + +if (__DEV__) { + MpRouter.displayName = 'MpRouter'; + MpRouter.propTypes = { + children: PropTypes.node, + routes: PropTypes.arrayOf(PropTypes.object), + initialEntries: PropTypes.arrayOf( + PropTypes.oneOfType([ + PropTypes.string, + PropTypes.shape({ + pathname: PropTypes.string, + search: PropTypes.string, + hash: PropTypes.string, + state: PropTypes.object, + key: PropTypes.string + }) + ]) + ), + initialIndex: PropTypes.number + }; +} diff --git a/packages/router-mp/src/navigateApis.ts b/packages/router-mp/src/navigateApis.ts new file mode 100644 index 000000000..28b5138f1 --- /dev/null +++ b/packages/router-mp/src/navigateApis.ts @@ -0,0 +1,65 @@ +import { matchPathname, pathToString, resolvePath } from '@shuvi/router'; +import { + navigateTo as nativeNavigateTo, + redirectTo as nativeRedirectTo, + navigateBack +} from '@tarojs/taro'; +import invariant from '@shuvi/utils/lib/invariant'; +// @ts-ignore +import routesMap from '@shuvi/app/files/routesMap'; + +type IOptions = { + url: string; + delta?: number; + [any: string]: any; +}; + +function mapUrlToMpPath(options: IOptions) { + if (typeof options === 'object') { + invariant(!options.hash, 'not supported url hash'); + options.hash = ''; + } + if (typeof options.url === 'string') { + const { pathname, query, hash } = resolvePath(options.url); + invariant(!hash, 'not supported url hash'); + if (hash) { + options.url = pathToString({ pathname, query }); + } + for (let i = 0; i < routesMap.length; i++) { + const routeMap = routesMap[i]; + const match = matchPathname(routeMap[0], pathname); + if (match) { + const { params } = match; + options.url = pathToString({ + pathname: routeMap[1], + query: { + ...query, + ...(Object.keys(params).length + ? { + __params: JSON.stringify(match.params), + __pathname: pathname + } + : {}) + } + }); + return; + } + } + } +} + +interface INavigate { + (options: IOptions): Promise | void; +} + +export const navigateTo: INavigate = function (options) { + mapUrlToMpPath(options); + return nativeNavigateTo(options); +}; + +export const redirectTo: INavigate = function (options) { + mapUrlToMpPath(options); + return nativeRedirectTo(options); +}; + +export { navigateBack }; diff --git a/packages/router-mp/tsconfig.build.cjs.json b/packages/router-mp/tsconfig.build.cjs.json new file mode 100644 index 000000000..f63c83efa --- /dev/null +++ b/packages/router-mp/tsconfig.build.cjs.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "lib", + "module": "commonjs", + "lib": ["esnext", "DOM"] + }, + "include": [ + "src" + ] +} diff --git a/packages/router-mp/tsconfig.build.esm.json b/packages/router-mp/tsconfig.build.esm.json new file mode 100644 index 000000000..2d64834e1 --- /dev/null +++ b/packages/router-mp/tsconfig.build.esm.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.build.json", + "compilerOptions": { + "rootDir": "./src", + "module": "esnext", + "outDir": "esm", + "lib": ["esnext", "DOM"] + }, + "include": [ + "src" + ] +} diff --git a/packages/router-mp/tsconfig.json b/packages/router-mp/tsconfig.json new file mode 100644 index 000000000..5331d728c --- /dev/null +++ b/packages/router-mp/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "lib": ["esnext", "DOM"], + }, + "include": [ + "src" + ] +} diff --git a/packages/router/src/history/base.ts b/packages/router/src/history/base.ts index 71fdb9549..9e177dca4 100644 --- a/packages/router/src/history/base.ts +++ b/packages/router/src/history/base.ts @@ -36,7 +36,7 @@ export const ACTION_PUSH: Action = 'PUSH'; */ export const ACTION_REPLACE: Action = 'REPLACE'; -interface TransitionOptions { +export interface TransitionOptions { state?: State; action?: Action; redirectedFrom?: PathRecord; diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index b41dc9af5..43dc0e4ff 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -6,7 +6,7 @@ export { rankRouteBranches } from './matchRoutes'; -export { pathToString, parseQuery } from './utils'; +export { pathToString, parseQuery, resolvePath, createLocation } from './utils'; export * from './types'; export * from './history'; export * from './router'; diff --git a/packages/router/src/utils/path.ts b/packages/router/src/utils/path.ts index 84314d0e5..eff4280da 100644 --- a/packages/router/src/utils/path.ts +++ b/packages/router/src/utils/path.ts @@ -40,7 +40,10 @@ export function pathToString({ hash = '', query = {} }: PartialPath): string { - search = search || qs.stringify(query); + if (!search) { + const queryString = qs.stringify(query); + search = queryString ? `?${queryString}` : ''; + } return pathname + search + hash; } diff --git a/test/fixtures/basic-miniprogram-ts/shuvi.config.js b/test/fixtures/basic-miniprogram-ts/shuvi.config.js index 76cc3ada3..8348b0ce3 100644 --- a/test/fixtures/basic-miniprogram-ts/shuvi.config.js +++ b/test/fixtures/basic-miniprogram-ts/shuvi.config.js @@ -3,6 +3,5 @@ module.exports = { platform: 'mp', router: { history: 'memory' - }, - routes: [] + } }; diff --git a/test/fixtures/basic-miniprogram-ts/src/app.config.ts b/test/fixtures/basic-miniprogram-ts/src/app.config.ts index ebb3a7fe4..f750502ac 100644 --- a/test/fixtures/basic-miniprogram-ts/src/app.config.ts +++ b/test/fixtures/basic-miniprogram-ts/src/app.config.ts @@ -1,11 +1,5 @@ export default { - pages: [ - 'pages/index/index', - 'pages/sub/index', - 'pages/detail/index', - 'pages/list/index', - 'pages/my/index' - ], + entryPagePath: 'pages/index/index', window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', diff --git a/test/fixtures/basic-miniprogram-ts/src/pages/index/index.jsx b/test/fixtures/basic-miniprogram-ts/src/pages/index/index.jsx index 7d8b0c091..68b949fa6 100644 --- a/test/fixtures/basic-miniprogram-ts/src/pages/index/index.jsx +++ b/test/fixtures/basic-miniprogram-ts/src/pages/index/index.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react' import { View } from '@binance/mp-components' -import { navigateTo } from '@binance/mp-service' +import { navigateTo } from '@binance/mp-service'; +import { Link } from '@shuvi/services/router-mp'; // import consoleLogMain from '../../utils/consoleLogMain' import './index.scss' import style from './test.scss' @@ -8,7 +9,6 @@ export default () => { const [o, setO] = useState({ haha: 1234 }) useEffect(() => { console.warn('sdfdsfsdfdf') - debugger; }) return ( @@ -24,6 +24,12 @@ export default () => { navigateTo({ url: '/pages/list/index' })}> Go to list + navigateTo({ url: '/pages/list/index' })}> + Go to list + + + link test /my/a/b/c?query=query + ) } diff --git a/test/fixtures/basic-miniprogram-ts/src/pages/my/[[...other]].config.js b/test/fixtures/basic-miniprogram-ts/src/pages/my/[[...other]].config.js new file mode 100644 index 000000000..fa849306a --- /dev/null +++ b/test/fixtures/basic-miniprogram-ts/src/pages/my/[[...other]].config.js @@ -0,0 +1,3 @@ +export default { + navigationBarTitleText: '匹配全部测试' +}; diff --git a/test/fixtures/basic-miniprogram-ts/src/pages/my/[[...other]].jsx b/test/fixtures/basic-miniprogram-ts/src/pages/my/[[...other]].jsx new file mode 100644 index 000000000..c24f4b897 --- /dev/null +++ b/test/fixtures/basic-miniprogram-ts/src/pages/my/[[...other]].jsx @@ -0,0 +1,17 @@ +import React, { useEffect } from 'react' +import { View, Text } from '@tarojs/components' +import { useCurrentRoute } from '@shuvi/app'; + +export default () => { + const { query, params } = useCurrentRoute(); + useEffect (()=>{ + console.log('other Component') + }) + return ( + + 匹配全部测试 other Component + query:{JSON.stringify(query)} + params:{JSON.stringify(params)} + + ) +} diff --git a/test/fixtures/basic-miniprogram/shuvi.config.js b/test/fixtures/basic-miniprogram/shuvi.config.js index 76cc3ada3..2330d7cc1 100644 --- a/test/fixtures/basic-miniprogram/shuvi.config.js +++ b/test/fixtures/basic-miniprogram/shuvi.config.js @@ -4,5 +4,31 @@ module.exports = { router: { history: 'memory' }, - routes: [] + runtimeConfig: { + a: 'a', + b: 'b', + hello: 'hello' + }, + routes: [ + { + path: '/', + component: 'pages/index/index' + }, + { + path: '/pages/sub/:title', + component: 'pages/sub/index' + }, + { + path: '/pages/detail/:id', + component: 'pages/detail/index' + }, + { + path: '/:first/:detail/:id', + component: 'pages/detail/index' + }, + { + path: '/:other(.*)', + component: 'pages/my/index' + } + ] }; diff --git a/test/fixtures/basic-miniprogram/src/app.config.js b/test/fixtures/basic-miniprogram/src/app.config.js index ebb3a7fe4..b55f65407 100644 --- a/test/fixtures/basic-miniprogram/src/app.config.js +++ b/test/fixtures/basic-miniprogram/src/app.config.js @@ -1,11 +1,4 @@ export default { - pages: [ - 'pages/index/index', - 'pages/sub/index', - 'pages/detail/index', - 'pages/list/index', - 'pages/my/index' - ], window: { backgroundTextStyle: 'light', navigationBarBackgroundColor: '#fff', diff --git a/test/fixtures/basic-miniprogram/src/pages/detail/index.jsx b/test/fixtures/basic-miniprogram/src/pages/detail/index.jsx index a2d4e5097..246ce3271 100644 --- a/test/fixtures/basic-miniprogram/src/pages/detail/index.jsx +++ b/test/fixtures/basic-miniprogram/src/pages/detail/index.jsx @@ -1,5 +1,7 @@ -import React, { Component } from 'react' +import React, { useEffect } from 'react' +import Taro from '@tarojs/taro' import { View, Text } from '@tarojs/components' +import { useCurrentRoute } from '@shuvi/app'; import consoleLogMain from '../../utils/consoleLogMain' import consoleLogSubVendors from '../../utils/consoleLogSubVendors' import testExcludeString from '../../utils/testExcludeString' @@ -8,25 +10,36 @@ import '../../css/sub-vendors.css' import '../../css/sub-common.css' import subCommonStyles from '../../css/sub-common.module.css' import vendorsStyles from '../../css/sub-vendors.module.css' +import { navigateBack } from '@shuvi/services/router-mp'; import _ from 'lodash' -export default class Detail extends Component { - componentDidMount() { +export default () => { + const { query, params } = useCurrentRoute(); + useEffect(()=>{ consoleLogMain() consoleLogSubVendors() testExcludeString() - } - - render() { - return ( - - + I m detail + `}>I m detail + + navigateBack(-1)}> + test go back -1 + + Taro.navigateTo({ url: '/pages/index/index' })}> + Go to index + + Taro.navigateTo({ url: '/pages/my/index' })}> + Go to my - ) - } + query:{JSON.stringify(query)} + params:{JSON.stringify(params)} + + ) } diff --git a/test/fixtures/basic-miniprogram/src/pages/index/index.jsx b/test/fixtures/basic-miniprogram/src/pages/index/index.jsx index 7d8b0c091..756b6065a 100644 --- a/test/fixtures/basic-miniprogram/src/pages/index/index.jsx +++ b/test/fixtures/basic-miniprogram/src/pages/index/index.jsx @@ -1,29 +1,48 @@ import React, { useState, useEffect } from 'react' import { View } from '@binance/mp-components' -import { navigateTo } from '@binance/mp-service' +import Taro, { navigateTo } from '@binance/mp-service' // import consoleLogMain from '../../utils/consoleLogMain' +import { getRuntimeConfig } from '@shuvi/app'; +import { useCurrentRoute } from '@shuvi/app'; +import { Link } from '@shuvi/services/router-mp'; + +globalThis.Taro = Taro; + +const runtimeConfig = getRuntimeConfig(); import './index.scss' import style from './test.scss' + export default () => { - const [o, setO] = useState({ haha: 1234 }) + const { query, params } = useCurrentRoute(); + const [o, setO] = useState({ haha: 123 }) useEffect(() => { console.warn('sdfdsfsdfdf') - debugger; }) return ( navigateTo({ url: '/pages/sub/index' })}> Go to {o.haha} - navigateTo({ url: '/pages/detail/index' })}> + navigateTo({ url: '/pages/detail/index?a=b&__params=%7B%22name%22%3A%22success%22%7D'})}> Go to detail + + link test /a/b/c?query=query + + + link test /pages/detail/1?query=other + navigateTo({ url: '/pages/my/index' })}> Go to my navigateTo({ url: '/pages/list/index' })}> Go to list + + runtimeConfig:{JSON.stringify(runtimeConfig)} + + query:{JSON.stringify(query)} + params:{JSON.stringify(params)} ) } diff --git a/test/fixtures/basic-miniprogram/src/pages/sub/index.jsx b/test/fixtures/basic-miniprogram/src/pages/sub/index.jsx index df3801419..233188f5b 100644 --- a/test/fixtures/basic-miniprogram/src/pages/sub/index.jsx +++ b/test/fixtures/basic-miniprogram/src/pages/sub/index.jsx @@ -11,8 +11,8 @@ export default class Index extends Component { render () { return ( - Taro.navigateTo({ url: '/pages/detail/index' })}> - Go to detail + Taro.navigateTo({ url: '/pages/detail/index?query=detail' })}> + Go to detail ?query=detail Taro.navigateTo({ url: '/pages/my/index' })}> Go to my