diff --git a/packages/uni-cli-shared/package.json b/packages/uni-cli-shared/package.json index a1b8073a2d09b11544044818c7e7b6a03d4dfd61..15b62f6cb9d0d72dc26ab077d06e0111eeb02a27 100644 --- a/packages/uni-cli-shared/package.json +++ b/packages/uni-cli-shared/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/uni-cli-shared", - "version": "0.2.991", + "version": "0.2.993", "description": "uni-cli-shared", "main": "lib/index.js", "files": [ diff --git a/packages/vue-cli-plugin-uni/lib/h5/compiler-options.js b/packages/vue-cli-plugin-uni/lib/h5/compiler-options.js index 22f99054dfe4ba40f97e15cd7065b2a7f4b88cac..9318fe6233d5bbb3e60343af4e26572042ab54e5 100644 --- a/packages/vue-cli-plugin-uni/lib/h5/compiler-options.js +++ b/packages/vue-cli-plugin-uni/lib/h5/compiler-options.js @@ -4,18 +4,25 @@ const { const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/ -function processEvent (expr) { +function processEvent(expr, filterModules) { const isMethodPath = simplePathRE.test(expr) if (isMethodPath) { - expr = expr + '($event)' + if (filterModules.find(name => expr.indexOf(name + '.') === 0)) { + return ` +$event = $handleWxsEvent($event); +${expr}($event, $getComponentDescriptor()) +` + } else { + expr = expr + '($event)' + } } return ` -$event = $handleEvent($event); +$event = $handleEvent($event); ${expr} ` } -function hasOwn (obj, key) { +function hasOwn(obj, key) { return hasOwnProperty.call(obj, key) } @@ -26,7 +33,7 @@ const deprecated = { } } -function addTag (tag) { +function addTag(tag) { if (!process.UNI_TAGS) { process.UNI_TAGS = new Set() } @@ -36,7 +43,7 @@ function addTag (tag) { module.exports = { preserveWhitespace: false, modules: [require('../format-text'), { - preTransformNode (el, { + preTransformNode(el, { warn }) { if (el.tag.indexOf('v-uni-') === 0) { @@ -46,8 +53,9 @@ module.exports = { el.tag = 'v-uni-' + el.tag } }, - postTransformNode (el, { - warn + postTransformNode(el, { + warn, + filterModules }) { if (el.tag === 'block') { el.tag = 'template' @@ -65,6 +73,7 @@ module.exports = { } } if (el.events) { + filterModules = filterModules || [] const { events: eventsMap } = deprecated @@ -81,11 +90,10 @@ module.exports = { const handlers = el.events[name] if (Array.isArray(handlers)) { handlers.forEach(handler => { - handler.value = processEvent( - handler.value) + handler.value = processEvent(handler.value, filterModules) }) } else { - handlers.value = processEvent(handlers.value) + handlers.value = processEvent(handlers.value, filterModules) } }) } diff --git a/packages/vue-cli-plugin-uni/lib/h5/index.js b/packages/vue-cli-plugin-uni/lib/h5/index.js index 28683ab11eff186ff133e2ff222c75775f810eb6..b29194012e6a9f4e622ed4a4b9040484d3aa9e2d 100644 --- a/packages/vue-cli-plugin-uni/lib/h5/index.js +++ b/packages/vue-cli-plugin-uni/lib/h5/index.js @@ -10,7 +10,7 @@ const { const WebpackHtmlAppendPlugin = require('../../packages/webpack-html-append-plugin') -function resolve (dir) { +function resolve(dir) { return path.resolve(__dirname, '../../', dir) } @@ -61,7 +61,7 @@ if (devServer && Object.keys(devServer).length) { module.exports = { vueConfig, - webpackConfig (webpackConfig) { + webpackConfig(webpackConfig) { return { devtool: process.env.NODE_ENV === 'production' ? false : 'source-map', resolve: { @@ -80,6 +80,11 @@ module.exports = { before: [``] } } + }, { + resourceQuery: /vue&type=template/, + use: [{ + loader: resolve('packages/h5-vue-template-loader') + }] }, { resourceQuery: /blockType=wxs/, use: [{ @@ -95,7 +100,7 @@ module.exports = { plugins } }, - chainWebpack (webpackConfig) { + chainWebpack(webpackConfig) { webpackConfig.plugins.delete('copy') if (!process.env.UNI_OPT_PREFETCH) { diff --git a/packages/vue-cli-plugin-uni/package.json b/packages/vue-cli-plugin-uni/package.json index b4e067a96e2d2293585184e891008f0140f32fd3..c7c984326d06e7bc864ea57369a61b5e0255b31d 100644 --- a/packages/vue-cli-plugin-uni/package.json +++ b/packages/vue-cli-plugin-uni/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/vue-cli-plugin-uni", - "version": "0.9.527", + "version": "0.9.528", "description": "uni-app plugin for vue-cli 3", "main": "index.js", "scripts": { diff --git a/packages/vue-cli-plugin-uni/packages/h5-vue-template-loader/index.js b/packages/vue-cli-plugin-uni/packages/h5-vue-template-loader/index.js new file mode 100644 index 0000000000000000000000000000000000000000..27e9fd2d6b34e6b5ee0441807d3f126aa84ad34d --- /dev/null +++ b/packages/vue-cli-plugin-uni/packages/h5-vue-template-loader/index.js @@ -0,0 +1,20 @@ +const path = require('path') + +const loaderUtils = require('loader-utils') + +module.exports = function(content) { + this.cacheable && this.cacheable() + + const vueLoaderOptions = this.loaders[0] + if (vueLoaderOptions.ident === 'vue-loader-options') { + const params = loaderUtils.parseQuery(this.resourceQuery) + /* eslint-disable no-mixed-operators */ + const filterModules = JSON.parse(params && params['filter-modules'] || '{}') + Object.assign(vueLoaderOptions.options.compilerOptions, { + filterModules: Object.keys(filterModules) + }) + } else { + throw new Error('vue-loader-options parse error') + } + return content +} diff --git a/packages/webpack-uni-pages-loader/package.json b/packages/webpack-uni-pages-loader/package.json index 0675408df3aeef16db8422accd6237769b91b65f..9b857ea4b8a5814bd8d64f8d6ee4cdea0e6da463 100644 --- a/packages/webpack-uni-pages-loader/package.json +++ b/packages/webpack-uni-pages-loader/package.json @@ -1,6 +1,6 @@ { "name": "@dcloudio/webpack-uni-pages-loader", - "version": "0.2.874", + "version": "0.2.875", "description": "uni-app pages.json loader", "main": "lib/index.js", "files": [ @@ -16,7 +16,7 @@ "strip-json-comments": "^2.0.1" }, "uni-app": { - "compilerVersion": "2.2.3" + "compilerVersion": "2.2.4" }, "gitHead": "08ea04b669e93f0db3acb2dfa38138298edd5789" } diff --git a/src/core/view/plugins/index.js b/src/core/view/plugins/index.js index 98db0d0cb4f6ffdce273fd6633d9b7fc1076ef2c..0660875e1e9c3abd9e56c5bac9f46103af3c4ba9 100644 --- a/src/core/view/plugins/index.js +++ b/src/core/view/plugins/index.js @@ -9,6 +9,10 @@ import { import initBehaviors from './behaviors' +import { + createComponentDescriptor +} from './wxs/component-descriptor' + function pageMounted () { // 通知 Service,View 层已 ready UniViewJSBridge.publishHandler('onPageReady', {}, this.$page.id) @@ -25,17 +29,36 @@ export default { } = {}) { initEvents() + const findUniTarget = function ($event, $el) { + let target = $event.target + for (; target && target !== $el; target = target.parentNode) { + if (target.tagName && target.tagName.indexOf('UNI-') === 0) { + break + } + } + return target + } + Vue.prototype.$handleEvent = function ($event) { if ($event instanceof Event) { // 未处理的 event 对象 需要对 target 校正及包装 // 查找 uniTarget - let target = $event.target - const $el = this.$el - for (; target && target !== $el; target = target.parentNode) { - if (target.tagName && target.tagName.indexOf('UNI-') === 0) { - break - } - } + const target = findUniTarget($event, this.$el) + $event = processEvent.call(this, $event.type, $event, {}, target || $event.target, $event.currentTarget) + } + return $event + } + + Vue.prototype.$getComponentDescriptor = function (vm) { + return createComponentDescriptor(vm || this) + } + + Vue.prototype.$handleWxsEvent = function ($event) { + if ($event instanceof Event) { // 未处理的 event 对象 需要对 target 校正及包装 + // 查找 uniTarget + const target = findUniTarget($event, this.$el) + const instance = target && target.__vue__ && target.__vue__.$getComponentDescriptor() $event = processEvent.call(this, $event.type, $event, {}, target || $event.target, $event.currentTarget) + $event.instance = instance } return $event } @@ -43,13 +66,13 @@ export default { Vue.mixin({ beforeCreate () { const options = this.$options - - const wxs = options.wxs - if (wxs) { - Object.keys(wxs).forEach(module => { - this[module] = wxs[module] - }) - } + + const wxs = options.wxs + if (wxs) { + Object.keys(wxs).forEach(module => { + this[module] = wxs[module] + }) + } if (options.behaviors && options.behaviors.length) { initBehaviors(options, this) diff --git a/src/core/view/plugins/wxs/component-descriptor.js b/src/core/view/plugins/wxs/component-descriptor.js new file mode 100644 index 0000000000000000000000000000000000000000..7cdab334c1b5c99866254db0a4fa9079881404ad --- /dev/null +++ b/src/core/view/plugins/wxs/component-descriptor.js @@ -0,0 +1,151 @@ +import { + isPlainObject +} from 'uni-shared' + +const CLASS_RE = /^\s+|\s+$/g +const WXS_CLASS_RE = /\s+/ + +function getWxsClsArr (clsArr, classList, isAdd) { + const wxsClsArr = [] + + let checkClassList = function (cls) { + if (isAdd) { + checkClassList = function (cls) { + return !classList.contains(cls) + } + } else { + checkClassList = function (cls) { + return classList.contains(cls) + } + } + return checkClassList(cls) + } + + clsArr.forEach(cls => { + cls = cls.replace(CLASS_RE, '') + checkClassList(cls) && wxsClsArr.push(cls) + }) + return wxsClsArr +} + +function parseStyleText (cssText) { + const res = {} + const listDelimiter = /;(?![^(]*\))/g + const propertyDelimiter = /:(.+)/ + cssText.split(listDelimiter).forEach(function (item) { + if (item) { + const tmp = item.split(propertyDelimiter) + tmp.length > 1 && (res[tmp[0].trim()] = tmp[1].trim()) + } + }) + return res +} + +class ComponentDescriptor { + constructor (vm) { + this.$vm = vm + this.$el = vm.$el + } + + selectComponent (selector) { + if (!this.$el || !selector) { + return + } + const el = this.$el.querySelector(selector) + return el && el.__vue__ && createComponentDescriptor(el.__vue__) + } + + selectAllComponents (selector) { + if (!this.$el || !selector) { + return [] + } + const descriptors = [] + this.$el.querySelectorAll(selector).forEach(el => { + el.__vue__ && descriptors.push(createComponentDescriptor(el.__vue__)) + }) + return descriptors + } + + setStyle (style) { + if (!this.$el || !style) { + return this + } + if (typeof style === 'string') { + style = parseStyleText(style) + } + if (isPlainObject(style)) { + this.$el.__wxsStyle = style + } + return this + } + + addClass (...clsArr) { + if (!this.$el || !clsArr.length) { + return this + } + + const wxsClsArr = getWxsClsArr(clsArr, this.$el.classList, true) + if (wxsClsArr.length) { + const wxsClass = this.$el.__wxsClass || '' + this.$el.__wxsClass = wxsClass + (wxsClass ? ' ' : '') + wxsClsArr.join(' ') + this.$vm.$forceUpdate() + } + + return this + } + + removeClass (...clsArr) { + if (!this.$el || !clsArr.length) { + return this + } + const oldWxsClsArr = (this.$el.__wxsClass || '').split(WXS_CLASS_RE) + const wxsClsArr = getWxsClsArr(clsArr, this.$el.classList, false) + if (wxsClsArr.length) { + oldWxsClsArr.length && wxsClsArr.forEach(cls => { + const clsIndex = oldWxsClsArr.findIndex(oldCls => oldCls === cls) + if (clsIndex !== -1) { + oldWxsClsArr.splice(clsIndex, 1) + } + }) + this.$el.__wxsClass = oldWxsClsArr.join(' ') + this.$vm.$forceUpdate() + } + + return this + } + + hasClass (cls) { + return this.$el && this.$el.classList.contains(cls) + } + + getDataset () { + return this.$el && this.$el.dataset + } + + callMethod (funcName, args = {}) { + // TODO 需跨平台 + return (this.$vm[funcName] && this.$vm[funcName](JSON.parse(JSON.stringify(args))), this) + } + + requestAnimationFrame (callback) { + return (global.requestAnimationFrame(callback), this) + } + + getState () { + return this.$el && (this.$el.__wxsState || (this.$el.__wxsState = {})) + } + + triggerEvent (eventName, detail = {}, options = {}) { + // TODO options + return (this.$vm.$emit(eventName, detail), this) + } +} + +export function createComponentDescriptor (vm) { + if (vm && vm.$el) { + if (!vm.$el.__wxsComponentDescriptor) { + vm.$el.__wxsComponentDescriptor = new ComponentDescriptor(vm) + } + return vm.$el.__wxsComponentDescriptor + } +}