提交 46efbd97 编写于 作者: fxy060608's avatar fxy060608

feat: add ssrRef,shallowSsrRef

上级 b7f14f1e
......@@ -44,7 +44,3 @@ declare var __uniRoutes: UniApp.UniRoutes
declare var __uniConfig: UniApp.UniConfig
declare var UniViewJSBridge: UniApp.UniViewJSBridge
declare var UniServiceJSBridge: UniApp.UniServiceJSBridge
declare const getCurrentPages: <T extends AnyObject = {}>(
isAll?: boolean
) => Array<Page.PageInstance<AnyObject, T> & T>
......@@ -184,7 +184,7 @@ function createNormalizeUrl(type: string) {
}
}
if (routeOptions.meta.isTabBar) {
const pages = getCurrentPages(true)
const pages = getCurrentPages()
const tabBarPagePath = routeOptions.path.substr(1)
if (pages.find((page) => page.route === tabBarPagePath)) {
return 'tabBar page `' + tabBarPagePath + '` already exists'
......
......@@ -3,6 +3,72 @@
Object.defineProperty(exports, '__esModule', { value: true });
var vue = require('vue');
var shared = require('@vue/shared');
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const UNI_SSR = '__uniSSR';
const UNI_SSR_DATA = 'data';
const UNI_SSR_GLOBAL_DATA = 'globalData';
function getSSRDataType() {
return vue.getCurrentInstance() ? UNI_SSR_DATA : UNI_SSR_GLOBAL_DATA;
}
function assertKey(key, shallow = false) {
if (!key) {
throw new Error(`${shallow ? 'shallowSsrRef' : 'ssrRef'}: You must provide a key.`);
}
}
function proxy(target, track, trigger) {
return new Proxy(target, {
get(target, prop) {
track();
if (shared.isObject(target[prop])) {
return proxy(target[prop], track, trigger);
}
return Reflect.get(target, prop);
},
set(obj, prop, newVal) {
const result = Reflect.set(obj, prop, newVal);
trigger();
return result;
},
});
}
const ssrServerRef = (value, key, shallow = false) => {
const type = getSSRDataType();
assertKey(key, shallow);
const ctx = vue.useSSRContext();
const __uniSSR = ctx[UNI_SSR] || (ctx[UNI_SSR] = {});
const state = __uniSSR[type] || (__uniSSR[type] = {});
// SSR 模式下 watchEffect 不生效 https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/apiWatch.ts#L253
// 故自定义ref
return vue.customRef((track, trigger) => {
const customTrigger = () => (trigger(), (state[key] = sanitise(value)));
return {
get: () => {
track();
if (!shallow && shared.isObject(value)) {
return proxy(value, track, customTrigger);
}
return value;
},
set: (v) => {
value = v;
customTrigger();
},
};
});
};
const ssrRef = (value, key) => {
{
return ssrServerRef(value, key);
}
};
const shallowSsrRef = (value, key) => {
{
return ssrServerRef(value, key, true);
}
};
// @ts-ignore
// App and Page
......@@ -82,3 +148,5 @@ exports.onTabItemTap = onTabItemTap;
exports.onThemeChange = onThemeChange;
exports.onUnhandledRejection = onUnhandledRejection;
exports.onUnload = onUnload;
exports.shallowSsrRef = shallowSsrRef;
exports.ssrRef = ssrRef;
import { ComponentInternalInstance } from 'vue';
import { ref } from 'vue';
import { shallowRef } from 'vue';
export declare const onAddToFavorites: (hook: () => any, target?: ComponentInternalInstance | null) => any;
......@@ -46,4 +48,10 @@ export declare const onUnhandledRejection: (hook: () => any, target?: ComponentI
export declare const onUnload: (hook: () => any, target?: ComponentInternalInstance | null) => any;
export declare const shallowSsrRef: SSRRef;
declare type SSRRef = (value: unknown, key?: string, shallow?: boolean) => ReturnType<typeof ref> | ReturnType<typeof shallowRef>;
export declare const ssrRef: SSRRef;
export { }
import { getCurrentInstance, isInSSRComponentSetup, injectHook } from 'vue';
import { shallowRef, ref, getCurrentInstance, isInSSRComponentSetup, injectHook } from 'vue';
const UNI_SSR = '__uniSSR';
const UNI_SSR_DATA = 'data';
const UNI_SSR_GLOBAL_DATA = 'globalData';
function getSSRDataType() {
return getCurrentInstance() ? UNI_SSR_DATA : UNI_SSR_GLOBAL_DATA;
}
function assertKey(key, shallow = false) {
if (!key) {
throw new Error(`${shallow ? 'shallowSsrRef' : 'ssrRef'}: You must provide a key.`);
}
}
const ssrClientRef = (value, key, shallow = false) => {
const valRef = shallow ? shallowRef(value) : ref(value);
const __uniSSR = window[UNI_SSR];
if (!__uniSSR) {
return valRef;
}
const type = getSSRDataType();
assertKey(key, shallow);
valRef.value = __uniSSR[type][key];
return valRef;
};
const ssrRef = (value, key) => {
return ssrClientRef(value, key);
};
const shallowSsrRef = (value, key) => {
return ssrClientRef(value, key, true);
};
// @ts-ignore
// App and Page
......@@ -55,4 +85,4 @@ const onNavigationBarSearchInputClicked = /*#__PURE__*/ createHook(ON_NAVIGATION
const onNavigationBarSearchInputConfirmed = /*#__PURE__*/ createHook(ON_NAVIGATION_BAR_SEARCH_INPUT_CONFIRMED);
const onNavigationBarSearchInputFocusChanged = /*#__PURE__*/ createHook(ON_NAVIGATION_BAR_SEARCH_INPUT_FOCUS_CHANGED);
export { onAddToFavorites, onBackPress, onError, onHide, onLaunch, onNavigationBarButtonTap, onNavigationBarSearchInputChanged, onNavigationBarSearchInputClicked, onNavigationBarSearchInputConfirmed, onNavigationBarSearchInputFocusChanged, onPageNotFound, onPageScroll, onPullDownRefresh, onReachBottom, onReady, onResize, onShareAppMessage, onShareTimeline, onShow, onTabItemTap, onThemeChange, onUnhandledRejection, onUnload };
export { onAddToFavorites, onBackPress, onError, onHide, onLaunch, onNavigationBarButtonTap, onNavigationBarSearchInputChanged, onNavigationBarSearchInputClicked, onNavigationBarSearchInputConfirmed, onNavigationBarSearchInputFocusChanged, onPageNotFound, onPageScroll, onPullDownRefresh, onReachBottom, onReady, onResize, onShareAppMessage, onShareTimeline, onShow, onTabItemTap, onThemeChange, onUnhandledRejection, onUnload, shallowSsrRef, ssrRef };
export * from './ssr'
export * from './apiLifecycle'
import {
ref,
shallowRef,
customRef,
useSSRContext,
getCurrentInstance,
} from 'vue'
import { isObject } from '@vue/shared'
import {
sanitise,
UNI_SSR,
UNI_SSR_DATA,
UNI_SSR_GLOBAL_DATA,
} from '@dcloudio/uni-shared'
type SSRRef = (
value: unknown,
key?: string,
shallow?: boolean
) => ReturnType<typeof ref> | ReturnType<typeof shallowRef>
function getSSRDataType() {
return getCurrentInstance() ? UNI_SSR_DATA : UNI_SSR_GLOBAL_DATA
}
function assertKey(key?: string, shallow = false) {
if (!key) {
throw new Error(
`${shallow ? 'shallowSsrRef' : 'ssrRef'}: You must provide a key.`
)
}
}
const ssrClientRef: SSRRef = (value, key, shallow = false) => {
const valRef = shallow ? shallowRef(value) : ref(value)
if (__PLATFORM__ !== 'h5') {
return valRef
}
const __uniSSR = (window as any)[UNI_SSR]
if (!__uniSSR) {
return valRef
}
const type = getSSRDataType()
assertKey(key, shallow)
valRef.value = (__uniSSR[type] as any)[key!]
return valRef
}
function proxy(
target: Record<string | number, any>,
track: () => void,
trigger: () => void
): Record<string | number, any> {
return new Proxy(target, {
get(target, prop: string) {
track()
if (isObject(target[prop])) {
return proxy(target[prop], track, trigger)
}
return Reflect.get(target, prop)
},
set(obj, prop, newVal) {
const result = Reflect.set(obj, prop, newVal)
trigger()
return result
},
})
}
const ssrServerRef: SSRRef = (value, key, shallow = false) => {
const type = getSSRDataType()
assertKey(key, shallow)
const ctx = useSSRContext()!
const __uniSSR = ctx[UNI_SSR] || (ctx[UNI_SSR] = {})
const state = __uniSSR[type] || (__uniSSR[type] = {})
// SSR 模式下 watchEffect 不生效 https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/apiWatch.ts#L253
// 故自定义ref
return customRef((track, trigger) => {
const customTrigger = () => (trigger(), (state[key!] = sanitise(value)))
return {
get: () => {
track()
if (!shallow && isObject(value)) {
return proxy(value, track, customTrigger)
}
return value
},
set: (v) => {
value = v
customTrigger()
},
}
})
}
export const ssrRef: SSRRef = (value, key) => {
if (__NODE_JS__) {
return ssrServerRef(value, key)
}
return ssrClientRef(value, key)
}
export const shallowSsrRef: SSRRef = (value, key) => {
if (__NODE_JS__) {
return ssrServerRef(value, key, true)
}
return ssrClientRef(value, key, true)
}
......@@ -14,5 +14,5 @@
"removeComments": false,
"lib": ["ESNext", "DOM"]
},
"include": ["src"]
"include": ["src", "../global.d.ts", "../shims-uni-app.d.ts"]
}
<template>
<view style="display: none;" />
</template>
<script>
const attrs = [
'titleIcon',
'titleIconRadius',
'subtitleText',
'subtitleSize',
'subtitleColor',
'subtitleOverflow',
'titleAlign',
'backgroundImage',
'backgroundRepeat',
'blurEffect'
]
export default {
props: {
title: {
type: String,
default: ''
},
titleIcon: {
type: String,
default: ''
},
titleIconRadius: {
type: String,
default: ''
},
subtitleText: {
type: String,
default: ''
},
subtitleSize: {
type: String,
default: ''
},
subtitleColor: {
type: String,
default: ''
},
subtitleOverflow: {
type: String,
default: ''
},
titleAlign: {
type: String,
default: ''
},
backgroundImage: {
type: String,
default: ''
},
backgroundRepeat: {
type: String,
default: ''
},
blurEffect: {
type: String,
default: ''
},
loading: {
type: Boolean,
default: false
},
frontColor: {
type: String,
default: '#ffffff'
},
backgroundColor: {
type: String,
default: '#000000'
},
colorAnimationDuration: {
type: Number,
default: 0
},
colorAnimationTimingFunc: {
type: String,
default: 'linear'
}
},
created () {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
this.__$page = page
this.$watch('title', () => {
this.setNavigationBarTitle()
})
this.$watch('loading', () => {
this.setNavigationBarLoading()
})
this.$watch(() => [
this.frontColor,
this.backgroundColor,
this.colorAnimationDuration,
this.colorAnimationTimingFunc
],
() => {
this.setNavigationBarColor()
})
// #ifdef APP-PLUS
this.__$webview = page.$getAppWebview()
attrs.forEach(key => {
const titleNView = {}
if (this[key] || this[key].length > 0) {
titleNView[key] = this[key]
}
this.setTitleNView(titleNView)
this.$watch(key, (val) => {
const titleStyle = {}
titleStyle[key] = val
this.setTitleNView(titleStyle)
})
})
// #endif
},
beforeMount () {
this.title && this.setNavigationBarTitle()
this.setNavigationBarLoading()
this.setNavigationBarColor()
},
methods: {
setNavigationBarTitle () {
uni.setNavigationBarTitle({
__page__: this.__$page,
title: this.title
})
},
setNavigationBarLoading () {
uni[(this.loading ? 'show' : 'hide') + 'NavigationBarLoading']({
__page__: this.__$page
})
},
setNavigationBarColor () {
uni.setNavigationBarColor({
__page__: this.__$page,
frontColor: this.frontColor,
backgroundColor: this.backgroundColor,
animation: {
duration: this.colorAnimationDuration,
timingFunc: this.colorAnimationTimingFunc
}
})
},
setTitleNView (titleNView) {
const webview = this.__$webview
const style = webview.getStyle()
if (style && style.titleNView) {
webview.setStyle({
titleNView: titleNView
})
}
}
}
}
</script>
<template>
<view style="display: none;">
<slot />
</view>
</template>
<script>
const scrolldoneEvent = {
type: 'scrolldone',
target: {
id: '',
offsetLeft: 0,
offsetTop: 0,
dataset: {}
},
currentTarget: {
id: '',
offsetLeft: 0,
offsetTop: 0,
dataset: {}
},
detail: {}
}
export default {
props: {
backgroundTextStyle: {
type: String,
default: 'dark',
validator (value) {
return ['dark', 'light'].indexOf(value) !== -1
}
},
backgroundColor: {
type: String,
default: '#ffffff'
},
backgroundColorTop: {
type: String,
default: '#ffffff'
},
backgroundColorBottom: {
type: String,
default: '#ffffff'
},
scrollTop: {
type: String,
default: ''
},
scrollDuration: {
type: Number,
default: 300
},
pageStyle: {
type: String,
default: ''
},
enablePullDownRefresh: {
type: [Boolean, String],
default: false
},
rootFontSize: {
type: String,
default: ''
}
},
created () {
const page = getCurrentPages()[0]
this.$pageVm = page.$vm || page
// event
// h5 暂不支持生命周期 onResize,补充后,可以移除该条件编译
// #ifdef H5
uni.onWindowResize(evt => {
this.$emit('resize', evt)
})
// #endif
// #ifndef H5
this.$pageVm.$on('hook:onResize', evt => {
this.$emit('resize', evt)
})
// #endif
// 父节点一定是 page
this.$pageVm.$on('hook:onPageScroll', evt => {
this.$emit('scroll', evt)
})
// #ifdef APP-PLUS
this._currentWebview = page.$getAppWebview()
if (this.enablePullDownRefresh) {
this.setPullDownRefresh(this._currentWebview, true)
}
this.$watch('enablePullDownRefresh', (val) => {
this.setPullDownRefresh(this._currentWebview, val)
})
// #endif
// props
this.$watch('backgroundTextStyle', () => {
this.setBackgroundTextStyle()
})
this.$watch(() => [
this.rootFontSize,
this.pageStyle
], () => {
this.setPageMeta()
})
this.$watch(() => [
this.backgroundColor,
this.backgroundColorTop,
this.backgroundColorBottom
], () => {
this.setBackgroundColor()
})
this.$watch(() => [
this.scrollTop,
this.scrollDuration
], () => {
this.pageScrollTo()
})
},
beforeMount () {
this.setBackgroundColor()
if (this.rootFontSize || this.pageStyle) {
this.setPageMeta()
}
this.backgroundTextStyle && this.setBackgroundTextStyle()
this.scrollTop && this.pageScrollTo()
},
methods: {
setPullDownRefresh (webview, enabled) {
webview.setStyle({
pullToRefresh: {
support: enabled,
style: plus.os.name === 'Android' ? 'circle' : 'default'
}
})
},
setPageMeta () {
// h5 和 app-plus 设置 rootFontSize
// #ifdef H5 || APP-PLUS
uni.setPageMeta({
pageStyle: this.pageStyle,
rootFontSize: this.rootFontSize
})
// #endif
},
setBackgroundTextStyle () {
// TODO h5 app-plus 暂不支持
// #ifdef MP
uni.setBackgroundTextStyle({
textStyle: this.backgroundTextStyle
})
// #endif
},
setBackgroundColor () {
// TODO h5 app-plus 暂不支持
// #ifdef MP
uni.setBackgroundColor({
backgroundColor: this.backgroundColor,
backgroundColorTop: this.backgroundColorTop,
backgroundColorBottom: this.backgroundColorBottom
})
// #endif
},
pageScrollTo () {
let scrollTop = String(this.scrollTop)
if (scrollTop.indexOf('rpx') !== -1) {
scrollTop = uni.upx2px(scrollTop.replace('rpx', ''))
}
scrollTop = parseFloat(scrollTop)
if (isNaN(scrollTop)) {
return
}
const pageScrollDone = (evt) => {
if (evt.scrollTop === scrollTop) {
this.$pageVm.$off('hook:onPageScroll', pageScrollDone)
this.$emit('scrolldone', scrolldoneEvent)
}
}
uni.pageScrollTo({
scrollTop,
duration: this.scrollDuration,
success: () => {
this.$pageVm.$on('hook:onPageScroll', pageScrollDone)
}
})
}
}
}
</script>
<template>
<view v-show="matches">
<slot />
</view>
</template>
<script>
let mediaQueryObserver
export default {
name: 'UniMatchMedia',
props: {
width: {
type: [Number, String],
default: ''
},
minWidth: {
type: [Number, String],
default: ''
},
maxWidth: {
type: [Number, String],
default: ''
},
height: {
type: [Number, String],
default: ''
},
minHeight: {
type: [Number, String],
default: ''
},
maxHeight: {
type: [Number, String],
default: ''
},
orientation: {
type: String,
default: ''
}
},
data () {
return {
matches: true
}
},
mounted () {
mediaQueryObserver = uni.createMediaQueryObserver(this)
mediaQueryObserver.observe({
width: this.width,
maxWidth: this.maxWidth,
minWidth: this.minWidth,
height: this.height,
minHeight: this.minHeight,
maxHeight: this.maxHeight,
orientation: this.orientation
}, matches => {
this.matches = matches
})
},
destroyed () {
mediaQueryObserver.disconnect()
}
}
</script>
<style>
view {
display: block;
}
</style>
{
"uniCloud.component.add.success": "Success",
"uniCloud.component.update.success": "Success",
"uniCloud.component.remove.showModal.title": "Tips",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}
import en from './en.json'
import es from './es.json'
import fr from './fr.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
es,
fr,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否刪除數據"
}
<template>
<view>
<slot
:options="options"
:data="dataList"
:pagination="paginationInternal"
:loading="loading"
:hasMore="hasMore"
:error="errorMessage"
/>
</view>
</template>
<script>
import { onMounted, getCurrentInstance } from 'vue'
import { ssrRef, shallowSsrRef } from '@dcloudio/uni-app'
import { initVueI18n } from '@dcloudio/uni-i18n'
import messages from './i18n/index'
const { t } = initVueI18n(messages)
const events = {
load: 'load',
error: 'error'
}
const pageMode = {
add: 'add',
replace: 'replace'
}
const loadMode = {
auto: 'auto',
onready: 'onready',
manual: 'manual'
}
const attrs = [
'pageCurrent',
'pageSize',
'collection',
'action',
'field',
'getcount',
'orderby',
'where',
'groupby',
'groupField',
'distinct'
]
export default {
name: 'UniClouddb',
setup(props) {
// 单条记录时,使用shallowRef(仅支持赋值修改),列表时,采用ref(支持push等修改)
const dataListRef = props.getone ? shallowSsrRef(undefined) : ssrRef([])
const instance = getCurrentInstance()
onMounted(() => {
// client端判断是否需要再次请求数据(正常情况下,SSR返回的html中已包含此数据状态,无需再次额外请求)
if ((!dataListRef.value || dataListRef.value.length === 0) && !props.manual && props.loadtime === loadMode.auto) {
instance.proxy.loadData()
}
})
return { dataList: dataListRef }
},
// 服务端serverPrefetch生命周期,用于服务端加载数据,等将来全端支持Suspense时,可以采用 Suspense + async setup 来实现一版
async serverPrefetch() {
if (!this.manual && this.loadtime === loadMode.auto) {
return this.loadData()
}
},
props: {
options: {
type: [Object, Array],
default() {
return {}
}
},
collection: {
type: String,
default: ''
},
action: {
type: String,
default: ''
},
field: {
type: String,
default: ''
},
orderby: {
type: String,
default: ''
},
where: {
type: [String, Object],
default: ''
},
pageData: {
type: String,
default: 'add'
},
pageCurrent: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
getcount: {
type: [Boolean, String],
default: false
},
getone: {
type: [Boolean, String],
default: false
},
gettree: {
type: [Boolean, String, Object],
default: false
},
gettreepath: {
type: [Boolean, String],
default: false
},
startwith: {
type: String,
default: ''
},
limitlevel: {
type: Number,
default: 10
},
groupby: {
type: String,
default: ''
},
groupField: {
type: String,
default: ''
},
distinct: {
type: [Boolean, String],
default: false
},
pageIndistinct: {
type: [Boolean, String],
default: false
},
foreignKey: {
type: String,
default: ''
},
loadtime: {
type: String,
default: 'auto'
},
manual: {
type: Boolean,
default: false
}
},
data() {
return {
loading: false,
hasMore: false,
paginationInternal: {},
errorMessage: ''
}
},
created() {
this._isEnded = false
this.paginationInternal = {
current: this.pageCurrent,
size: this.pageSize,
count: 0
}
this.$watch(() => {
var al = []
attrs.forEach(key => {
al.push(this[key])
})
return al
}, (newValue, oldValue) => {
if (this.loadtime === loadMode.manual) {
return
}
this.paginationInternal.size = this.pageSize
let needReset = false
for (let i = 2; i < newValue.length; i++) {
if (newValue[i] !== oldValue[i]) {
needReset = true
break
}
}
if (needReset) {
this.clear()
this.reset()
}
if (newValue[0] !== oldValue[0]) {
this.paginationInternal.current = this.pageCurrent
}
this._execLoadData()
})
// #ifdef MP-TOUTIAO
let changeName
const events = this.$scope.dataset.eventOpts
for (var i = 0; i < events.length; i++) {
const event = events[i]
if (event[0].includes('^load')) {
changeName = event[1][0][0]
}
}
if (changeName) {
let parent = this.$parent
let maxDepth = 16
this._changeDataFunction = null
while (parent && maxDepth > 0) {
const fun = parent[changeName]
if (fun && typeof fun === 'function') {
this._changeDataFunction = fun
maxDepth = 0
break
}
parent = parent.$parent
maxDepth--
}
}
// #endif
},
mounted() {
// if (!this.manual && this.loadtime === loadMode.auto) {
// this.loadData()
// }
},
methods: {
loadData(args1, args2) {
let callback = null
let clear = false
if (typeof args1 === 'object') {
if (args1.clear) {
if (this.pageData === pageMode.replace) {
this.clear()
} else {
clear = args1.clear
}
this.reset()
}
if (args1.current !== undefined) {
this.paginationInternal.current = args1.current
}
if (typeof args2 === 'function') {
callback = args2
}
} else if (typeof args1 === 'function') {
callback = args1
}
return this._execLoadData(callback, clear)
},
loadMore() {
if (this._isEnded || this.loading) {
return
}
if (this.pageData === pageMode.add) {
this.paginationInternal.current++
}
this._execLoadData()
},
refresh() {
this.clear()
this._execLoadData()
},
clear() {
this._isEnded = false
this.dataList = []
},
reset() {
this.paginationInternal.current = 1
},
add(value, {
action,
showToast = true,
toastTitle,
success,
fail,
complete,
needConfirm = true,
needLoading = true,
loadingTitle = ''
} = {}) {
if (needLoading) {
uni.showLoading({
title: loadingTitle
})
}
/* eslint-disable no-undef */
let db = uniCloud.database()
if (action) {
db = db.action(action)
}
db.collection(this.collection).add(value).then((res) => {
success && success(res)
if (showToast) {
uni.showToast({
title: toastTitle || t('uniCloud.component.add.success')
})
}
}).catch((err) => {
fail && fail(err)
if (needConfirm) {
uni.showModal({
content: err.message,
showCancel: false
})
}
}).finally(() => {
if (needLoading) {
uni.hideLoading()
}
complete && complete()
})
},
remove(id, {
action,
success,
fail,
complete,
confirmTitle,
confirmContent,
needConfirm = true,
needLoading = true,
loadingTitle = ''
} = {}) {
if (!id || !id.length) {
return
}
if (!needConfirm) {
this._execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle)
return
}
uni.showModal({
title: confirmTitle || t('uniCloud.component.remove.showModal.title'),
content: confirmContent || t('uniCloud.component.remove.showModal.content'),
showCancel: true,
success: (res) => {
if (!res.confirm) {
return
}
this._execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle)
}
})
},
update(id, value, {
action,
showToast = true,
toastTitle,
success,
fail,
complete,
needConfirm = true,
needLoading = true,
loadingTitle = ''
} = {}) {
if (needLoading) {
uni.showLoading({
title: loadingTitle
})
}
/* eslint-disable no-undef */
let db = uniCloud.database()
if (action) {
db = db.action(action)
}
return db.collection(this.collection).doc(id).update(value).then((res) => {
success && success(res)
if (showToast) {
uni.showToast({
title: toastTitle || t('uniCloud.component.update.success')
})
}
}).catch((err) => {
fail && fail(err)
if (needConfirm) {
uni.showModal({
content: err.message,
showCancel: false
})
}
}).finally(() => {
if (needLoading) {
uni.hideLoading()
}
complete && complete()
})
},
_execLoadData(callback, clear) {
if (this.loading) {
return
}
this.loading = true
this.errorMessage = ''
return this._getExec().then((res) => {
this.loading = false
const {
data,
count
} = res.result
this._isEnded = data.length < this.pageSize
this.hasMore = !this._isEnded
const data2 = this.getone ? (data.length ? data[0] : undefined) : data
if (this.getcount) {
this.paginationInternal.count = count
}
callback && callback(data2, this._isEnded, this.paginationInternal)
this._dispatchEvent(events.load, data2)
if (this.getone || this.pageData === pageMode.replace) {
this.dataList = data2
} else {
if (clear) {
this.dataList = data2
} else {
this.dataList.push(...data2)
}
}
}).catch((err) => {
this.loading = false
this.errorMessage = err
callback && callback()
this.$emit(events.error, err)
if (process.env.NODE_ENV === 'development') {
console.error(err)
}
})
},
_getExec() {
/* eslint-disable no-undef */
let db = uniCloud.database()
if (this.action) {
db = db.action(this.action)
}
db = db.collection(this.collection)
if (!(!this.where || !Object.keys(this.where).length)) {
db = db.where(this.where)
}
if (this.field) {
db = db.field(this.field)
}
if (this.foreignKey) {
db = db.foreignKey(this.foreignKey)
}
if (this.groupby) {
db = db.groupBy(this.groupby)
}
if (this.groupField) {
db = db.groupField(this.groupField)
}
if (this.distinct === true) {
db = db.distinct()
}
if (this.orderby) {
db = db.orderBy(this.orderby)
}
const {
current,
size
} = this.paginationInternal
const getOptions = {}
if (this.getcount) {
getOptions.getCount = this.getcount
}
const treeOptions = {
limitLevel: this.limitlevel,
startWith: this.startwith
}
if (this.gettree) {
getOptions.getTree = treeOptions
}
if (this.gettreepath) {
getOptions.getTreePath = treeOptions
}
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
return db
},
_execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle) {
if (!this.collection || !id) {
return
}
const ids = Array.isArray(id) ? id : [id]
if (!ids.length) {
return
}
if (needLoading) {
uni.showLoading({
mask: true,
title: loadingTitle
})
}
/* eslint-disable no-undef */
const db = uniCloud.database()
const dbCmd = db.command
let exec = db
if (action) {
exec = exec.action(action)
}
exec.collection(this.collection).where({
_id: dbCmd.in(ids)
}).remove().then((res) => {
success && success(res.result)
if (this.pageData === pageMode.replace) {
this.refresh()
} else {
this.removeData(ids)
}
}).catch((err) => {
fail && fail(err)
if (needConfirm) {
uni.showModal({
content: err.message,
showCancel: false
})
}
}).finally(() => {
if (needLoading) {
uni.hideLoading()
}
complete && complete()
})
},
removeData(ids) {
const il = ids.slice(0)
const dl = this.dataList
for (let i = dl.length - 1; i >= 0; i--) {
const index = il.indexOf(dl[i]._id)
if (index >= 0) {
dl.splice(i, 1)
il.splice(index, 1)
}
}
},
_dispatchEvent(type, data) {
if (this._changeDataFunction) {
this._changeDataFunction(data, this._isEnded, this.paginationInternal)
} else {
this.$emit(type, data, this._isEnded, this.paginationInternal)
}
}
}
}
</script>
......@@ -2,6 +2,7 @@
"name": "@dcloudio/uni-components",
"version": "3.0.0",
"description": "@dcloudio/uni-components",
"main": "index.js",
"files": [
"style"
],
......
......@@ -653,9 +653,27 @@ function wrapperTaskApi(name, fn, protocol, options) {
});
};
}
function wrapperSyncApi(name, fn, protocol, options) {
return (...args) => {
const errMsg = beforeInvokeApi(name, args, protocol, options);
if (errMsg) {
throw new Error(errMsg);
}
return fn.apply(null, args);
};
}
function wrapperAsyncApi(name, fn, protocol, options) {
return wrapperTaskApi(name, fn, protocol, options);
}
function defineTaskApi(name, fn, protocol, options) {
return promisify(wrapperTaskApi(name, fn, process.env.NODE_ENV !== "production" ? protocol : void 0, options));
}
function defineSyncApi(name, fn, protocol, options) {
return wrapperSyncApi(name, fn, process.env.NODE_ENV !== "production" ? protocol : void 0, options);
}
function defineAsyncApi(name, fn, protocol, options) {
return promisify(wrapperAsyncApi(name, fn, process.env.NODE_ENV !== "production" ? protocol : void 0, options));
}
const SCHEME_RE = /^([a-z-]+:)?\/\//i;
const DATA_RE = /^data:.*,.*/;
function addBase(filePath) {
......@@ -691,6 +709,46 @@ function getRealPath(filePath) {
return filePath;
}
const API_ON_TAB_BAR_MID_BUTTON_TAP = "onTabBarMidButtonTap";
const API_GET_STORAGE = "getStorage";
const GetStorageProtocol = {
key: {
type: String,
required: true
}
};
const API_GET_STORAGE_SYNC = "getStorageSync";
const GetStorageSyncProtocol = [
{
name: "key",
type: String,
required: true
}
];
const API_SET_STORAGE = "setStorage";
const SetStorageProtocol = {
key: {
type: String,
required: true
},
data: {
required: true
}
};
const API_SET_STORAGE_SYNC = "setStorageSync";
const SetStorageSyncProtocol = [
{
name: "key",
type: String,
required: true
},
{
name: "data",
required: true
}
];
const API_REMOVE_STORAGE = "removeStorage";
const RemoveStorageProtocol = GetStorageProtocol;
const RemoveStorageSyncProtocol = GetStorageSyncProtocol;
const API_REQUEST = "request";
const dataType = {
JSON: "json"
......@@ -8350,10 +8408,141 @@ function parseHeaders(headers) {
});
return headersObject;
}
const STORAGE_KEYS = "uni-storage-keys";
function parseValue(value) {
const types = ["object", "string", "number", "boolean", "undefined"];
try {
const object = typeof value === "string" ? JSON.parse(value) : value;
const type = object.type;
if (types.indexOf(type) >= 0) {
const keys = Object.keys(object);
if (keys.length === 2 && "data" in object) {
if (typeof object.data === type) {
return object.data;
}
if (type === "object" && /^\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}\.\d{3}Z$/.test(object.data)) {
return new Date(object.data);
}
} else if (keys.length === 1) {
return "";
}
}
} catch (error) {
}
}
const setStorageSync = /* @__PURE__ */ defineSyncApi(API_SET_STORAGE_SYNC, (key, data) => {
const type = typeof data;
const value = type === "string" ? data : JSON.stringify({
type,
data
});
localStorage.setItem(key, value);
}, SetStorageSyncProtocol);
const setStorage = /* @__PURE__ */ defineAsyncApi(API_SET_STORAGE, ({key, data}, {resolve, reject}) => {
try {
setStorageSync(key, data);
resolve();
} catch (error) {
reject(error.message);
}
}, SetStorageProtocol);
function getStorageOrigin(key) {
const value = localStorage && localStorage.getItem(key);
if (typeof value !== "string") {
throw new Error("data not found");
}
let data = value;
try {
const object = JSON.parse(value);
const result = parseValue(object);
if (result !== void 0) {
data = result;
}
} catch (error) {
}
return data;
}
const getStorageSync = /* @__PURE__ */ defineSyncApi(API_GET_STORAGE_SYNC, (key, t2) => {
try {
return getStorageOrigin(key);
} catch (error) {
return "";
}
}, GetStorageSyncProtocol);
const getStorage = /* @__PURE__ */ defineAsyncApi(API_GET_STORAGE, ({key}, {resolve, reject}) => {
try {
const data = getStorageOrigin(key);
resolve({
data
});
} catch (error) {
reject(error.message);
}
}, GetStorageProtocol);
const removeStorageSync = /* @__PURE__ */ defineSyncApi(API_REMOVE_STORAGE, (key) => {
if (localStorage) {
localStorage.removeItem(key);
}
}, RemoveStorageSyncProtocol);
const removeStorage = /* @__PURE__ */ defineAsyncApi(API_REMOVE_STORAGE, ({key}, {resolve}) => {
removeStorageSync(key);
resolve();
}, RemoveStorageProtocol);
const clearStorageSync = /* @__PURE__ */ defineSyncApi("clearStorageSync", () => {
if (localStorage) {
localStorage.clear();
}
});
const clearStorage = /* @__PURE__ */ defineAsyncApi("clearStorage", (_, {resolve}) => {
clearStorageSync();
resolve();
});
const getStorageInfoSync = /* @__PURE__ */ defineSyncApi("getStorageInfoSync", () => {
const length = localStorage && localStorage.length || 0;
const keys = [];
let currentSize = 0;
for (let index2 = 0; index2 < length; index2++) {
const key = localStorage.key(index2);
const value = localStorage.getItem(key) || "";
currentSize += key.length + value.length;
if (key !== STORAGE_KEYS) {
keys.push(key);
}
}
return {
keys,
currentSize: Math.ceil(currentSize * 2 / 1024),
limitSize: Number.MAX_VALUE
};
});
const getStorageInfo = /* @__PURE__ */ defineAsyncApi("getStorageInfo", (_, {resolve}) => {
resolve(getStorageInfoSync());
});
const getSystemInfoSync = /* @__PURE__ */ defineSyncApi("getSystemInfoSync", () => {
{
return {
deviceId: Date.now() + "" + Math.floor(Math.random() * 1e7),
platform: "nodejs"
};
}
});
require("localstorage-polyfill");
global.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var api = /* @__PURE__ */ Object.freeze({
__proto__: null,
[Symbol.toStringTag]: "Module",
request
request,
setStorageSync,
setStorage,
getStorageSync,
getStorage,
removeStorageSync,
removeStorage,
clearStorageSync,
clearStorage,
getStorageInfoSync,
getStorageInfo,
getSystemInfoSync
});
const uni$1 = api;
const UniServiceJSBridge$1 = /* @__PURE__ */ shared.extend(ServiceJSBridge, {
......@@ -9236,10 +9425,21 @@ exports.UniViewJSBridge = UniViewJSBridge$1;
exports.Video = index$5;
exports.View = index$6;
exports.WebView = index$4;
exports.clearStorage = clearStorage;
exports.clearStorageSync = clearStorageSync;
exports.getApp = getApp$1;
exports.getCurrentPages = getCurrentPages$1;
exports.getStorage = getStorage;
exports.getStorageInfo = getStorageInfo;
exports.getStorageInfoSync = getStorageInfoSync;
exports.getStorageSync = getStorageSync;
exports.getSystemInfoSync = getSystemInfoSync;
exports.plugin = index$m;
exports.removeStorage = removeStorage;
exports.removeStorageSync = removeStorageSync;
exports.request = request;
exports.setStorage = setStorage;
exports.setStorageSync = setStorageSync;
exports.setupApp = setupApp;
exports.setupPage = setupPage;
exports.uni = uni$1;
......
......@@ -2516,7 +2516,7 @@ function createNormalizeUrl(type) {
return;
} else if (type === API_PRELOAD_PAGE) {
if (routeOptions.meta.isTabBar) {
const pages = getCurrentPages(true);
const pages = getCurrentPages();
const tabBarPagePath = routeOptions.path.substr(1);
if (pages.find((page) => page.route === tabBarPagePath)) {
return "tabBar page `" + tabBarPagePath + "` already exists";
......
......@@ -22,7 +22,9 @@
"url": "https://github.com/dcloudio/uni-app/issues"
},
"dependencies": {
"localstorage-polyfill": "^1.0.1",
"safe-area-insets": "^1.4.1",
"vue-router": "^4.0.6"
"vue-router": "^4.0.6",
"xmlhttprequest": "^1.8.0"
}
}
......@@ -25,6 +25,13 @@ import {
export const getSystemInfoSync = defineSyncApi<typeof uni.getSystemInfoSync>(
'getSystemInfoSync',
() => {
if (__NODE_JS__) {
//TODO 临时搞一下配合 uniCloud 测试
return ({
deviceId: Date.now() + '' + Math.floor(Math.random() * 1e7),
platform: 'nodejs',
} as unknown) as UniApp.GetSystemInfoResult
}
const pixelRatio = window.devicePixelRatio
// 横屏时 iOS 获取的屏幕宽高颠倒,进行纠正
const screenFix = getScreenFix()
......
//#if _NODE_JS_
// 目前这几个接口主要是 uniCloud 使用了
// 目前采用 polyfill 解决 xhr 和 storage
/* eslint-disable no-restricted-globals */
require('localstorage-polyfill')
global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest
export * from './network/request'
export * from './storage/storage'
export * from './device/getSystemInfoSync'
//#else
export * from './base/canIUse'
......
......@@ -47,7 +47,7 @@ if (FORMAT === 'cjs') {
export default defineConfig({
root: __dirname,
define: {
global: 'window',
global: FORMAT === 'cjs' ? 'global' : 'window',
__DEV__: `(process.env.NODE_ENV !== 'production')`,
__TEST__: false,
__PLATFORM__: JSON.stringify('h5'),
......
......@@ -226,6 +226,9 @@ function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_
if (typeof locale !== 'string') {
[locale, messages] = [messages, locale];
}
if (typeof locale !== 'string') {
locale = fallbackLocale;
}
const i18n = new I18n({
locale: locale || fallbackLocale,
fallbackLocale,
......
......@@ -222,6 +222,9 @@ function initVueI18n(locale = LOCALE_EN, messages = {}, fallbackLocale = LOCALE_
if (typeof locale !== 'string') {
[locale, messages] = [messages, locale];
}
if (typeof locale !== 'string') {
locale = fallbackLocale;
}
const i18n = new I18n({
locale: locale || fallbackLocale,
fallbackLocale,
......
......@@ -38,6 +38,9 @@ export function initVueI18n(
if (typeof locale !== 'string') {
;[locale, messages] = [messages as BuiltInLocale, locale as LocaleMessages]
}
if (typeof locale !== 'string') {
locale = fallbackLocale
}
const i18n = new I18n({
locale: locale || fallbackLocale,
fallbackLocale,
......
......@@ -2239,7 +2239,7 @@ function createAppAPI() {
if (__VUE_OPTIONS_API__) {
if (!context.mixins.includes(mixin)) {
context.mixins.push(mixin);
// global mixin with props/emits de-optimizes props/emits
// window mixin with props/emits de-optimizes props/emits
// normalization caching.
if (mixin.props || mixin.emits) {
context.deopt = true;
......@@ -2337,7 +2337,7 @@ function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false
// local registration
// check instance[type] first for components with mixin or extends.
resolve(instance[type] || Component[type], name) ||
// global registration
// window registration
resolve(instance.appContext[type], name);
if (!res && maybeSelfReference) {
// fallback to implicit self-reference
......@@ -2734,7 +2734,7 @@ function applyOptions(instance, options, deferredData = [], deferredWatch = [],
shouldCacheAccess = false;
callSyncHook('beforeCreate', "bc" /* BEFORE_CREATE */, options, instance, globalMixins);
shouldCacheAccess = true;
// global mixins are applied first
// window mixins are applied first
applyMixins(instance, globalMixins, deferredData, deferredWatch, deferredProvide);
}
// extending a base component...
......@@ -3215,7 +3215,7 @@ const PublicInstanceProxyHandlers = {
return ctx[key];
}
else if (
// global properties
// window properties
((globalProperties = appContext.config.globalProperties),
hasOwn(globalProperties, key))) {
return globalProperties[key];
......@@ -3327,7 +3327,7 @@ function createRenderContext(instance) {
set: NOOP
});
});
// expose global properties
// expose window properties
const { globalProperties } = instance.appContext.config;
Object.keys(globalProperties).forEach(key => {
Object.defineProperty(target, key, {
......
......@@ -192,7 +192,8 @@ function once(fn, ctx = null) {
}
return res;
});
}
}
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -295,7 +296,10 @@ const TABBAR_HEIGHT = 50;
const ON_REACH_BOTTOM_DISTANCE = 50;
const RESPONSIVE_MIN_WIDTH = 768;
const COMPONENT_NAME_PREFIX = 'VUni';
const PRIMARY_COLOR = '#007aff';
const PRIMARY_COLOR = '#007aff';
const UNI_SSR = '__uniSSR';
const UNI_SSR_DATA = 'data';
const UNI_SSR_GLOBAL_DATA = 'globalData';
function getEnvLocale() {
const { env } = process;
......@@ -314,6 +318,9 @@ exports.PRIMARY_COLOR = PRIMARY_COLOR;
exports.RESPONSIVE_MIN_WIDTH = RESPONSIVE_MIN_WIDTH;
exports.TABBAR_HEIGHT = TABBAR_HEIGHT;
exports.TAGS = TAGS;
exports.UNI_SSR = UNI_SSR;
exports.UNI_SSR_DATA = UNI_SSR_DATA;
exports.UNI_SSR_GLOBAL_DATA = UNI_SSR_GLOBAL_DATA;
exports.addFont = addFont;
exports.debounce = debounce;
exports.decode = decode;
......@@ -331,6 +338,7 @@ exports.parseQuery = parseQuery;
exports.passive = passive;
exports.plusReady = plusReady;
exports.removeLeadingSlash = removeLeadingSlash;
exports.sanitise = sanitise;
exports.scrollTo = scrollTo;
exports.stringifyQuery = stringifyQuery;
exports.updateElementStyle = updateElementStyle;
......@@ -76,6 +76,8 @@ export declare function removeLeadingSlash(str: string): string;
export declare const RESPONSIVE_MIN_WIDTH = 768;
export declare const sanitise: (val: unknown) => any;
declare function scrollTo_2(scrollTop: number | string, duration: number): void;
export { scrollTo_2 as scrollTo }
......@@ -85,6 +87,12 @@ export declare const TABBAR_HEIGHT = 50;
export declare const TAGS: string[];
export declare const UNI_SSR = "__uniSSR";
export declare const UNI_SSR_DATA = "data";
export declare const UNI_SSR_GLOBAL_DATA = "globalData";
export declare function updateElementStyle(element: HTMLElement, styles: Partial<CSSStyleDeclaration>): void;
export { }
......@@ -188,7 +188,8 @@ function once(fn, ctx = null) {
}
return res;
});
}
}
const sanitise = (val) => (val && JSON.parse(JSON.stringify(val))) || val;
const encode = encodeURIComponent;
function stringifyQuery(obj, encodeStr = encode) {
......@@ -291,7 +292,10 @@ const TABBAR_HEIGHT = 50;
const ON_REACH_BOTTOM_DISTANCE = 50;
const RESPONSIVE_MIN_WIDTH = 768;
const COMPONENT_NAME_PREFIX = 'VUni';
const PRIMARY_COLOR = '#007aff';
const PRIMARY_COLOR = '#007aff';
const UNI_SSR = '__uniSSR';
const UNI_SSR_DATA = 'data';
const UNI_SSR_GLOBAL_DATA = 'globalData';
function getEnvLocale() {
const { env } = process;
......@@ -299,4 +303,4 @@ function getEnvLocale() {
return (lang && lang.replace(/[.:].*/, '')) || 'en';
}
export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, ON_REACH_BOTTOM_DISTANCE, PLUS_RE, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, addFont, debounce, decode, decodedQuery, getEnvLocale, getLen, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, normalizeTarget, once, parseQuery, passive, plusReady, removeLeadingSlash, scrollTo, stringifyQuery, updateElementStyle };
export { BUILT_IN_TAGS, COMPONENT_NAME_PREFIX, COMPONENT_PREFIX, COMPONENT_SELECTOR_PREFIX, NAVBAR_HEIGHT, ON_REACH_BOTTOM_DISTANCE, PLUS_RE, PRIMARY_COLOR, RESPONSIVE_MIN_WIDTH, TABBAR_HEIGHT, TAGS, UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA, addFont, debounce, decode, decodedQuery, getEnvLocale, getLen, invokeArrayFns, isBuiltInComponent, isCustomElement, isNativeTag, normalizeDataset, normalizeTarget, once, parseQuery, passive, plusReady, removeLeadingSlash, sanitise, scrollTo, stringifyQuery, updateElementStyle };
......@@ -6,3 +6,7 @@ export const RESPONSIVE_MIN_WIDTH = 768
export const COMPONENT_NAME_PREFIX = 'VUni'
export const PRIMARY_COLOR = '#007aff'
export const UNI_SSR = '__uniSSR'
export const UNI_SSR_DATA = 'data'
export const UNI_SSR_GLOBAL_DATA = 'globalData'
......@@ -36,3 +36,6 @@ export function once<T extends (...args: any[]) => any>(
return res
}) as T
}
export const sanitise = (val: unknown) =>
(val && JSON.parse(JSON.stringify(val))) || val
import { UNI_SSR, UNI_SSR_DATA, UNI_SSR_GLOBAL_DATA } from '@dcloudio/uni-shared'
import { renderToString } from '@vue/server-renderer'
let AppInstance
......@@ -27,7 +28,16 @@ export async function render(url, manifest = {}) {
// which we can then use to determine what files need to be preloaded for this
// request.
const preloadLinks = renderPreloadLinks(ctx.modules, manifest)
return [html, preloadLinks]
// the SSR context
const __uniSSR = ctx[UNI_SSR] || (ctx[UNI_SSR] = {})
if(!__uniSSR[UNI_SSR_DATA]){
__uniSSR[UNI_SSR_DATA] = {}
}
if(!__uniSSR[UNI_SSR_GLOBAL_DATA]){
__uniSSR[UNI_SSR_GLOBAL_DATA] = {}
}
const appContext = renderAppContext(ctx)
return [html, preloadLinks, appContext]
}
function renderPreloadLinks(modules, manifest) {
......@@ -57,3 +67,7 @@ function renderPreloadLink(file) {
return ''
}
}
function renderAppContext(ctx){
return `<script>window.__uniSSR = ${JSON.stringify(ctx[UNI_SSR])}</script>`
}
......@@ -24,6 +24,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@vue/compiler-sfc": "^3.0.11",
"@rollup/pluginutils": "^4.1.0",
"autoprefixer": "^10.2.5",
"cac": "^6.7.3",
......@@ -33,7 +34,6 @@
"express": "^4.17.1",
"fs-extra": "^9.0.1",
"jsonc-parser": "^3.0.0",
"magic-string": "^0.25.7",
"mime": "^2.5.2",
"module-alias": "^2.2.2",
"postcss-selector-parser": "^6.0.4",
......@@ -50,8 +50,7 @@
"@types/express": "^4.17.11",
"@types/mime": "^2.0.3",
"@types/module-alias": "^2.0.0",
"@types/sass": "^1.16.0",
"@vue/compiler-sfc": "^3.0.11"
"@types/sass": "^1.16.0"
},
"uni-app": {
"compilerVersion": "3.1.2"
......
......@@ -59,11 +59,12 @@ export async function createSSRServer(options: CliOptions & ServerOptions) {
await vite.ssrLoadModule(resolveMainPathOnce(process.env.UNI_INPUT_DIR))
).render
const [appHtml, preloadLinks] = await render(url)
const [appHtml, preloadLinks, appContext] = await render(url)
const html = template
.replace(`<!--preload-links-->`, preloadLinks)
.replace(`<!--app-html-->`, appHtml)
.replace(`<!--app-context-->`, appContext)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
vite && vite.ssrFixStacktrace(e)
......
......@@ -18,6 +18,7 @@ import { uniStaticPlugin } from './static'
import { uniCssScopedPlugin } from './cssScoped'
import { uniRenderjsPlugin } from './renderjs'
import { uniPreVuePlugin } from './preVue'
import { uniSSRPlugin } from './ssr'
const debugPlugin = debug('vite:uni:plugin')
......@@ -137,6 +138,12 @@ export function initPlugins(
}
}
addPlugin(
plugins,
uniSSRPlugin(extend({ exclude: [...COMMON_EXCLUDE] }, options)),
'vite:vue'
)
addPlugin(
plugins,
uniEasycomPlugin(extend(uniEasycomPluginOptions, options)),
......
......@@ -2,15 +2,7 @@ import path, { sep } from 'path'
import debug from 'debug'
import { Plugin } from 'vite'
import {
BaseNode,
Program,
Property,
Identifier,
MemberExpression,
MethodDefinition,
ExportSpecifier,
} from 'estree'
import { BaseNode, Program, Identifier } from 'estree'
import {
attachScopes,
......@@ -22,7 +14,7 @@ import { AcornNode } from 'rollup'
import { walk } from 'estree-walker'
import MagicString from 'magic-string'
import { MagicString } from '@vue/compiler-sfc'
import {
EXTNAME_JS,
......@@ -32,6 +24,8 @@ import {
import { UniPluginFilterOptions } from '.'
import { isProperty, isReference, isMemberExpression } from '../../utils'
interface Scope {
parent: Scope
contains: (name: string) => boolean
......@@ -231,39 +225,6 @@ export function uniInjectPlugin(options: InjectOptions): Plugin {
const escape = (str: string) => str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&')
const isProperty = (node: BaseNode): node is Property =>
node.type === 'Property'
const isIdentifier = (node: BaseNode): node is Identifier =>
node.type === 'Identifier'
const isMemberExpression = (node: BaseNode): node is MemberExpression =>
node.type === 'MemberExpression'
const isMethodDefinition = (node: BaseNode): node is MethodDefinition =>
node.type === 'MethodDefinition'
const isExportSpecifier = (node: BaseNode): node is ExportSpecifier =>
node.type === 'ExportSpecifier'
const isReference = (node: BaseNode, parent: BaseNode): boolean => {
if (isMemberExpression(node)) {
return !node.computed && isReference(node.object, node)
}
if (isIdentifier(node)) {
if (isMemberExpression(parent))
return parent.computed || node === parent.object
// `bar` in { bar: foo }
if (isProperty(parent) && node !== parent.value) return false
// `bar` in `class Foo { bar () {...} }`
if (isMethodDefinition(parent)) return false
// `bar` in `export { foo as bar }`
if (isExportSpecifier(parent) && node !== parent.local) return false
return true
}
return false
}
const flatten = (startNode: BaseNode) => {
const parts = []
let node = startNode
......
import debug from 'debug'
import crypto from 'crypto'
import { Plugin } from 'vite'
import { walk } from 'estree-walker'
import { CallExpression } from 'estree'
import { createFilter } from '@rollup/pluginutils'
import { UniPluginFilterOptions } from '.'
import { isIdentifier, isCallExpression, isMemberExpression } from '../../utils'
import { MagicString } from '@vue/compiler-sfc'
const debugSSR = debug('vite:uni:ssr')
const KEYED_FUNC_RE = /(ssrRef|shallowSsrRef)/
export function uniSSRPlugin(options: UniPluginFilterOptions): Plugin {
const filter = createFilter(options.include, options.exclude)
return {
name: 'vite:uni-ssr',
transform(code, id) {
if (!filter(id)) return null
if (!KEYED_FUNC_RE.test(code)) {
return code
}
debugSSR('try', id)
const ast = this.parse(code)
const s = new MagicString(code)
walk(ast, {
enter(node) {
if (!isCallExpression(node)) {
return
}
const { callee, arguments: args } = node as CallExpression
if (args.length !== 1) {
return
}
const name = isIdentifier(callee)
? callee.name
: isMemberExpression(callee) && isIdentifier(callee.property)
? callee.property.name
: ''
if (name !== 'ssrRef' && name !== 'shallowSsrRef') {
return
}
const { end } = (node as unknown) as { end: number }
const key = id + '-' + (node as any).end
debugSSR(key, name)
s.appendLeft(end - 1, ", '" + createKey(`${id}-${end}`) + "'")
},
})
return {
code: s.toString(),
map: s.generateMap().toString(),
}
},
}
}
function createKey(source: string) {
const hash = crypto.createHash('md5')
hash.update(source)
return hash.digest('base64').toString()
}
import {
Literal,
BaseNode,
Property,
Identifier,
CallExpression,
MemberExpression,
MethodDefinition,
ExportSpecifier,
} from 'estree'
export const isProperty = (node: BaseNode): node is Property =>
node.type === 'Property'
export const isIdentifier = (node: BaseNode): node is Identifier =>
node.type === 'Identifier'
export const isCallExpression = (node: BaseNode): node is CallExpression =>
node.type === 'CallExpression'
export const isMemberExpression = (node: BaseNode): node is MemberExpression =>
node.type === 'MemberExpression'
export const isMethodDefinition = (node: BaseNode): node is MethodDefinition =>
node.type === 'MethodDefinition'
export const isExportSpecifier = (node: BaseNode): node is ExportSpecifier =>
node.type === 'ExportSpecifier'
export const isReference = (node: BaseNode, parent: BaseNode): boolean => {
if (isMemberExpression(node)) {
return !node.computed && isReference(node.object, node)
}
if (isIdentifier(node)) {
if (isMemberExpression(parent))
return parent.computed || node === parent.object
// `bar` in { bar: foo }
if (isProperty(parent) && node !== parent.value) return false
// `bar` in `class Foo { bar () {...} }`
if (isMethodDefinition(parent)) return false
// `bar` in `export { foo as bar }`
if (isExportSpecifier(parent) && node !== parent.local) return false
return true
}
return false
}
export function createLiteral(value: string) {
return {
type: 'Literal',
value,
raw: `'${value}'`,
} as Literal
}
......@@ -40,6 +40,10 @@ function clearEasycom() {
export const initEasycomsOnce = once(
(inputDir: string, platform: UniApp.PLATFORM) => {
const buildInComponentsDir = path.resolve(
require.resolve('@dcloudio/uni-components'),
'../lib'
)
const componentsDir = path.resolve(inputDir, 'components')
const uniModulesDir = path.resolve(inputDir, 'uni_modules')
const initEasycomOptions = (pagesJson?: UniApp.PagesJson) => {
......@@ -48,8 +52,12 @@ export const initEasycomsOnce = once(
const easycomOptions: EasycomOption = {
dirs:
easycom && easycom.autoscan === false
? [] // 禁止自动扫描
: [componentsDir, ...initUniModulesEasycomDirs(uniModulesDir)],
? [buildInComponentsDir] // 禁止自动扫描
: [
buildInComponentsDir,
componentsDir,
...initUniModulesEasycomDirs(uniModulesDir),
],
rootDir: inputDir,
autoscan: !!(easycom && easycom.autoscan),
custom: (easycom && easycom.custom) || {},
......
export * from './ast'
export * from './ssr'
export * from './filter'
export * from './features'
......
......@@ -115,8 +115,10 @@ function createAliasPlugin(buildOptions) {
function createReplacePlugin(buildOptions, format) {
const replacements = {
global: format === 'cjs' ? 'global' : 'window',
__DEV__: `(process.env.NODE_ENV !== 'production')`,
__TEST__: false,
__PLATFORM__: JSON.stringify('h5'),
__NODE_JS__: format === 'cjs',
}
if (buildOptions.replacements) {
......
......@@ -1803,9 +1803,9 @@ camelcase@^6.0.0:
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001219:
version "1.0.30001222"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001222.tgz#2789b8487282cbbe1700924f53951303d28086a9"
integrity sha512-rPmwUK0YMjfMlZVmH6nVB5U3YJ5Wnx3vmT5lnRO3nIKO8bJ+TRWMbGuuiSugDJqESy/lz+1hSrlQEagCtoOAWQ==
version "1.0.30001223"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001223.tgz#39b49ff0bfb3ee3587000d2f66c47addc6e14443"
integrity sha512-k/RYs6zc/fjbxTjaWZemeSmOjO0JJV+KguOBA3NwPup8uzxM1cMhR2BD9XmO86GuqaqTCO8CgkgH9Rz//vdDiA==
capture-exit@^2.0.0:
version "2.0.0"
......@@ -3102,9 +3102,9 @@ glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
is-glob "^4.0.1"
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
version "7.1.7"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
......@@ -4430,6 +4430,11 @@ loader-utils@^1.1.0:
emojis-list "^3.0.0"
json5 "^1.0.1"
localstorage-polyfill@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/localstorage-polyfill/-/localstorage-polyfill-1.0.1.tgz#4b3083d4bc51d23b4158537e66816137413fd31a"
integrity sha1-SzCD1LxR0jtBWFN+ZoFhN0E/0xo=
locate-path@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
......@@ -4447,11 +4452,6 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
lodash.get@^4.0.0:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
......@@ -6230,13 +6230,12 @@ symbol-tree@^3.2.4:
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
table@^6.0.4:
version "6.6.0"
resolved "https://registry.yarnpkg.com/table/-/table-6.6.0.tgz#905654b79df98d9e9a973de1dd58682532c40e8e"
integrity sha512-iZMtp5tUvcnAdtHpZTWLPF0M7AgiQsURR2DwmxnJwSy8I3+cY+ozzVvYha3BOLG2TB+L0CqjIz+91htuj6yCXg==
version "6.7.0"
resolved "https://registry.yarnpkg.com/table/-/table-6.7.0.tgz#26274751f0ee099c547f6cb91d3eff0d61d155b2"
integrity sha512-SAM+5p6V99gYiiy2gT5ArdzgM1dLDed0nkrWmG6Fry/bUS/m9x83BwpJUOf1Qj/x2qJd+thL6IkIx7qPGRxqBw==
dependencies:
ajv "^8.0.1"
lodash.clonedeep "^4.5.0"
lodash.flatten "^4.4.0"
lodash.truncate "^4.4.2"
slice-ansi "^4.0.0"
string-width "^4.2.0"
......@@ -6785,6 +6784,11 @@ xmlchars@^2.2.0:
resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
xmlhttprequest@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc"
integrity sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw=
xregexp@3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-3.1.0.tgz#14d8461e0bdd38224bfee5039a0898fc42fcd336"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册