Repository: vuejs/vue
Pull request: feat(weex): implement "weex.supports" api to support feature detection

<!--
Please make sure to read the Pull Request Guidelines:
https://github.com/vuejs/vue/blob/dev/.github/CONTRIBUTING.md#pull-request-guidelines
-->

<!-- PULL REQUEST TEMPLATE -->
<!-- (Update "[ ]" to "[x]" to check a box) -->

**What kind of change does this PR introduce?**

- [ ] Bugfix
- [x] Feature
- [ ] Code style update
- [ ] Refactor
- [ ] Build-related changes
- [ ] Other, please describe:

**Does this PR introduce a breaking change?**

- [ ] Yes
- [x] No

**Other information:**

## `weex.supports`

`weex.supports` is used to detect whether a feature is supported in the current environment.

### API

```js
weex.supports(condition: String): Boolean | Null
```

#### Parameter

+ a formatted string: `@{type}/{name}`.

The `type` must be "component" or "module", the `name` can be tag name, module name or the method name in a specific module.

#### Return Value

+ if supported, returns `true`.
+ if unsupported, returns `false`.
+ if unclear, returns `null`.

### Examples

```js
// Detects whether the specific component is supported
weex.supports('@component/slider') // true
weex.supports('@component/my-tab') // false

// Detects whether the specific module is supported
weex.supports('@module/stream')  // true
weex.supports('@module/abcdef')  // false

// Detects whether the method in specific module is supported
weex.supports('@module/dom.getComponentRect') // true
weex.supports('@module/navigator.jumpToPage') // false

// invalid input
weex.supports('div') // null
weex.supports('module/*') // null
weex.supports('@stream/fetch') // null
weex.supports('getComponentRect') // null
```

Changed files:
filename: src/platforms/weex/entry-framework.js
patch: @@ -79,6 +79,7 @@ export function createInstance (
   const weexInstanceVar = {
     config,
     document,
+    supports,
     requireModule: moduleGetter
   }
   Object.freeze(weexInstanceVar)
@@ -216,6 +217,18 @@ export function registerModules (newModules) {
   }
 }
 
+/**
+ * Check whether the module or the method has been registered.
+ * @param {String} module name
+ * @param {String} method name (optional)
+ */
+export function isRegisteredModule (name, method) {
+  if (typeof method === 'string') {
+    return !!(modules[name] && modules[name][method])
+  }
+  return !!modules[name]
+}
+
 /**
  * Register native components information.
  * @param {array} newComponents
@@ -235,6 +248,35 @@ export function registerComponents (newComponents) {
   }
 }
 
+/**
+ * Check whether the component has been registered.
+ * @param {String} component name
+ */
+export function isRegisteredComponent (name) {
+  return !!components[name]
+}
+
+/**
+ * Detects whether Weex supports specific features.
+ * @param {String} condition
+ */
+export function supports (condition) {
+  if (typeof condition !== 'string') return null
+
+  const res = condition.match(/^@(\w+)\/(\w+)(\.(\w+))?$/i)
+  if (res) {
+    const type = res[1]
+    const name = res[2]
+    const method = res[4]
+    switch (type) {
+      case 'module': return isRegisteredModule(name, method)
+      case 'component': return isRegisteredComponent(name)
+    }
+  }
+
+  return null
+}
+
 /**
  * Create a fresh instance of Vue for each Weex instance.
  */

filename: test/weex/runtime/framework.spec.js
patch: @@ -409,6 +409,30 @@ describe('framework APIs', () => {
     ).toEqual(['b(1)'])
   })
 
+  it('isRegisteredModule', () => {
+    framework.registerModules({
+      foo: ['a', 'b'],
+      bar: [
+        { name: 'x', args: ['string'] },
+        { name: 'y', args: ['number'] }
+      ]
+    })
+    expect(framework.isRegisteredModule('foo')).toBe(true)
+    expect(framework.isRegisteredModule('bar')).toBe(true)
+    expect(framework.isRegisteredModule('foo', 'a')).toBe(true)
+    expect(framework.isRegisteredModule('foo', 'b')).toBe(true)
+    expect(framework.isRegisteredModule('bar', 'x')).toBe(true)
+    expect(framework.isRegisteredModule('bar', 'y')).toBe(true)
+    expect(framework.isRegisteredModule('FOO')).toBe(false)
+    expect(framework.isRegisteredModule(' bar ')).toBe(false)
+    expect(framework.isRegisteredModule('unknown')).toBe(false)
+    expect(framework.isRegisteredModule('#}{)=}')).toBe(false)
+    expect(framework.isRegisteredModule('foo', '')).toBe(false)
+    expect(framework.isRegisteredModule('foo', 'c')).toBe(false)
+    expect(framework.isRegisteredModule('bar', 'z')).toBe(false)
+    expect(framework.isRegisteredModule('unknown', 'unknown')).toBe(false)
+  })
+
   it('registerComponents', () => {
     framework.registerComponents(['foo', { type: 'bar' }, 'text'])
     const instance = new Instance(runtime)
@@ -431,6 +455,42 @@ describe('framework APIs', () => {
     })
   })
 
+  it('isRegisteredComponent', () => {
+    framework.registerComponents(['foo', { type: 'bar' }, 'text'])
+    expect(framework.isRegisteredComponent('foo')).toBe(true)
+    expect(framework.isRegisteredComponent('bar')).toBe(true)
+    expect(framework.isRegisteredComponent('text')).toBe(true)
+    expect(framework.isRegisteredComponent('FOO')).toBe(false)
+    expect(framework.isRegisteredComponent(' bar ')).toBe(false)
+    expect(framework.isRegisteredComponent('<text>')).toBe(false)
+    expect(framework.isRegisteredComponent('#}{)=}')).toBe(false)
+  })
+
+  it('weex.supports', () => {
+    framework.registerComponents(['apple', { type: 'banana' }])
+    framework.registerModules({
+      cat: ['eat', 'sleep'],
+      dog: [
+        { name: 'bark', args: ['string'] }
+      ]
+    })
+    expect(framework.supports('@component/apple')).toBe(true)
+    expect(framework.supports('@component/banana')).toBe(true)
+    expect(framework.supports('@module/cat')).toBe(true)
+    expect(framework.supports('@module/cat.eat')).toBe(true)
+    expect(framework.supports('@module/cat.sleep')).toBe(true)
+    expect(framework.supports('@module/dog.bark')).toBe(true)
+    expect(framework.supports('@component/candy')).toBe(false)
+    expect(framework.supports('@module/bird')).toBe(false)
+    expect(framework.supports('@module/bird.sing')).toBe(false)
+    expect(framework.supports('@module/dog.sleep')).toBe(false)
+    expect(framework.supports('apple')).toBe(null)
+    expect(framework.supports('<banana>')).toBe(null)
+    expect(framework.supports('cat')).toBe(null)
+    expect(framework.supports('@dog')).toBe(null)
+    expect(framework.supports('@component/dog#bark')).toBe(null)
+  })
+
   it('vm.$getConfig', () => {
     const instance = new Instance(runtime)
     instance.$create(`