diff --git a/package.json b/package.json index e20de23264ba88133189b0411e7f18548e7eaadd..20f6b3cf0aed6c261d2ddc664c9bc8834ef9cbff 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "lint-staged": "^10.5.3", "mini-types": "^0.1.4", "miniprogram-api-typings": "^3.2.0", + "module-alias": "^2.2.2", "prettier": "^2.2.1", "rollup": "^2.35.1", "rollup-plugin-node-builtins": "^2.1.2", diff --git a/packages/global.d.ts b/packages/global.d.ts index 35df5576401cf8d76850221aa924cb532b421858..6c88bcf6a43a28ddc3264070ead04824d1bde70d 100644 --- a/packages/global.d.ts +++ b/packages/global.d.ts @@ -26,6 +26,7 @@ declare var __UNI_FEATURE_LEFTWINDOW__: boolean declare var __UNI_FEATURE_RIGHTWINDOW__: boolean declare var __UNI_FEATURE_RESPONSIVE__: boolean declare var __UNI_FEATURE_PULL_DOWN_REFRESH__: boolean +declare var __UNI_FEATURE_NAVIGATIONBAR_BUTTONS__: boolean // TODO declare var __uniRoutes: any declare var __uniConfig: UniApp.UniConfig diff --git a/packages/shims-uni-app.d.ts b/packages/shims-uni-app.d.ts index fb9bf1b1bbbb5393f4a5a40bd0a5bb781a4bf703..bcb86dae5e45694e409e0d9fdd7f0822f606aa7f 100644 --- a/packages/shims-uni-app.d.ts +++ b/packages/shims-uni-app.d.ts @@ -1,13 +1,5 @@ declare namespace UniApp { - type PLATFORM = - | 'h5' - | 'app-plus' - | 'mp-alipay' - | 'mp-baidu' - | 'mp-qq' - | 'mp-toutiao' - | 'mp-weixin' - | 'quickapp-webview' + type PLATFORM = keyof PagesJsonPagePlatformStyle interface LayoutWindowOptions { matchMedia?: { minWidth?: number @@ -28,16 +20,38 @@ declare namespace UniApp { rightWindow?: LayoutWindowOptions } + interface PageNavigationBarButton { + type?: + | 'none' + | 'forward' + | 'back' + | 'share' + | 'favorite' + | 'home' + | 'menu' + | 'close' + color?: string + background?: string + colorPressed?: string + float?: 'right' | 'left' + fontWeight?: string + fontSize?: string + fontSrc?: string + select?: boolean + text?: string + width?: string + } interface PageNavigationBar { - type: 'default' | 'transparent' | 'float' | 'none' - titleText: string - textStyle: 'black' | 'white' - timingFunc: string - duration: string - backgroundColor: string - titlePenetrate: 'YES' | 'NO' - shadowColorType: 'grey' | 'blue' | 'green' | 'orange' | 'red' | 'yellow' - backButton: boolean + type?: 'default' | 'transparent' | 'float' | 'none' + titleText?: string + textStyle?: 'black' | 'white' + timingFunc?: string + duration?: string + backgroundColor?: string + titlePenetrate?: 'YES' | 'NO' + shadowColorType?: 'grey' | 'blue' | 'green' | 'orange' | 'red' | 'yellow' + backButton?: boolean + buttons?: PageNavigationBarButton[] } interface PageRefreshOptions { support: boolean @@ -47,7 +61,24 @@ declare namespace UniApp { range: number offset: number } - interface PageRouteMeta { + + interface PagesJsonPagePlatformStyle { + h5?: PagesJsonPageStyle + 'app-plus'?: PagesJsonPageStyle + 'mp-alipay'?: PagesJsonPageStyle + 'mp-baidu'?: PagesJsonPageStyle + 'mp-qq'?: PagesJsonPageStyle + 'mp-toutiao'?: PagesJsonPageStyle + 'mp-weixin'?: PagesJsonPageStyle + 'quickapp-webview'?: PagesJsonPageStyle + } + + interface PagesJsonPageStyle extends PagesJsonPagePlatformStyle { + enablePullDownRefresh?: boolean + navigationBar: PageNavigationBar + refreshOptions?: PageRefreshOptions + } + interface PageRouteMeta extends PagesJsonPageStyle { id: number isQuit?: boolean isEntry?: boolean @@ -57,18 +88,11 @@ declare namespace UniApp { topWindow?: boolean leftWindow?: boolean rightWindow?: boolean - enablePullDownRefresh?: boolean - navigationBar: PageNavigationBar - refreshOptions?: PageRefreshOptions - } - - interface PagesJsonPageStyle { - enablePullDownRefresh?: boolean } interface PagesJsonPageOptions { path: string - style?: PagesJsonPageStyle + style: PagesJsonPageStyle } interface PagesJsonSubpackagesOptions { root: string diff --git a/packages/uni-h5/dist/uni-h5.esm.js b/packages/uni-h5/dist/uni-h5.esm.js index 743499c2b60a66fe425d46b3a9a297a4785a3d39..690c2b59671b84ddba19ec3bfdd37b52d23692be 100644 --- a/packages/uni-h5/dist/uni-h5.esm.js +++ b/packages/uni-h5/dist/uni-h5.esm.js @@ -1,5 +1,5 @@ import {isFunction, extend, isPlainObject, hasOwn as hasOwn$1, hyphenate, isArray, isObject as isObject$1, capitalize, toRawType, makeMap as makeMap$1, isPromise} from "@vue/shared"; -import {injectHook, defineComponent, inject, provide, reactive, nextTick, computed, openBlock, createBlock, Fragment, withDirectives, createVNode, vShow, createCommentVNode, withCtx, KeepAlive, resolveDynamicComponent, resolveComponent, mergeProps, onMounted, ref, toDisplayString, toHandlers, renderSlot, withModifiers, vModelDynamic, renderList, vModelText, createTextVNode} from "vue"; +import {injectHook, defineComponent, inject, provide, reactive, nextTick, computed, withDirectives, createVNode, vShow, withCtx, openBlock, createBlock, KeepAlive, resolveDynamicComponent, resolveComponent, onMounted, ref, mergeProps, toDisplayString, toHandlers, renderSlot, createCommentVNode, withModifiers, vModelDynamic, Fragment, renderList, vModelText, createTextVNode} from "vue"; import {NAVBAR_HEIGHT, COMPONENT_NAME_PREFIX, isCustomElement, plusReady, debounce} from "@dcloudio/uni-shared"; import {createRouter, createWebHistory, createWebHashHistory, useRoute, RouterView} from "vue-router"; function applyOptions(options, instance2, publicThis) { @@ -871,55 +871,48 @@ function useKeepAliveRoute() { } var Layout = defineComponent({ name: "Layout", + props: { + onChange: Function + }, emits: ["change"], setup() { - const route = __UNI_FEATURE_TABBAR__ ? useRoute() : null; - const keepAliveRoute = __UNI_FEATURE_PAGES__ ? useKeepAliveRoute() : null; - __UNI_FEATURE_TOPWINDOW__ ? useTopWindow() : null; - __UNI_FEATURE_LEFTWINDOW__ ? useLeftWindow() : null; - __UNI_FEATURE_RIGHTWINDOW__ ? useRightWindow() : null; + const route = __UNI_FEATURE_TABBAR__ && useRoute(); + const keepAliveRoute = __UNI_FEATURE_PAGES__ && useKeepAliveRoute(); + __UNI_FEATURE_TOPWINDOW__ && useTopWindow(); + __UNI_FEATURE_LEFTWINDOW__ && useLeftWindow(); + __UNI_FEATURE_RIGHTWINDOW__ && useRightWindow(); return () => { - return openBlock(), createBlock(Fragment, null, [ - createLayoutVNode(keepAliveRoute), - createTabBarVNode(route) - ], 64); + const layoutTsx = createLayoutTsx(keepAliveRoute); + const tabBarTsx = __UNI_FEATURE_TABBAR__ && createTabBarTsx(route); + return [layoutTsx, tabBarTsx].filter(Boolean); }; } }); -function createLayoutVNode(keepAliveRoute, topWindow, leftWindow, rightWindow) { +function createLayoutTsx(keepAliveRoute, topWindow, leftWindow, rightWindow) { const routerVNode = __UNI_FEATURE_PAGES__ ? createRouterViewVNode(keepAliveRoute) : createPageVNode(); if (!__UNI_FEATURE_RESPONSIVE__) { return routerVNode; } - const topWindowVNode = __UNI_FEATURE_TOPWINDOW__ ? createTopWindowVNode() : createCommentVNode("", true); - const leftWindowVNode = __UNI_FEATURE_LEFTWINDOW__ ? createLeftWindowVNode() : createCommentVNode("", true); - const rightWindowVNode = __UNI_FEATURE_RIGHTWINDOW__ ? createRightWindowVNode() : createCommentVNode("", true); - return createVNode("uni-layout", null, [ - topWindowVNode, - createVNode("uni-content", null, [ - createVNode("uni-main", null, [routerVNode]), - leftWindowVNode, - rightWindowVNode - ]) - ]); + const topWindowTsx = __UNI_FEATURE_TOPWINDOW__ ? createTopWindowTsx() : null; + const leftWindowTsx = __UNI_FEATURE_LEFTWINDOW__ ? createLeftWindowTsx() : null; + const rightWindowTsx = __UNI_FEATURE_RIGHTWINDOW__ ? createRightWindowTsx() : null; + return createVNode("uni-layout", null, [topWindowTsx, createVNode("uni-content", null, [createVNode("uni-main", null, [routerVNode]), leftWindowTsx, rightWindowTsx])]); } -function createTabBarVNode(route) { - return __UNI_FEATURE_TABBAR__ ? withDirectives(createVNode(TabBar, null, null, 512), [ - [vShow, route.meta.isTabBar] - ]) : createCommentVNode("", true); +function createTabBarTsx(route) { + return withDirectives(createVNode(TabBar, null, null, 512), [[vShow, route.meta.isTabBar]]); } function createPageVNode() { return createVNode(__uniRoutes[1].component); } function createRouterViewVNode(keepAliveRoute) { return createVNode(RouterView, null, { - default: withCtx(({Component}) => [ - (openBlock(), createBlock(KeepAlive, {cache: keepAliveRoute.routeCache}, [ - (openBlock(), createBlock(resolveDynamicComponent(Component), { - key: keepAliveRoute.routeKey.value - })) - ], 1032, ["cache"])) - ]), + default: withCtx(({ + Component + }) => [(openBlock(), createBlock(KeepAlive, { + cache: keepAliveRoute.routeCache + }, [(openBlock(), createBlock(resolveDynamicComponent(Component), { + key: keepAliveRoute.routeKey.value + }))], 1032, ["cache"]))]), _: 1 }); } @@ -948,53 +941,27 @@ function useRightWindow() { height: 0 }; } -function createTopWindowVNode(topWindow) { - if (!__UNI_FEATURE_TOPWINDOW__) { - return createCommentVNode("", true); - } - const {component, style, height, show} = useTopWindow(); - return withDirectives(createVNode("uni-top-window", null, [ - createVNode("div", { - ref: "topWindow", - class: "uni-top-window", - style - }, [ - createVNode(component, mergeProps({ - onVnodeMounted(vnode) { - }, - "navigation-bar-title-text": "" - }), null, 16, ["navigation-bar-title-text"]) - ], 4), - createVNode("div", { - class: "uni-top-window--placeholder", - style: {height} - }, null, 4) - ], 512), [[vShow, show]]); +function createTopWindowTsx(topWindow) { } -function createLeftWindowVNode(leftWindow) { +function createLeftWindowTsx(leftWindow) { } -function createRightWindowVNode(leftWindow) { +function createRightWindowTsx(leftWindow) { } -const CSS_VARS = [ - "--status-bar-height", - "--top-window-height", - "--window-left", - "--window-right", - "--window-margin" -]; +const CSS_VARS = ["--status-bar-height", "--top-window-height", "--window-left", "--window-right", "--window-margin"]; var AppComponent = defineComponent({ name: "App", setup() { useCssVar(); useAppLifecycle(); - const {clazz, onChange: onChange2} = useAppClass(); - return () => (openBlock(), createBlock("uni-app", { + const { + clazz, + onChange: onChange2 + } = useAppClass(); + return () => createVNode("uni-app", { class: clazz.value - }, [ - createVNode(Layout, { - onChange: onChange2 - }, null, 8, ["onChange"]) - ], 2)); + }, [createVNode(Layout, { + onChange: onChange2 + }, null, 8, ["onChange"])], 2); } }); function useCssVar() { @@ -8074,17 +8041,16 @@ var PageHead = defineComponent({ clazz, style } = usePageHead(navigationBar); - const backButtonJsx = createBackButtonJsx(navigationBar); + const backButtonJsx = __UNI_FEATURE_PAGES__ ? createBackButtonJsx(navigationBar) : null; + const leftButtonsJsx = __UNI_FEATURE_NAVIGATIONBAR_BUTTONS__ ? createButtonsJsx("left", navigationBar) : []; return () => createVNode("uni-page-head", { "uni-page-head-type": navigationBar.type - }, { - default: () => [createVNode("div", { - class: clazz.value, - style: style.value - }, [createVNode("div", { - class: "uni-page-head-hd" - }, [backButtonJsx])], 6)] - }, 8, ["uni-page-head-type"]); + }, [createVNode("div", { + class: clazz.value, + style: style.value + }, [createVNode("div", { + class: "uni-page-head-hd" + }, [backButtonJsx, ...leftButtonsJsx])], 6)], 8, ["uni-page-head-type"]); } }); function createBackButtonJsx(navigationBar) { @@ -8092,11 +8058,19 @@ function createBackButtonJsx(navigationBar) { return createVNode("div", { class: "uni-page-head-btn" }, [createVNode("i", { - style: "{color:color,fontSize:'27px'}", + style: "fontSize:27px", class: "uni-btn-icon" }, [createTextVNode("\uE601")])]); } } +function createButtonsJsx(float, navigationBar) { + if (isArray(navigationBar.buttons)) { + return navigationBar.buttons.filter((btn) => btn.float === float).map((btn, index2) => createVNode("div", { + key: index2 + }, [createVNode("i", null, null)])); + } + return []; +} function usePageHead(navigationBar) { const clazz = computed(() => { const { diff --git a/packages/uni-h5/lib/babel-plugin-jsx/README.md b/packages/uni-h5/lib/babel-plugin-jsx/README.md new file mode 100644 index 0000000000000000000000000000000000000000..aef7dc5e7682b2ff5992d8c4674368b352a98753 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/README.md @@ -0,0 +1,334 @@ +# Babel Plugin JSX for Vue 3.0 + +[![CircleCI](https://circleci.com/gh/vuejs/jsx-next.svg?style=svg)](https://circleci.com/gh/vuejs/vue-next) [![npm package](https://img.shields.io/npm/v/@vue/babel-plugin-jsx.svg?style=flat-square)](https://www.npmjs.com/package/@vue/babel-plugin-jsx) + +To add Vue JSX support. + +English | [简体中文](/packages/babel-plugin-jsx/README-zh_CN.md) + +## Installation + +Install the plugin with: + +```bash +npm install @vue/babel-plugin-jsx -D +``` + +Then add the plugin to .babelrc: + +```js +{ + "plugins": ["@vue/babel-plugin-jsx"] +} +``` + +## Usage + +### options + +#### transformOn + +Type: `boolean` + +Default: `false` + +transform `on: { click: xx }` to `onClick: xxx` + +#### optimize + +Type: `boolean` + +Default: `false` + +enable optimization or not. It's not recommended to enable it If you are not familiar with Vue 3. + +#### isCustomElement + +Type: `(tag: string) => boolean` + +Default: `undefined` + +configuring custom elements + +#### mergeProps + +Type: `boolean` + +Default: `true` + +merge static and dynamic class / style attributes / onXXX handlers + +#### enableObjectSlots + +Type: `boolean` + +Default: `true` + +Whether to enable `object slots` (mentioned below the document) syntax". It might be useful in JSX, but it will add a lot of `_isSlot` condition expressions which increase your bundle size. And `v-slots` is still available even if `enableObjectSlots` is turned off. + +## Syntax + +### Content + +functional component + +```jsx +const App = () =>
Vue 3.0
; +``` + +with render + +```jsx +const App = { + render() { + return
Vue 3.0
; + }, +}; +``` + +```jsx +import { withModifiers, defineComponent } from "vue"; + +const App = defineComponent({ + setup() { + const count = ref(0); + + const inc = () => { + count.value++; + }; + + return () => ( +
{count.value}
+ ); + }, +}); +``` + +Fragment + +```jsx +const App = () => ( + <> + I'm + Fragment + +); +``` + +### Attributes / Props + +```jsx +const App = () => ; +``` + +with a dynamic binding: + +```jsx +const placeholderText = "email"; +const App = () => ; +``` + +### Directives + +v-show + +```jsx +const App = { + data() { + return { visible: true }; + }, + render() { + return ; + }, +}; +``` + +v-model + +> Note: You should pass the second param as string for using `arg`. + +```jsx + +``` + +```jsx + +``` + +```jsx + +``` + +Will compile to: + +```js +h(A, { + argument: val, + argumentModifiers: { + modifier: true, + }, + "onUpdate:argument": ($event) => (val = $event), +}); +``` + +v-models + +> Note: You should pass a Two-dimensional Arrays to v-models. + +```jsx + +``` + +```jsx + +``` + +```jsx + +``` + +Will compile to: + +```js +h(A, { + modelValue: foo, + modelModifiers: { + modifier: true, + }, + "onUpdate:modelValue": ($event) => (foo = $event), + bar: bar, + barModifiers: { + modifier: true, + }, + "onUpdate:bar": ($event) => (bar = $event), +}); +``` + +custom directive + +```jsx +const App = { + directives: { custom: customDirective }, + setup() { + return () => ; + }, +}; +``` + +### Slot + +> Note: In `jsx`, _`v-slot`_ should be replace with **`v-slots`** + +```jsx +const App = { + setup() { + const slots = { + foo: () => B, + }; + return () => ( + +
A
+
+ ); + }, +}; + +// or + +const App = { + setup() { + const slots = { + default: () =>
A
, + foo: () => B, + }; + return () => ; + }, +}; + +// or you can use object slots when `enableObjectSlots` is not false. +const App = { + setup() { + return () => ( + <> + + {{ + default: () =>
A
, + foo: () => B, + }} +
+ {() => "foo"} + + ); + }, +}; +``` + +### In TypeScript + +`tsconfig.json`: + +```json +{ + "compilerOptions": { + "jsx": "preserve" + } +} +``` + +## Who is using + + + + + + + + + +
+ + +
+ Ant Design Vue +
+
+ + +
+ Vant +
+
+ + +
+ Element Plus +
+
+ +## Compatibility + +This repo is only compatible with: + +- **Babel 7+** +- **Vue 3+** diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/buildProps.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/buildProps.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..91679f42d54ae778d8aa27f37e1d83d7fe40a9db --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/buildProps.d.ts @@ -0,0 +1,14 @@ +import * as t from '@babel/types'; +import { NodePath } from '@babel/traverse'; +import { State } from '.'; +export declare type Slots = t.Identifier | t.ObjectExpression | null; +declare const buildProps: (path: NodePath, state: State) => { + tag: t.CallExpression | t.Identifier | t.StringLiteral | t.MemberExpression; + props: t.Expression; + isComponent: boolean; + slots: null; + directives: t.ArrayExpression[]; + patchFlag: number; + dynamicPropNames: Set; +}; +export default buildProps; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/buildProps.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/buildProps.js new file mode 100644 index 0000000000000000000000000000000000000000..77e66ab620d4694ae8a696d43edb498c24e19b1c --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/buildProps.js @@ -0,0 +1,293 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const t = __importStar(require("@babel/types")); +const helper_module_imports_1 = require("@babel/helper-module-imports"); +const utils_1 = require("./utils"); +const parseDirectives_1 = __importDefault(require("./parseDirectives")); +const transform_vue_jsx_1 = require("./transform-vue-jsx"); +const xlinkRE = /^xlink([A-Z])/; +const onRE = /^on[^a-z]/; +const isOn = (key) => onRE.test(key); +const getJSXAttributeValue = (path, state) => { + const valuePath = path.get('value'); + if (valuePath.isJSXElement()) { + return transform_vue_jsx_1.transformJSXElement(valuePath, state); + } + if (valuePath.isStringLiteral()) { + return valuePath.node; + } + if (valuePath.isJSXExpressionContainer()) { + return utils_1.transformJSXExpressionContainer(valuePath); + } + return null; +}; +const transformJSXSpreadAttribute = (nodePath, path, mergeProps, args) => { + const argument = path.get('argument'); + const { properties } = argument.node; + if (!properties) { + if (argument.isIdentifier()) { + utils_1.walksScope(nodePath, argument.name, 2 /* DYNAMIC */); + } + args.push(mergeProps ? argument.node : t.spreadElement(argument.node)); + } + else if (mergeProps) { + args.push(t.objectExpression(properties)); + } + else { + args.push(...properties); + } +}; +const mergeAsArray = (existing, incoming) => { + if (t.isArrayExpression(existing.value)) { + existing.value.elements.push(incoming.value); + } + else { + existing.value = t.arrayExpression([ + existing.value, + incoming.value, + ]); + } +}; +const dedupeProperties = (properties = [], mergeProps) => { + if (!mergeProps) { + return properties; + } + const knownProps = new Map(); + const deduped = []; + properties.forEach((prop) => { + const { value: name } = prop.key; + const existing = knownProps.get(name); + if (existing) { + if (name === 'style' || name === 'class' || name.startsWith('on')) { + mergeAsArray(existing, prop); + } + } + else { + knownProps.set(name, prop); + deduped.push(prop); + } + }); + return deduped; +}; +/** + * Check if an attribute value is constant + * @param node + * @returns boolean + */ +const isConstant = (node) => { + if (t.isIdentifier(node)) { + return node.name === 'undefined'; + } + if (t.isArrayExpression(node)) { + const { elements } = node; + return elements.every((element) => element && isConstant(element)); + } + if (t.isObjectExpression(node)) { + return node.properties.every((property) => isConstant(property.value)); + } + if (t.isLiteral(node)) { + return true; + } + return false; +}; +const buildProps = (path, state) => { + const tag = utils_1.getTag(path, state); + const isComponent = utils_1.checkIsComponent(path.get('openingElement'), state); + const props = path.get('openingElement').get('attributes'); + const directives = []; + const dynamicPropNames = new Set(); + let slots = null; + let patchFlag = 0; + if (props.length === 0) { + return { + tag, + isComponent, + slots, + props: t.nullLiteral(), + directives, + patchFlag, + dynamicPropNames, + }; + } + let properties = []; + // patchFlag analysis + let hasRef = false; + let hasClassBinding = false; + let hasStyleBinding = false; + let hasHydrationEventBinding = false; + let hasDynamicKeys = false; + const mergeArgs = []; + const { mergeProps = true } = state.opts; + props + .forEach((prop) => { + if (prop.isJSXAttribute()) { + let name = utils_1.getJSXAttributeName(prop); + const attributeValue = getJSXAttributeValue(prop, state); + if (!isConstant(attributeValue) || name === 'ref') { + if (!isComponent + && isOn(name) + // omit the flag for click handlers becaues hydration gives click + // dedicated fast path. + && name.toLowerCase() !== 'onclick' + // omit v-model handlers + && name !== 'onUpdate:modelValue') { + hasHydrationEventBinding = true; + } + if (name === 'ref') { + hasRef = true; + } + else if (name === 'class' && !isComponent) { + hasClassBinding = true; + } + else if (name === 'style' && !isComponent) { + hasStyleBinding = true; + } + else if (name !== 'key' + && !utils_1.isDirective(name) + && name !== 'on') { + dynamicPropNames.add(name); + } + } + if (state.opts.transformOn && (name === 'on' || name === 'nativeOn')) { + if (!state.get('transformOn')) { + state.set('transformOn', helper_module_imports_1.addDefault(path, '@vue/babel-helper-vue-transform-on', { nameHint: '_transformOn' })); + } + mergeArgs.push(t.callExpression(state.get('transformOn'), [attributeValue || t.booleanLiteral(true)])); + return; + } + if (utils_1.isDirective(name)) { + const { directive, modifiers, values, args, directiveName, } = parseDirectives_1.default({ + tag, + isComponent, + name, + path: prop, + state, + value: attributeValue, + }); + if (directiveName === 'slots') { + slots = attributeValue; + return; + } + if (directive) { + directives.push(t.arrayExpression(directive)); + } + else if (directiveName === 'html') { + properties.push(t.objectProperty(t.stringLiteral('innerHTML'), values[0])); + dynamicPropNames.add('innerHTML'); + } + else if (directiveName === 'text') { + properties.push(t.objectProperty(t.stringLiteral('textContent'), values[0])); + dynamicPropNames.add('textContent'); + } + if (['models', 'model'].includes(directiveName)) { + values.forEach((value, index) => { + var _a, _b; + const argVal = (_a = args[index]) === null || _a === void 0 ? void 0 : _a.value; + const propName = argVal || 'modelValue'; + // must be v-model or v-models and is a component + if (!directive) { + properties.push(t.objectProperty(t.stringLiteral(propName), value)); + dynamicPropNames.add(propName); + if ((_b = modifiers[index]) === null || _b === void 0 ? void 0 : _b.size) { + properties.push(t.objectProperty(t.stringLiteral(`${argVal || 'model'}Modifiers`), t.objectExpression([...modifiers[index]].map((modifier) => t.objectProperty(t.stringLiteral(modifier), t.booleanLiteral(true)))))); + } + } + properties.push(t.objectProperty(t.stringLiteral(`onUpdate:${propName}`), t.arrowFunctionExpression([t.identifier('$event')], t.assignmentExpression('=', value, t.identifier('$event'))))); + dynamicPropNames.add(`onUpdate:${propName}`); + }); + } + } + else { + if (name.match(xlinkRE)) { + name = name.replace(xlinkRE, (_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`); + } + properties.push(t.objectProperty(t.stringLiteral(name), attributeValue || t.booleanLiteral(true))); + } + } + else { + if (properties.length && mergeProps) { + mergeArgs.push(t.objectExpression(dedupeProperties(properties, mergeProps))); + properties = []; + } + // JSXSpreadAttribute + hasDynamicKeys = true; + transformJSXSpreadAttribute(path, prop, mergeProps, mergeProps ? mergeArgs : properties); + } + }); + // patchFlag analysis + if (hasDynamicKeys) { + patchFlag |= 16 /* FULL_PROPS */; + } + else { + if (hasClassBinding) { + patchFlag |= 2 /* CLASS */; + } + if (hasStyleBinding) { + patchFlag |= 4 /* STYLE */; + } + if (dynamicPropNames.size) { + patchFlag |= 8 /* PROPS */; + } + if (hasHydrationEventBinding) { + patchFlag |= 32 /* HYDRATE_EVENTS */; + } + } + if ((patchFlag === 0 || patchFlag === 32 /* HYDRATE_EVENTS */) + && (hasRef || directives.length > 0)) { + patchFlag |= 512 /* NEED_PATCH */; + } + let propsExpression = t.nullLiteral(); + if (mergeArgs.length) { + if (properties.length) { + mergeArgs.push(t.objectExpression(dedupeProperties(properties, mergeProps))); + } + if (mergeArgs.length > 1) { + propsExpression = t.callExpression(utils_1.createIdentifier(state, 'mergeProps'), mergeArgs); + } + else { + // single no need for a mergeProps call + propsExpression = mergeArgs[0]; + } + } + else if (properties.length) { + // single no need for spread + if (properties.length === 1 && t.isSpreadElement(properties[0])) { + propsExpression = properties[0].argument; + } + else { + propsExpression = t.objectExpression(dedupeProperties(properties, mergeProps)); + } + } + return { + tag, + props: propsExpression, + isComponent, + slots, + directives, + patchFlag, + dynamicPropNames, + }; +}; +exports.default = buildProps; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/helpers.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/helpers.js new file mode 100644 index 0000000000000000000000000000000000000000..929b49e25ad492bf2a328a7337e109867caf6ed2 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/helpers.js @@ -0,0 +1,17 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const template_1 = __importDefault(require("@babel/template")); +const helpers = Object.create(null); +const helper = (tpl) => ({ + ast: () => template_1.default.program.ast(tpl), +}); +helpers.isSlot = helper ` + import { isVNode } from 'vue'; + export default function _isSlot(s) { + return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s)); + } +`; +exports.default = helpers; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/index.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..1cde9e6a10a317020b7d2cd6d20f3b32c227b310 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/index.d.ts @@ -0,0 +1,38 @@ +import * as t from '@babel/types'; +import * as BabelCore from '@babel/core'; +import { NodePath } from '@babel/traverse'; +export declare type State = { + get: (name: string) => any; + set: (name: string, value: any) => any; + opts: VueJSXPluginOptions; +}; +export interface VueJSXPluginOptions { + /** transform `on: { click: xx }` to `onClick: xxx` */ + transformOn?: boolean; + /** enable optimization or not. */ + optimize?: boolean; + /** merge static and dynamic class / style attributes / onXXX handlers */ + mergeProps?: boolean; + /** configuring custom elements */ + isCustomElement?: (tag: string) => boolean; + /** enable object slots syntax */ + enableObjectSlots?: boolean; +} +export declare type ExcludesBoolean = (x: T | false | true) => x is T; +declare const _default: ({ types }: typeof BabelCore) => { + name: string; + inherits: any; + visitor: { + Program: { + enter(path: NodePath, state: State): void; + exit(path: NodePath): void; + }; + JSXFragment: { + enter(path: BabelCore.NodePath, state: State): void; + }; + JSXElement: { + exit(path: BabelCore.NodePath, state: State): void; + }; + }; +}; +export default _default; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/index.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d8fbbdcbe2ed3757f9093f392fdf16ad13f12835 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/index.js @@ -0,0 +1,145 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const t = __importStar(require("@babel/types")); +const template_1 = __importDefault(require("@babel/template")); +const plugin_syntax_jsx_1 = __importDefault(require("@babel/plugin-syntax-jsx")); +const helper_module_imports_1 = require("@babel/helper-module-imports"); +const transform_vue_jsx_1 = __importDefault(require("./transform-vue-jsx")); +const sugar_fragment_1 = __importDefault(require("./sugar-fragment")); +const hasJSX = (parentPath) => { + let fileHasJSX = false; + parentPath.traverse({ + JSXElement(path) { + fileHasJSX = true; + path.stop(); + }, + JSXFragment(path) { + fileHasJSX = true; + path.stop(); + }, + }); + return fileHasJSX; +}; +exports.default = ({ types }) => ({ + name: 'babel-plugin-jsx', + inherits: plugin_syntax_jsx_1.default, + visitor: Object.assign(Object.assign(Object.assign({}, transform_vue_jsx_1.default), sugar_fragment_1.default), { Program: { + enter(path, state) { + if (hasJSX(path)) { + const importNames = [ + 'createVNode', + 'Fragment', + 'resolveComponent', + 'withDirectives', + 'vShow', + 'vModelSelect', + 'vModelText', + 'vModelCheckbox', + 'vModelRadio', + 'vModelText', + 'vModelDynamic', + 'resolveDirective', + 'mergeProps', + 'createTextVNode', + 'isVNode', + ]; + if (helper_module_imports_1.isModule(path)) { + // import { createVNode } from "vue"; + const importMap = {}; + importNames.forEach((name) => { + state.set(name, () => { + if (importMap[name]) { + return types.cloneNode(importMap[name]); + } + const identifier = helper_module_imports_1.addNamed(path, name, 'vue', { + ensureLiveReference: true, + }); + importMap[name] = identifier; + return identifier; + }); + }); + const { enableObjectSlots = true } = state.opts; + if (enableObjectSlots) { + state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => { + if (importMap.runtimeIsSlot) { + return importMap.runtimeIsSlot; + } + const { name: isVNodeName } = state.get('isVNode')(); + const isSlot = path.scope.generateUidIdentifier('isSlot'); + const ast = template_1.default.ast ` + function ${isSlot.name}(s) { + return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s)); + } + `; + const lastImport = path.get('body').filter((p) => p.isImportDeclaration()).pop(); + if (lastImport) { + lastImport.insertAfter(ast); + } + importMap.runtimeIsSlot = isSlot; + return isSlot; + }); + } + } + else { + // var _vue = require('vue'); + let sourceName = ''; + importNames.forEach((name) => { + state.set(name, () => { + if (!sourceName) { + sourceName = helper_module_imports_1.addNamespace(path, 'vue', { + ensureLiveReference: true, + }).name; + } + return t.memberExpression(t.identifier(sourceName), t.identifier(name)); + }); + }); + } + } + }, + exit(path) { + const body = path.get('body'); + const specifiersMap = new Map(); + body.filter((nodePath) => t.isImportDeclaration(nodePath.node) + && nodePath.node.source.value === 'vue') + .forEach((nodePath) => { + const { specifiers } = nodePath.node; + let shouldRemove = false; + specifiers.forEach((specifier) => { + if (!specifier.loc && t.isImportSpecifier(specifier) && t.isIdentifier(specifier.imported)) { + specifiersMap.set(specifier.imported.name, specifier); + shouldRemove = true; + } + }); + if (shouldRemove) { + nodePath.remove(); + } + }); + const specifiers = [...specifiersMap.keys()].map((imported) => specifiersMap.get(imported)); + if (specifiers.length) { + path.unshiftContainer('body', t.importDeclaration(specifiers, t.stringLiteral('vue'))); + } + }, + } }), +}); diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/parseDirectives.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/parseDirectives.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..046471ce1a2604cc93eb559e4de7bbb6fd407f7f --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/parseDirectives.d.ts @@ -0,0 +1,19 @@ +import * as t from '@babel/types'; +import { NodePath } from '@babel/traverse'; +import { State } from '.'; +export declare type Tag = t.Identifier | t.MemberExpression | t.StringLiteral | t.CallExpression; +declare const parseDirectives: (params: { + name: string; + path: NodePath; + value: t.StringLiteral | t.Expression | null; + state: State; + tag: Tag; + isComponent: boolean; +}) => { + directiveName: string; + modifiers: Set[]; + values: (t.ArrayExpression | t.ArrowFunctionExpression | t.AssignmentExpression | t.AwaitExpression | t.BigIntLiteral | t.BinaryExpression | t.LogicalExpression | t.BindExpression | t.FunctionExpression | t.BooleanLiteral | t.CallExpression | t.ClassExpression | t.ConditionalExpression | t.DecimalLiteral | t.DoExpression | t.Identifier | t.StringLiteral | t.NumericLiteral | t.NullLiteral | t.RegExpLiteral | t.MemberExpression | t.NewExpression | t.ObjectExpression | t.SequenceExpression | t.ParenthesizedExpression | t.ThisExpression | t.UnaryExpression | t.UpdateExpression | t.MetaProperty | t.Super | t.TaggedTemplateExpression | t.TemplateLiteral | t.YieldExpression | t.Import | t.OptionalMemberExpression | t.OptionalCallExpression | t.TypeCastExpression | t.JSXElement | t.JSXFragment | t.PipelinePrimaryTopicReference | t.RecordExpression | t.TupleExpression | t.TSAsExpression | t.TSTypeAssertion | t.TSNonNullExpression | null)[]; + args: (t.StringLiteral | t.NullLiteral)[]; + directive: t.Expression[] | undefined; +}; +export default parseDirectives; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/parseDirectives.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/parseDirectives.js new file mode 100644 index 0000000000000000000000000000000000000000..4bf7d07a4dd3a75f7200448fd20a898598989037 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/parseDirectives.js @@ -0,0 +1,150 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const t = __importStar(require("@babel/types")); +const utils_1 = require("./utils"); +/** + * Get JSX element type + * + * @param path Path + */ +const getType = (path) => { + const typePath = path + .get('attributes') + .find((attribute) => { + if (!t.isJSXAttribute(attribute)) { + return false; + } + return t.isJSXIdentifier(attribute.get('name')) + && attribute.get('name').node.name === 'type'; + }); + return typePath ? typePath.get('value').node : null; +}; +const parseModifiers = (value) => (t.isArrayExpression(value) + ? value.elements + .map((el) => (t.isStringLiteral(el) ? el.value : '')) + .filter(Boolean) + : []); +const parseDirectives = (params) => { + var _a, _b, _c; + const { name, path, value, state, tag, isComponent, } = params; + const args = []; + const vals = []; + const modifiersSet = []; + const underscoreModifiers = name.split('_'); + const directiveName = ((_a = underscoreModifiers.shift()) === null || _a === void 0 ? void 0 : _a.replace(/^v/, '').replace(/^-/, '').replace(/^\S/, (s) => s.toLowerCase())) || ''; + const isVModels = directiveName === 'models'; + const isVModel = directiveName === 'model'; + if (isVModel && !t.isJSXExpressionContainer(path.get('value'))) { + throw new Error('You have to use JSX Expression inside your v-model'); + } + if (isVModels && !isComponent) { + throw new Error('v-models can only use in custom components'); + } + const shouldResolve = !['html', 'text', 'model', 'models'].includes(directiveName) + || (isVModel && !isComponent); + if (['models', 'model'].includes(directiveName)) { + if (t.isArrayExpression(value)) { + const elementsList = isVModels ? value.elements : [value]; + elementsList.forEach((element) => { + if (isVModels && !t.isArrayExpression(element)) { + throw new Error('You should pass a Two-dimensional Arrays to v-models'); + } + const { elements } = element; + const [first, second, third] = elements; + let modifiers = underscoreModifiers; + if (t.isStringLiteral(second)) { + args.push(second); + modifiers = parseModifiers(third); + } + else if (t.isArrayExpression(second)) { + args.push(t.nullLiteral()); + modifiers = parseModifiers(second); + } + else { + // work as v-model={[value]} or v-models={[[value]]} + args.push(t.nullLiteral()); + } + modifiersSet.push(new Set(modifiers)); + vals.push(first); + }); + } + else if (isVModel) { + // work as v-model={value} + args.push(t.nullLiteral()); + modifiersSet.push(new Set(underscoreModifiers)); + } + } + else { + modifiersSet.push(new Set(underscoreModifiers)); + } + return { + directiveName, + modifiers: modifiersSet, + values: vals.length ? vals : [value], + args, + directive: shouldResolve ? [ + resolveDirective(path, state, tag, directiveName), + vals[0] || value, + !!((_b = modifiersSet[0]) === null || _b === void 0 ? void 0 : _b.size) && t.unaryExpression('void', t.numericLiteral(0), true), + !!((_c = modifiersSet[0]) === null || _c === void 0 ? void 0 : _c.size) && t.objectExpression([...modifiersSet[0]].map((modifier) => t.objectProperty(t.identifier(modifier), t.booleanLiteral(true)))), + ].filter(Boolean) : undefined, + }; +}; +const resolveDirective = (path, state, tag, directiveName) => { + var _a; + if (directiveName === 'show') { + return utils_1.createIdentifier(state, 'vShow'); + } + if (directiveName === 'model') { + let modelToUse; + const type = getType(path.parentPath); + switch (tag.value) { + case 'select': + modelToUse = utils_1.createIdentifier(state, 'vModelSelect'); + break; + case 'textarea': + modelToUse = utils_1.createIdentifier(state, 'vModelText'); + break; + default: + if (t.isStringLiteral(type) || !type) { + switch ((_a = type) === null || _a === void 0 ? void 0 : _a.value) { + case 'checkbox': + modelToUse = utils_1.createIdentifier(state, 'vModelCheckbox'); + break; + case 'radio': + modelToUse = utils_1.createIdentifier(state, 'vModelRadio'); + break; + default: + modelToUse = utils_1.createIdentifier(state, 'vModelText'); + } + } + else { + modelToUse = utils_1.createIdentifier(state, 'vModelDynamic'); + } + } + return modelToUse; + } + return t.callExpression(utils_1.createIdentifier(state, 'resolveDirective'), [ + t.stringLiteral(directiveName), + ]); +}; +exports.default = parseDirectives; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/patchFlags.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/patchFlags.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..ba11ca7c60b6a083c8fa92644bbdab0339cb95f1 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/patchFlags.d.ts @@ -0,0 +1,18 @@ +export declare const enum PatchFlags { + TEXT = 1, + CLASS = 2, + STYLE = 4, + PROPS = 8, + FULL_PROPS = 16, + HYDRATE_EVENTS = 32, + STABLE_FRAGMENT = 64, + KEYED_FRAGMENT = 128, + UNKEYED_FRAGMENT = 256, + NEED_PATCH = 512, + DYNAMIC_SLOTS = 1024, + HOISTED = -1, + BAIL = -2 +} +export declare const PatchFlagNames: { + [x: number]: string; +}; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/patchFlags.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/patchFlags.js new file mode 100644 index 0000000000000000000000000000000000000000..e1b71b5e39346edc0b1fb04195923caae9ceeba9 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/patchFlags.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PatchFlagNames = void 0; +// dev only flag -> name mapping +exports.PatchFlagNames = { + [1 /* TEXT */]: 'TEXT', + [2 /* CLASS */]: 'CLASS', + [4 /* STYLE */]: 'STYLE', + [8 /* PROPS */]: 'PROPS', + [16 /* FULL_PROPS */]: 'FULL_PROPS', + [32 /* HYDRATE_EVENTS */]: 'HYDRATE_EVENTS', + [64 /* STABLE_FRAGMENT */]: 'STABLE_FRAGMENT', + [128 /* KEYED_FRAGMENT */]: 'KEYED_FRAGMENT', + [256 /* UNKEYED_FRAGMENT */]: 'UNKEYED_FRAGMENT', + [1024 /* DYNAMIC_SLOTS */]: 'DYNAMIC_SLOTS', + [512 /* NEED_PATCH */]: 'NEED_PATCH', + [-1 /* HOISTED */]: 'HOISTED', + [-2 /* BAIL */]: 'BAIL', +}; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/rumtime.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/rumtime.js new file mode 100644 index 0000000000000000000000000000000000000000..c8924fa698d62ae40b63c76f6aa44ceb06d186a3 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/rumtime.js @@ -0,0 +1,8 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isSlot = void 0; +const vue_1 = require("vue"); +const isFunction = (val) => typeof val === 'function'; +const isObject = (val) => Object.prototype.toString.call(val) === '[object Object]'; +const isSlot = (s) => isFunction(s) || (isObject(s) && !vue_1.isVNode(s)); +exports.isSlot = isSlot; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/slotFlags.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/slotFlags.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..986e87ef214d9f329dc4097298f146fd2824f367 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/slotFlags.d.ts @@ -0,0 +1,22 @@ +declare const enum SlotFlags { + /** + * Stable slots that only reference slot props or context state. The slot + * can fully capture its own dependencies so when passed down the parent won't + * need to force the child to update. + */ + STABLE = 1, + /** + * Slots that reference scope variables (v-for or an outer slot prop), or + * has conditional structure (v-if, v-for). The parent will need to force + * the child to update because the slot does not fully capture its dependencies. + */ + DYNAMIC = 2, + /** + * `` being forwarded into a child component. Whether the parent needs + * to update the child is dependent on what kind of slots the parent itself + * received. This has to be refined at runtime, when the child's vnode + * is being created (in `normalizeChildren`) + */ + FORWARDED = 3 +} +export default SlotFlags; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/slotFlags.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/slotFlags.js new file mode 100644 index 0000000000000000000000000000000000000000..c8ad2e549bdc6801e0d1c80b0308d4b9bd4985ce --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/slotFlags.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/sugar-fragment.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/sugar-fragment.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..acaac91dcf1bf05e54722f34a12a9b4c0d1031d2 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/sugar-fragment.d.ts @@ -0,0 +1,9 @@ +import * as t from '@babel/types'; +import { NodePath } from '@babel/traverse'; +import { State } from '.'; +declare const _default: { + JSXFragment: { + enter(path: NodePath, state: State): void; + }; +}; +export default _default; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/sugar-fragment.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/sugar-fragment.js new file mode 100644 index 0000000000000000000000000000000000000000..18f1eb177a89d0ae22a25893565e5bc03b458ff5 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/sugar-fragment.js @@ -0,0 +1,37 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const t = __importStar(require("@babel/types")); +const utils_1 = require("./utils"); +const transformFragment = (path, Fragment) => { + const children = path.get('children') || []; + return t.jsxElement(t.jsxOpeningElement(Fragment, []), t.jsxClosingElement(Fragment), children.map(({ node }) => node), false); +}; +exports.default = ({ + JSXFragment: { + enter(path, state) { + const fragmentCallee = utils_1.createIdentifier(state, utils_1.FRAGMENT); + path.replaceWith(transformFragment(path, t.isIdentifier(fragmentCallee) + ? t.jsxIdentifier(fragmentCallee.name) + : t.jsxMemberExpression(t.jsxIdentifier(fragmentCallee.object.name), t.jsxIdentifier(fragmentCallee.property.name)))); + }, + }, +}); diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/transform-vue-jsx.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/transform-vue-jsx.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..45a0c7b8fb61a2e174800941f8c39546377e2150 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/transform-vue-jsx.d.ts @@ -0,0 +1,11 @@ +import * as t from '@babel/types'; +import { NodePath } from '@babel/traverse'; +import { State } from '.'; +declare const transformJSXElement: (path: NodePath, state: State) => t.CallExpression; +export { transformJSXElement }; +declare const _default: { + JSXElement: { + exit(path: NodePath, state: State): void; + }; +}; +export default _default; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/transform-vue-jsx.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/transform-vue-jsx.js new file mode 100644 index 0000000000000000000000000000000000000000..66ee20296a35981860d2a97f0a1a6b2446ed7c79 --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/transform-vue-jsx.js @@ -0,0 +1,162 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.transformJSXElement = void 0; +const t = __importStar(require("@babel/types")); +const utils_1 = require("./utils"); +const buildProps_1 = __importDefault(require("./buildProps")); +/** + * Get children from Array of JSX children + * @param paths Array + * @returns Array + */ +const getChildren = (paths, state) => paths + .map((path) => { + if (path.isJSXText()) { + const transformedText = utils_1.transformJSXText(path); + if (transformedText) { + return t.callExpression(utils_1.createIdentifier(state, 'createTextVNode'), [transformedText]); + } + return transformedText; + } + if (path.isJSXExpressionContainer()) { + const expression = utils_1.transformJSXExpressionContainer(path); + if (t.isIdentifier(expression)) { + const { name } = expression; + const { referencePaths = [] } = path.scope.getBinding(name) || {}; + referencePaths.forEach((referencePath) => { + utils_1.walksScope(referencePath, name, 2 /* DYNAMIC */); + }); + } + return expression; + } + if (t.isJSXSpreadChild(path)) { + return utils_1.transformJSXSpreadChild(path); + } + if (path.isCallExpression()) { + return path.node; + } + if (path.isJSXElement()) { + return transformJSXElement(path, state); + } + throw new Error(`getChildren: ${path.type} is not supported`); +}).filter(((value) => (value !== undefined + && value !== null + && !t.isJSXEmptyExpression(value)))); +const transformJSXElement = (path, state) => { + const children = getChildren(path.get('children'), state); + const { tag, props, isComponent, directives, patchFlag, dynamicPropNames, slots, } = buildProps_1.default(path, state); + const { optimize = false } = state.opts; + const slotFlag = path.getData('slotFlag') || 1 /* STABLE */; + let VNodeChild; + if (children.length > 1 || slots) { + /* + {a}{b} + ---> {{ default: () => [a, b], ...slots }} + ---> {[a, b]} + */ + VNodeChild = isComponent ? t.objectExpression([ + !!children.length && t.objectProperty(t.identifier('default'), t.arrowFunctionExpression([], t.arrayExpression(utils_1.buildIIFE(path, children)))), + ...(slots ? (t.isObjectExpression(slots) + ? slots.properties + : [t.spreadElement(slots)]) : []), + optimize && t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)), + ].filter(Boolean)) : t.arrayExpression(children); + } + else if (children.length === 1) { + /* + {a} or {() => a} + */ + const { enableObjectSlots = true } = state.opts; + const child = children[0]; + const objectExpression = t.objectExpression([ + t.objectProperty(t.identifier('default'), t.arrowFunctionExpression([], t.arrayExpression(utils_1.buildIIFE(path, [child])))), + optimize && t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)), + ].filter(Boolean)); + if (t.isIdentifier(child) && isComponent) { + VNodeChild = enableObjectSlots ? t.conditionalExpression(t.callExpression(state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), [child]), child, objectExpression) : objectExpression; + } + else if (t.isCallExpression(child) && child.loc && isComponent) { // the element was generated and doesn't have location information + if (enableObjectSlots) { + const { scope } = path; + const slotId = scope.generateUidIdentifier('slot'); + if (scope) { + scope.push({ + id: slotId, + kind: 'let', + }); + } + const alternate = t.objectExpression([ + t.objectProperty(t.identifier('default'), t.arrowFunctionExpression([], t.arrayExpression(utils_1.buildIIFE(path, [slotId])))), + optimize && t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)), + ].filter(Boolean)); + const assignment = t.assignmentExpression('=', slotId, child); + const condition = t.callExpression(state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), [assignment]); + VNodeChild = t.conditionalExpression(condition, slotId, alternate); + } + else { + VNodeChild = objectExpression; + } + } + else if (t.isFunctionExpression(child) || t.isArrowFunctionExpression(child)) { + VNodeChild = t.objectExpression([ + t.objectProperty(t.identifier('default'), child), + ]); + } + else if (t.isObjectExpression(child)) { + VNodeChild = t.objectExpression([ + ...child.properties, + optimize && t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)), + ].filter(Boolean)); + } + else { + VNodeChild = isComponent ? t.objectExpression([ + t.objectProperty(t.identifier('default'), t.arrowFunctionExpression([], t.arrayExpression([child]))), + ]) : t.arrayExpression([child]); + } + } + const createVNode = t.callExpression(utils_1.createIdentifier(state, 'createVNode'), [ + tag, + props, + VNodeChild || t.nullLiteral(), + !!patchFlag && optimize && t.numericLiteral(patchFlag), + !!dynamicPropNames.size && optimize + && t.arrayExpression([...dynamicPropNames.keys()].map((name) => t.stringLiteral(name))), + ].filter(Boolean)); + if (!directives.length) { + return createVNode; + } + return t.callExpression(utils_1.createIdentifier(state, 'withDirectives'), [ + createVNode, + t.arrayExpression(directives), + ]); +}; +exports.transformJSXElement = transformJSXElement; +exports.default = ({ + JSXElement: { + exit(path, state) { + path.replaceWith(transformJSXElement(path, state)); + }, + }, +}); diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/utils.d.ts b/packages/uni-h5/lib/babel-plugin-jsx/dist/utils.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..861a33bca2b721f83d2de528e912f1f6aff00b0a --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/utils.d.ts @@ -0,0 +1,68 @@ +import * as t from '@babel/types'; +import { NodePath } from '@babel/traverse'; +import { State } from '.'; +import SlotFlags from './slotFlags'; +declare const JSX_HELPER_KEY = "JSX_HELPER_KEY"; +declare const FRAGMENT = "Fragment"; +/** + * create Identifier + * @param path NodePath + * @param state + * @param name string + * @returns MemberExpression + */ +declare const createIdentifier: (state: State, name: string) => t.Identifier | t.MemberExpression; +/** + * Checks if string is describing a directive + * @param src string + */ +declare const isDirective: (src: string) => boolean; +/** + * Should transformed to slots + * @param tag string + * @returns boolean + */ +declare const shouldTransformedToSlots: (tag: string) => boolean; +/** + * Check if a Node is a component + * + * @param t + * @param path JSXOpeningElement + * @returns boolean + */ +declare const checkIsComponent: (path: NodePath) => boolean; +/** + * Transform JSXMemberExpression to MemberExpression + * @param path JSXMemberExpression + * @returns MemberExpression + */ +declare const transformJSXMemberExpression: (path: NodePath) => t.MemberExpression; +/** + * Get tag (first attribute for h) from JSXOpeningElement + * @param path JSXElement + * @param state State + * @returns Identifier | StringLiteral | MemberExpression | CallExpression + */ +declare const getTag: (path: NodePath, state: State) => t.Identifier | t.CallExpression | t.StringLiteral | t.MemberExpression; +declare const getJSXAttributeName: (path: NodePath) => string; +/** + * Transform JSXText to StringLiteral + * @param path JSXText + * @returns StringLiteral | null + */ +declare const transformJSXText: (path: NodePath) => t.StringLiteral | null; +/** + * Transform JSXExpressionContainer to Expression + * @param path JSXExpressionContainer + * @returns Expression + */ +declare const transformJSXExpressionContainer: (path: NodePath) => (t.Expression); +/** + * Transform JSXSpreadChild + * @param path JSXSpreadChild + * @returns SpreadElement + */ +declare const transformJSXSpreadChild: (path: NodePath) => t.SpreadElement; +declare const walksScope: (path: NodePath, name: string, slotFlag: SlotFlags) => void; +declare const buildIIFE: (path: NodePath, children: t.Expression[]) => t.Expression[]; +export { createIdentifier, isDirective, checkIsComponent, transformJSXMemberExpression, getTag, getJSXAttributeName, transformJSXText, transformJSXSpreadChild, transformJSXExpressionContainer, shouldTransformedToSlots, FRAGMENT, walksScope, buildIIFE, JSX_HELPER_KEY, }; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/dist/utils.js b/packages/uni-h5/lib/babel-plugin-jsx/dist/utils.js new file mode 100644 index 0000000000000000000000000000000000000000..8e4e84f58ded7818e8c646335f909a2eaec6b35d --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/dist/utils.js @@ -0,0 +1,210 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.JSX_HELPER_KEY = exports.buildIIFE = exports.walksScope = exports.FRAGMENT = exports.shouldTransformedToSlots = exports.transformJSXExpressionContainer = exports.transformJSXSpreadChild = exports.transformJSXText = exports.getJSXAttributeName = exports.getTag = exports.transformJSXMemberExpression = exports.checkIsComponent = exports.isDirective = exports.createIdentifier = void 0; +const t = __importStar(require("@babel/types")); +const html_tags_1 = __importDefault(require("html-tags")); +const svg_tags_1 = __importDefault(require("svg-tags")); +const JSX_HELPER_KEY = 'JSX_HELPER_KEY'; +exports.JSX_HELPER_KEY = JSX_HELPER_KEY; +const FRAGMENT = 'Fragment'; +exports.FRAGMENT = FRAGMENT; +const KEEP_ALIVE = 'KeepAlive'; +/** + * create Identifier + * @param path NodePath + * @param state + * @param name string + * @returns MemberExpression + */ +const createIdentifier = (state, name) => state.get(name)(); +exports.createIdentifier = createIdentifier; +/** + * Checks if string is describing a directive + * @param src string + */ +const isDirective = (src) => src.startsWith('v-') + || (src.startsWith('v') && src.length >= 2 && src[1] >= 'A' && src[1] <= 'Z'); +exports.isDirective = isDirective; +/** + * Should transformed to slots + * @param tag string + * @returns boolean + */ +const shouldTransformedToSlots = (tag) => !(tag.endsWith(FRAGMENT) || tag === KEEP_ALIVE); +exports.shouldTransformedToSlots = shouldTransformedToSlots; +/** + * Check if a Node is a component + * + * @param t + * @param path JSXOpeningElement + * @returns boolean + */ +const checkIsComponent = (path, state) => { + const namePath = path.get('name'); + if (namePath.isJSXMemberExpression()) { + return shouldTransformedToSlots(namePath.node.property.name); // For withCtx + } + const tag = namePath.node.name; + if(state.opts.isCustomElement && state.opts.isCustomElement(tag)){ + return false + } + return shouldTransformedToSlots(tag) && !html_tags_1.default.includes(tag) && !svg_tags_1.default.includes(tag); +}; +exports.checkIsComponent = checkIsComponent; +/** + * Transform JSXMemberExpression to MemberExpression + * @param path JSXMemberExpression + * @returns MemberExpression + */ +const transformJSXMemberExpression = (path) => { + const objectPath = path.node.object; + const propertyPath = path.node.property; + const transformedObject = t.isJSXMemberExpression(objectPath) + ? transformJSXMemberExpression(path.get('object')) + : t.isJSXIdentifier(objectPath) + ? t.identifier(objectPath.name) + : t.nullLiteral(); + const transformedProperty = t.identifier(propertyPath.name); + return t.memberExpression(transformedObject, transformedProperty); +}; +exports.transformJSXMemberExpression = transformJSXMemberExpression; +/** + * Get tag (first attribute for h) from JSXOpeningElement + * @param path JSXElement + * @param state State + * @returns Identifier | StringLiteral | MemberExpression | CallExpression + */ +const getTag = (path, state) => { + var _a, _b; + const namePath = path.get('openingElement').get('name'); + if (namePath.isJSXIdentifier()) { + const { name } = namePath.node; + if (!html_tags_1.default.includes(name) && !svg_tags_1.default.includes(name)) { + return (name === FRAGMENT + ? createIdentifier(state, FRAGMENT) + : path.scope.hasBinding(name) + ? t.identifier(name) + : ((_b = (_a = state.opts).isCustomElement) === null || _b === void 0 ? void 0 : _b.call(_a, name)) ? t.stringLiteral(name) + : t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)])); + } + return t.stringLiteral(name); + } + if (namePath.isJSXMemberExpression()) { + return transformJSXMemberExpression(namePath); + } + throw new Error(`getTag: ${namePath.type} is not supported`); +}; +exports.getTag = getTag; +const getJSXAttributeName = (path) => { + const nameNode = path.node.name; + if (t.isJSXIdentifier(nameNode)) { + return nameNode.name; + } + return `${nameNode.namespace.name}:${nameNode.name.name}`; +}; +exports.getJSXAttributeName = getJSXAttributeName; +/** + * Transform JSXText to StringLiteral + * @param path JSXText + * @returns StringLiteral | null + */ +const transformJSXText = (path) => { + const { node } = path; + const lines = node.value.split(/\r\n|\n|\r/); + let lastNonEmptyLine = 0; + for (let i = 0; i < lines.length; i++) { + if (lines[i].match(/[^ \t]/)) { + lastNonEmptyLine = i; + } + } + let str = ''; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const isFirstLine = i === 0; + const isLastLine = i === lines.length - 1; + const isLastNonEmptyLine = i === lastNonEmptyLine; + // replace rendered whitespace tabs with spaces + let trimmedLine = line.replace(/\t/g, ' '); + // trim whitespace touching a newline + if (!isFirstLine) { + trimmedLine = trimmedLine.replace(/^[ ]+/, ''); + } + // trim whitespace touching an endline + if (!isLastLine) { + trimmedLine = trimmedLine.replace(/[ ]+$/, ''); + } + if (trimmedLine) { + if (!isLastNonEmptyLine) { + trimmedLine += ' '; + } + str += trimmedLine; + } + } + return str !== '' ? t.stringLiteral(str) : null; +}; +exports.transformJSXText = transformJSXText; +/** + * Transform JSXExpressionContainer to Expression + * @param path JSXExpressionContainer + * @returns Expression + */ +const transformJSXExpressionContainer = (path) => path.get('expression').node; +exports.transformJSXExpressionContainer = transformJSXExpressionContainer; +/** + * Transform JSXSpreadChild + * @param path JSXSpreadChild + * @returns SpreadElement + */ +const transformJSXSpreadChild = (path) => t.spreadElement(path.get('expression').node); +exports.transformJSXSpreadChild = transformJSXSpreadChild; +const walksScope = (path, name, slotFlag) => { + if (path.scope.hasBinding(name) && path.parentPath) { + if (t.isJSXElement(path.parentPath.node)) { + path.parentPath.setData('slotFlag', slotFlag); + } + walksScope(path.parentPath, name, slotFlag); + } +}; +exports.walksScope = walksScope; +const buildIIFE = (path, children) => { + const { parentPath } = path; + if (t.isAssignmentExpression(parentPath)) { + const { left } = parentPath.node; + if (t.isIdentifier(left)) { + return children.map((child) => { + if (t.isIdentifier(child) && child.name === left.name) { + const insertName = path.scope.generateUidIdentifier(child.name); + parentPath.insertBefore(t.variableDeclaration('const', [ + t.variableDeclarator(insertName, t.callExpression(t.functionExpression(null, [], t.blockStatement([t.returnStatement(child)])), [])), + ])); + return insertName; + } + return child; + }); + } + } + return children; +}; +exports.buildIIFE = buildIIFE; diff --git a/packages/uni-h5/lib/babel-plugin-jsx/package.json b/packages/uni-h5/lib/babel-plugin-jsx/package.json new file mode 100644 index 0000000000000000000000000000000000000000..d40be471b128b324ec09f53835b507629bd1ab5e --- /dev/null +++ b/packages/uni-h5/lib/babel-plugin-jsx/package.json @@ -0,0 +1,51 @@ +{ + "name": "@vue/babel-plugin-jsx", + "version": "1.0.3", + "description": "Babel plugin for Vue 3.0 JSX", + "author": "Amour1688 ", + "homepage": "https://github.com/vuejs/jsx-next/tree/dev/packages/babel-plugin-jsx#readme", + "license": "MIT", + "main": "dist/index.js", + "repository": { + "type": "git", + "url": "git+https://github.com/vuejs/jsx-next.git" + }, + "scripts": { + "build": "tsc", + "lint": "eslint 'src/*.ts'", + "test": "yarn build && jest --coverage", + "prepublishOnly": "yarn build" + }, + "bugs": { + "url": "https://github.com/vuejs/jsx-next/issues" + }, + "files": [ + "dist" + ], + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "@vue/babel-helper-vue-transform-on": "^1.0.2", + "camelcase": "^6.0.0", + "html-tags": "^3.1.0", + "svg-tags": "^1.0.0" + }, + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@types/jest": "^26.0.7", + "@types/svg-tags": "^1.0.0", + "@typescript-eslint/eslint-plugin": "^4.0.1", + "@typescript-eslint/parser": "^4.0.1", + "@vue/compiler-dom": "3.0.5", + "@vue/test-utils": "2.0.0-beta.2", + "jest": "^26.0.1", + "regenerator-runtime": "^0.13.5", + "ts-jest": "^26.1.3", + "typescript": "^4.0.2", + "vue": "3.0.5" + } +} diff --git a/packages/uni-h5/src/framework/components/app/index.ts b/packages/uni-h5/src/framework/components/app/index.tsx similarity index 73% rename from packages/uni-h5/src/framework/components/app/index.ts rename to packages/uni-h5/src/framework/components/app/index.tsx index dfcc43d503f90824ec615c1c3a9bebdb6dde8a3c..b3a940e8b0cbd3c28b201da948956f02f94f0e9d 100644 --- a/packages/uni-h5/src/framework/components/app/index.ts +++ b/packages/uni-h5/src/framework/components/app/index.tsx @@ -1,12 +1,4 @@ -import { - ref, - onMounted, - computed, - openBlock, - createBlock, - createVNode, - defineComponent, -} from 'vue' +import { ref, computed, onMounted, defineComponent } from 'vue' import Layout from './layout' @@ -28,25 +20,9 @@ export default defineComponent({ const { clazz, onChange } = useAppClass() return () => ( - openBlock(), - createBlock( - 'uni-app', - { - class: clazz.value, - }, - [ - createVNode( - Layout, - { - onChange, - }, - null, - 8 /* PROPS */, - ['onChange'] - ), - ], - 2 /* CLASS */ - ) + + + ) }, }) diff --git a/packages/uni-h5/src/framework/components/app/layout/index.ts b/packages/uni-h5/src/framework/components/app/layout/index.ts deleted file mode 100644 index 2218405e4b36dcd22228f558e7b62a5643d295eb..0000000000000000000000000000000000000000 --- a/packages/uni-h5/src/framework/components/app/layout/index.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { - vShow, - withCtx, - Fragment, - KeepAlive, - openBlock, - mergeProps, - createBlock, - createVNode, - withDirectives, - defineComponent, - resolveComponent, - ConcreteComponent, - createCommentVNode, - resolveDynamicComponent, -} from 'vue' - -import { RouteLocationNormalizedLoaded, RouterView, useRoute } from 'vue-router' - -import TabBar from '../tabBar' - -import { useKeepAliveRoute } from '../../../plugin/page' - -type KeepAliveRoute = ReturnType - -export default defineComponent({ - name: 'Layout', - emits: ['change'], - setup() { - const route = __UNI_FEATURE_TABBAR__ ? useRoute() : null - const keepAliveRoute = __UNI_FEATURE_PAGES__ ? useKeepAliveRoute() : null - const topWindow = __UNI_FEATURE_TOPWINDOW__ ? useTopWindow() : null - const leftWindow = __UNI_FEATURE_LEFTWINDOW__ ? useLeftWindow() : null - const rightWindow = __UNI_FEATURE_RIGHTWINDOW__ ? useRightWindow() : null - return () => { - return ( - openBlock(), - createBlock( - Fragment, - null, - [ - createLayoutVNode( - keepAliveRoute, - topWindow, - leftWindow, - rightWindow - ), - createTabBarVNode(route), - ], - 64 /* STABLE_FRAGMENT */ - ) - ) - } - }, -}) - -function createLayoutVNode( - keepAliveRoute: KeepAliveRoute | null, - topWindow: unknown, - leftWindow: unknown, - rightWindow: unknown -) { - const routerVNode = __UNI_FEATURE_PAGES__ - ? createRouterViewVNode(keepAliveRoute!) - : createPageVNode() - // 非响应式 - if (!__UNI_FEATURE_RESPONSIVE__) { - return routerVNode - } - const topWindowVNode = __UNI_FEATURE_TOPWINDOW__ - ? createTopWindowVNode(topWindow) - : createCommentVNode('', true) - const leftWindowVNode = __UNI_FEATURE_LEFTWINDOW__ - ? createLeftWindowVNode(leftWindow) - : createCommentVNode('', true) - const rightWindowVNode = __UNI_FEATURE_RIGHTWINDOW__ - ? createRightWindowVNode(rightWindow) - : createCommentVNode('', true) - return createVNode('uni-layout', null, [ - topWindowVNode, - createVNode('uni-content', null, [ - createVNode('uni-main', null, [routerVNode]), - leftWindowVNode, - rightWindowVNode, - ]), - ]) -} - -function createTabBarVNode(route: RouteLocationNormalizedLoaded | null) { - return __UNI_FEATURE_TABBAR__ - ? withDirectives(createVNode(TabBar, null, null, 512 /* NEED_PATCH */), [ - [vShow, route!.meta.isTabBar], // TODO mediaQuery and api - ]) - : createCommentVNode('', true) -} - -function createPageVNode() { - return createVNode(__uniRoutes[1].component) -} - -function createRouterViewVNode( - keepAliveRoute: ReturnType -) { - return createVNode(RouterView, null, { - default: withCtx(({ Component }) => [ - (openBlock(), - createBlock( - KeepAlive, - { cache: keepAliveRoute.routeCache }, - [ - (openBlock(), - createBlock(resolveDynamicComponent(Component), { - key: keepAliveRoute.routeKey.value, - })), - ], - 1032 /* PROPS, DYNAMIC_SLOTS */, - ['cache'] - )), - ]), - _: 1 /* STABLE */, - }) -} - -function useTopWindow() { - const component = resolveComponent('VUniTopWindow') as ConcreteComponent - return { - component, - style: (component as any).style, - height: 0, - show: false, - } -} -function useLeftWindow() { - const component = resolveComponent('VUniLeftWindow') as ConcreteComponent - return { - component, - style: (component as any).style, - height: 0, - } -} -function useRightWindow() { - const component = resolveComponent('VUniRightWindow') as ConcreteComponent - return { - component, - style: (component as any).style, - height: 0, - } -} - -function createTopWindowVNode(topWindow: unknown) { - if (!__UNI_FEATURE_TOPWINDOW__) { - return createCommentVNode('', true) - } - const { component, style, height, show } = useTopWindow() - return withDirectives( - createVNode( - 'uni-top-window', - null, - [ - createVNode( - 'div', - { - ref: 'topWindow', - class: 'uni-top-window', - style, - }, - [ - createVNode( - component, - mergeProps( - { - onVnodeMounted(vnode) { - // update style.offsetHeight - }, - 'navigation-bar-title-text': '', - } - //bindWindow - ), - null, - 16 /* FULL_PROPS */, - ['navigation-bar-title-text'] - ), - ], - 4 /* STYLE */ - ), - createVNode( - 'div', - { - class: 'uni-top-window--placeholder', - style: { height }, - }, - null, - 4 /* STYLE */ - ), - ], - 512 /* NEED_PATCH */ - ), - [[vShow, show]] - ) -} - -function createLeftWindowVNode(leftWindow: unknown) {} -function createRightWindowVNode(leftWindow: unknown) {} diff --git a/packages/uni-h5/src/framework/components/app/layout/index.tsx b/packages/uni-h5/src/framework/components/app/layout/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..5c5fb88bf510dc422adbb10587aab2fd4d8447b5 --- /dev/null +++ b/packages/uni-h5/src/framework/components/app/layout/index.tsx @@ -0,0 +1,141 @@ +import { + withCtx, + KeepAlive, + openBlock, + createBlock, + createVNode, + defineComponent, + resolveComponent, + ConcreteComponent, + resolveDynamicComponent, +} from 'vue' + +import { RouteLocationNormalizedLoaded, RouterView, useRoute } from 'vue-router' + +import TabBar from '../tabBar' + +import { useKeepAliveRoute } from '../../../plugin/page' + +type KeepAliveRoute = ReturnType + +export default defineComponent({ + name: 'Layout', + props: { + onChange: Function, + }, + emits: ['change'], + setup() { + const route = __UNI_FEATURE_TABBAR__ && useRoute() + const keepAliveRoute = __UNI_FEATURE_PAGES__ && useKeepAliveRoute() + const topWindow = __UNI_FEATURE_TOPWINDOW__ && useTopWindow() + const leftWindow = __UNI_FEATURE_LEFTWINDOW__ && useLeftWindow() + const rightWindow = __UNI_FEATURE_RIGHTWINDOW__ && useRightWindow() + return () => { + const layoutTsx = createLayoutTsx( + keepAliveRoute, + topWindow, + leftWindow, + rightWindow + ) + const tabBarTsx = + __UNI_FEATURE_TABBAR__ && + createTabBarTsx(route as RouteLocationNormalizedLoaded) + return [layoutTsx, tabBarTsx].filter(Boolean) + } + }, +}) + +function createLayoutTsx( + keepAliveRoute: KeepAliveRoute | false, + topWindow?: unknown, + leftWindow?: unknown, + rightWindow?: unknown +) { + const routerVNode = __UNI_FEATURE_PAGES__ + ? createRouterViewVNode(keepAliveRoute as KeepAliveRoute) + : createPageVNode() + // 非响应式 + if (!__UNI_FEATURE_RESPONSIVE__) { + return routerVNode + } + const topWindowTsx = __UNI_FEATURE_TOPWINDOW__ + ? createTopWindowTsx(topWindow) + : null + const leftWindowTsx = __UNI_FEATURE_LEFTWINDOW__ + ? createLeftWindowTsx(leftWindow) + : null + const rightWindowTsx = __UNI_FEATURE_RIGHTWINDOW__ + ? createRightWindowTsx(rightWindow) + : null + return ( + + {topWindowTsx} + + {routerVNode} + {leftWindowTsx} + {rightWindowTsx} + + + ) +} + +function createTabBarTsx(route: RouteLocationNormalizedLoaded) { + return +} + +function createPageVNode() { + return createVNode(__uniRoutes[1].component) +} + +function createRouterViewVNode( + keepAliveRoute: ReturnType +) { + return createVNode(RouterView, null, { + default: withCtx(({ Component }) => [ + (openBlock(), + createBlock( + KeepAlive, + { cache: keepAliveRoute.routeCache }, + [ + (openBlock(), + createBlock(resolveDynamicComponent(Component), { + key: keepAliveRoute.routeKey.value, + })), + ], + 1032 /* PROPS, DYNAMIC_SLOTS */, + ['cache'] + )), + ]), + _: 1 /* STABLE */, + }) +} + +function useTopWindow() { + const component = resolveComponent('VUniTopWindow') as ConcreteComponent + return { + component, + style: (component as any).style, + height: 0, + show: false, + } +} +function useLeftWindow() { + const component = resolveComponent('VUniLeftWindow') as ConcreteComponent + return { + component, + style: (component as any).style, + height: 0, + } +} +function useRightWindow() { + const component = resolveComponent('VUniRightWindow') as ConcreteComponent + return { + component, + style: (component as any).style, + height: 0, + } +} + +function createTopWindowTsx(topWindow: unknown) {} +function createLeftWindowTsx(leftWindow: unknown) {} +function createRightWindowTsx(leftWindow: unknown) {} diff --git a/packages/uni-h5/src/framework/components/page/pageHead.tsx b/packages/uni-h5/src/framework/components/page/pageHead.tsx index 432901a217ed8d67e34a40badc139361d8117d57..47a8cc77e6a93ef85d0a8fabdd518fbf1dbafbb5 100644 --- a/packages/uni-h5/src/framework/components/page/pageHead.tsx +++ b/packages/uni-h5/src/framework/components/page/pageHead.tsx @@ -1,15 +1,5 @@ -import { - computed, - createBlock, - // createCommentVNode, - // createVNode, - defineComponent, - openBlock, - // renderSlot, - // SetupContext, - // withCtx, -} from 'vue' - +import { computed, defineComponent } from 'vue' +import { isArray } from '@vue/shared' import { usePageMeta } from '../../plugin/provide' export default defineComponent({ @@ -19,11 +9,20 @@ export default defineComponent({ const navigationBar = pageMeta.navigationBar UniServiceJSBridge.emit('onNavigationBarChange', navigationBar.titleText) const { clazz, style } = usePageHead(navigationBar) - const backButtonJsx = createBackButtonJsx(navigationBar) + // 单页面无需back按钮 + const backButtonJsx = __UNI_FEATURE_PAGES__ + ? createBackButtonJsx(navigationBar) + : null + const leftButtonsJsx = __UNI_FEATURE_NAVIGATIONBAR_BUTTONS__ + ? createButtonsJsx('left', navigationBar) + : [] return () => (
-
{backButtonJsx}
+
+ {backButtonJsx} + {...leftButtonsJsx} +
) @@ -34,7 +33,7 @@ function createBackButtonJsx(navigationBar: UniApp.PageNavigationBar) { if (navigationBar.backButton) { return (
- +
@@ -42,6 +41,22 @@ function createBackButtonJsx(navigationBar: UniApp.PageNavigationBar) { } } +function createButtonsJsx( + float: 'left' | 'right', + navigationBar: UniApp.PageNavigationBar +) { + if (isArray(navigationBar.buttons)) { + return navigationBar.buttons + .filter((btn) => btn.float === float) + .map((btn, index) => ( +
+ +
+ )) + } + return [] +} + function usePageHead(navigationBar: UniApp.PageNavigationBar) { const clazz = computed(() => { const { type, titlePenetrate, shadowColorType } = navigationBar diff --git a/packages/uni-h5/vite.config.ts b/packages/uni-h5/vite.config.ts index 7c04cdd1600de184a8bc203bbfa22560dec4d17c..65e6b97521187a1b28a5deb0aff691568479c69c 100644 --- a/packages/uni-h5/vite.config.ts +++ b/packages/uni-h5/vite.config.ts @@ -2,11 +2,17 @@ import path from 'path' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' -import vueJsx from '@vitejs/plugin-vue-jsx' import replace from '@rollup/plugin-replace' import { isCustomElement } from '../uni-shared' +const moduleAlias = require('module-alias') +moduleAlias.addAlias( + '@vue/babel-plugin-jsx', + path.join(__dirname, 'lib/babel-plugin-jsx') +) +const vueJsx = require('@vitejs/plugin-vue-jsx') + function resolve(file: string) { return path.resolve(__dirname, file) } diff --git a/packages/vite-plugin-uni/src/handleHotUpdate/index.ts b/packages/vite-plugin-uni/src/handleHotUpdate/index.ts index be058530aad435297e5dd2facb908556e7f3c333..b6a1aadcd69cccaa51cd98143466db63c6f8f6f1 100644 --- a/packages/vite-plugin-uni/src/handleHotUpdate/index.ts +++ b/packages/vite-plugin-uni/src/handleHotUpdate/index.ts @@ -8,16 +8,24 @@ import { getFeatures } from '../utils' const debugHmr = debug('uni:hmr') async function invalidate(file: string, moduleGraph: ModuleGraph) { - const mod = await moduleGraph.getModuleById(slash(file)) - if (mod) { - debugHmr('invalidate', mod.id) - moduleGraph.invalidateModule(mod) + const mods = await moduleGraph.getModulesByFile(slash(file)) + if (mods && mods.size) { + ;[...mods].forEach((mod) => { + debugHmr('invalidate', mod.id) + moduleGraph.invalidateModule(mod) + }) } } export function createHandleHotUpdate( options: VitePluginUniResolvedOptions ): Plugin['handleHotUpdate'] { + const invalidateFiles = [ + path.resolve(options.inputDir, 'pages.json.js'), + path.resolve(options.inputDir, 'manifest.json.js'), + require.resolve('@dcloudio/uni-h5/dist/uni-h5.esm.js'), + require.resolve('vite/dist/client/env.js'), + ] return async function ({ file, server }) { // TODO 目前简单处理,当pages.json,manifest.json发生变化,就直接刷新,理想情况下,应该区分变化的内容,仅必要时做整页面刷新 const isPagesJson = file.endsWith('pages.json') @@ -36,15 +44,8 @@ export function createHandleHotUpdate( ) debugHmr('define', server.config.define) // 当pages.json,manifest.json发生变化时,作废pages.json.js缓存 - await invalidate( - path.resolve(options.inputDir, 'pages.json.js'), - server.moduleGraph - ) - if (isManifestJson) { - await invalidate( - path.resolve(options.inputDir, 'manifest.json.js'), - server.moduleGraph - ) + for (const file of invalidateFiles) { + await invalidate(file, server.moduleGraph) } return [] } diff --git a/packages/vite-plugin-uni/src/utils/define.ts b/packages/vite-plugin-uni/src/utils/define.ts index 1265a2b6f8eef598c0aed0f86d7b0427512addab..d5f875da5f817b972f3bacb45b9dc3cc2d28833e 100644 --- a/packages/vite-plugin-uni/src/utils/define.ts +++ b/packages/vite-plugin-uni/src/utils/define.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import { ConfigEnv } from 'vite' import { parse } from 'jsonc-parser' - +import { isArray } from '@vue/shared' import { VitePluginUniResolvedOptions } from '..' import { normalizePagesJson } from './pagesJson' @@ -15,6 +15,7 @@ interface PagesFeatures { leftWindow: boolean rightWindow: boolean pullDownRefresh: boolean + navigationBarButtons: boolean } interface ManifestFeatures { wx: boolean @@ -46,6 +47,7 @@ function resolvePagesFeature( leftWindow: false, // 是否启用leftWindow rightWindow: false, // 是否启用rightWindow pullDownRefresh: false, // 是否启用下拉刷新 + navigationBarButtons: true, // 是否启用标题按钮 } const { @@ -89,6 +91,15 @@ function resolvePagesFeature( ) { features.nvue = false } + if ( + !pages.find( + (page) => + isArray(page.style.navigationBar.buttons) && + page.style.navigationBar.buttons.length + ) + ) { + features.navigationBarButtons = false + } } return features @@ -137,6 +148,7 @@ export function getFeatures( leftWindow, rightWindow, pullDownRefresh, + navigationBarButtons, } = Object.assign( resolveManifestFeature(options), resolvePagesFeature(options, command), @@ -156,5 +168,6 @@ export function getFeatures( __UNI_FEATURE_RIGHTWINDOW__: rightWindow, __UNI_FEATURE_RESPONSIVE__: topWindow || leftWindow || rightWindow, __UNI_FEATURE_PULL_DOWN_REFRESH__: pullDownRefresh, + __UNI_FEATURE_NAVIGATIONBAR_BUTTONS__: navigationBarButtons, } } diff --git a/packages/vite-plugin-uni/src/utils/pagesJson.ts b/packages/vite-plugin-uni/src/utils/pagesJson.ts index ff3b7cb04fdf9be081d77a70b936ce171dfe4514..004ca9cdda74670cbaa747de42096bedbe1990f8 100644 --- a/packages/vite-plugin-uni/src/utils/pagesJson.ts +++ b/packages/vite-plugin-uni/src/utils/pagesJson.ts @@ -55,7 +55,7 @@ function normalizeSubpackages( if (Array.isArray(subpackages)) { subpackages.forEach(({ root, pages: subPages }) => { if (root && subPages.length) { - subPages.forEach((subPage: { path: string }) => { + subPages.forEach((subPage) => { subPage.path = slash(path.join(root, subPage.path)) pages.push(subPage) }) @@ -66,7 +66,7 @@ function normalizeSubpackages( } function normalizePageStyle( - pageStyle: Record, + pageStyle: UniApp.PagesJsonPageStyle, platform: UniApp.PLATFORM ) { if (pageStyle) { @@ -118,10 +118,10 @@ function normalizeNavigationBar( const platforms = ['h5', 'app-plus', 'mp-', 'quickapp'] -function removePlatformStyle(pageStyle: Record) { +function removePlatformStyle(pageStyle: UniApp.PagesJsonPageStyle) { Object.keys(pageStyle).forEach((name) => { if (platforms.find((prefix) => name.startsWith(prefix))) { - delete pageStyle[name] + delete pageStyle[name as UniApp.PLATFORM] } }) return pageStyle diff --git a/yarn.lock b/yarn.lock index 7c8234212ba4c2a4c72997150a1085c11bf3d6cf..dcb50505d96f9803213580e2820e1a05c584e0c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -780,9 +780,9 @@ integrity sha512-XI3MGSejUQIJ3wzY0i5IHy5J3eb36M/ytgG8jIOssP08ovtRPcjpjXQqrx51AHBNBOisTS/NQNWJitI17+EwzQ== "@types/estree@*": - version "0.0.46" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe" - integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg== + version "0.0.47" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4" + integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg== "@types/estree@0.0.39": version "0.0.39" @@ -4308,6 +4308,11 @@ mkdirp@1.x: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +module-alias@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0" + integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"