Add theme (#516)

diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 2f01a83..3ac2b1d 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -9,7 +9,6 @@
       lang: 'zh-CN'
     }
   },
-  theme: 'fast',
   themeConfig: {
     repo: 'apache/incubator-weex-site',
     docsRepo: 'apache/incubator-weex-site',
diff --git a/docs/.vuepress/theme/Layout.vue b/docs/.vuepress/theme/Layout.vue
new file mode 100755
index 0000000..e2412f6
--- /dev/null
+++ b/docs/.vuepress/theme/Layout.vue
@@ -0,0 +1,217 @@
+<template>
+  <div
+    class="theme-container vuepress-theme-fast"
+    :class="pageClasses"
+    @touchstart="onTouchStart"
+    @touchend="onTouchEnd"
+  >
+    <Navbar
+      v-if="shouldShowNavbar"
+      @toggle-sidebar="toggleSidebar"
+    />
+
+    <div
+      class="sidebar-mask"
+      @click="toggleSidebar(false)"
+    ></div>
+
+    <Sidebar
+      :items="sidebarItems"
+      @toggle-sidebar="toggleSidebar"
+    >
+      <slot
+        name="sidebar-top"
+        slot="top"
+      />
+      <slot
+        name="sidebar-bottom"
+        slot="bottom"
+      />
+    </Sidebar>
+
+    <div
+      class="custom-layout"
+      v-if="$page.frontmatter.layout"
+    >
+      <component :is="$page.frontmatter.layout"/>
+    </div>
+
+    <Home v-else-if="$page.frontmatter.home"/>
+
+    <Page
+      v-else
+      :sidebar-items="sidebarItems"
+    >
+      <slot
+        name="page-top"
+        slot="top"
+      />
+      <slot
+        name="page-bottom"
+        slot="bottom"
+      />
+    </Page>
+
+    <SWUpdatePopup :updateEvent="swUpdateEvent"/>
+  </div>
+</template>
+<script>
+import Vue from 'vue'
+import nprogress from 'nprogress'
+import Home from './src/Home.vue'
+import Navbar from './src/Navbar.vue'
+import Page from './src/Page.vue'
+import Sidebar from './src/Sidebar.vue'
+import SWUpdatePopup from './src/SWUpdatePopup.vue'
+import { resolveSidebarItems, setSpm } from './src/util'
+
+export default {
+  components: { Home, Page, Sidebar, Navbar, SWUpdatePopup },
+
+  data () {
+    return {
+      isSidebarOpen: false,
+      swUpdateEvent: null
+    }
+  },
+
+  computed: {
+    shouldShowNavbar () {
+      const { themeConfig } = this.$site
+      const { frontmatter } = this.$page
+      if (
+        frontmatter.navbar === false ||
+        themeConfig.navbar === false) {
+        return false
+      }
+      return (
+        this.$title ||
+        themeConfig.logo ||
+        themeConfig.repo ||
+        themeConfig.nav ||
+        this.$themeLocaleConfig.nav
+      )
+    },
+
+    shouldShowSidebar () {
+      const { frontmatter } = this.$page
+      return (
+        !frontmatter.layout &&
+        !frontmatter.home &&
+        frontmatter.sidebar !== false &&
+        this.sidebarItems.length
+      )
+    },
+
+    sidebarItems () {
+      return resolveSidebarItems(
+        this.$page,
+        this.$route,
+        this.$site,
+        this.$localePath
+      )
+    },
+
+    pageClasses () {
+      const userPageClass = this.$page.frontmatter.pageClass
+      return [
+        {
+          'no-navbar': !this.shouldShowNavbar,
+          'sidebar-open': this.isSidebarOpen,
+          'no-sidebar': !this.shouldShowSidebar
+        },
+        userPageClass
+      ]
+    }
+  },
+
+  mounted () {
+    window.addEventListener('scroll', this.onScroll)
+
+    // configure progress bar
+    nprogress.configure({ showSpinner: false })
+
+    this.$router.beforeEach((to, from, next) => {
+      if (to.path !== from.path && !Vue.component(to.name)) {
+        nprogress.start()
+      }
+      next()
+    })
+
+    this.$router.afterEach(() => {
+      nprogress.done()
+      this.isSidebarOpen = false
+    })
+
+    this.$on('sw-updated', this.onSWUpdated)
+
+    const siteCfg = this.$site || {}
+    const themeCfg = siteCfg.themeConfig || {}
+    const SPM_A = themeCfg.spm
+    const SPM_B = window.location.pathname.replace('.html', '')
+
+    try {
+      if (process.env.NODE_ENV === 'production' && SPM_A) {
+        setSpm(`${SPM_A}.${SPM_B}`)
+      }
+    } catch {
+      if (SPM_A) {
+        setSpm(`${SPM_A}.${SPM_B}`)
+      }
+    }
+  },
+  watch: {
+    '$route' (to, from) {
+      const siteCfg = this.$site || {}
+      const themeCfg = siteCfg.themeConfig || {}
+      const currPath = to.path || ''
+      const fromPath = from.path || ''
+      const SPM_A = themeCfg.spm
+      const SPM_B = currPath.replace('.html', '')
+
+      try {
+        if (process.env.NODE_ENV === 'production' && currPath !== fromPath && SPM_A) {
+          setSpm(`${SPM_A}.${SPM_B}`)
+        }
+      } catch {
+        if (SPM_A) {
+          setSpm(`${SPM_A}.${SPM_B}`)
+        }
+      }
+    }
+  },
+
+  methods: {
+    toggleSidebar (to) {
+      this.isSidebarOpen = typeof to === 'boolean' ? to : !this.isSidebarOpen
+    },
+
+    // side swipe
+    onTouchStart (e) {
+      this.touchStart = {
+        x: e.changedTouches[0].clientX,
+        y: e.changedTouches[0].clientY
+      }
+    },
+
+    onTouchEnd (e) {
+      const dx = e.changedTouches[0].clientX - this.touchStart.x
+      const dy = e.changedTouches[0].clientY - this.touchStart.y
+      if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > 40) {
+        if (dx > 0 && this.touchStart.x <= 80) {
+          this.toggleSidebar(true)
+        } else {
+          this.toggleSidebar(false)
+        }
+      }
+    },
+
+    onSWUpdated (e) {
+      this.swUpdateEvent = e
+    }
+  }
+}
+</script>
+
+<style src="prismjs/themes/prism-tomorrow.css"></style>
+<style src="./src/styles/theme.styl" lang="stylus"></style>
diff --git a/docs/.vuepress/theme/src/AlgoliaSearchBox.vue b/docs/.vuepress/theme/src/AlgoliaSearchBox.vue
new file mode 100755
index 0000000..e5f516e
--- /dev/null
+++ b/docs/.vuepress/theme/src/AlgoliaSearchBox.vue
@@ -0,0 +1,140 @@
+<template>
+  <form
+    id="search-form"
+    class="algolia-search-wrapper search-box"
+  >
+    <input
+      id="algolia-search-input"
+      class="search-query"
+    >
+  </form>
+</template>
+
+<script>
+export default {
+  props: ['options'],
+
+  mounted () {
+    this.initialize()
+  },
+
+  methods: {
+    initialize () {
+      Promise.all([
+        import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.js'),
+        import(/* webpackChunkName: "docsearch" */ 'docsearch.js/dist/cdn/docsearch.min.css')
+      ]).then(([docsearch]) => {
+        docsearch = docsearch.default
+        docsearch(Object.assign(this.options, {
+          debug: false,
+          inputSelector: '#algolia-search-input'
+        }))
+      })
+    }
+  },
+
+  watch: {
+    options (newValue) {
+      this.$el.innerHTML = '<input id="algolia-search-input" class="search-query">'
+      this.initialize(newValue)
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.algolia-search-wrapper
+  & > span
+    vertical-align middle
+  .algolia-autocomplete
+    line-height normal
+    .ds-dropdown-menu
+      background-color #fff
+      border 1px solid #cfd4db
+      border-radius 4px
+      font-size 16px
+      margin 6px 0 0
+      padding 4px
+      text-align left
+      &:before
+        border-color #999
+      [class*=ds-dataset-]
+        border none
+        padding 0
+      .ds-suggestions
+        margin-top 0
+      .ds-suggestion
+        border-bottom 1px solid $borderColor
+    .algolia-docsearch-suggestion--highlight
+      color #00b4ff
+    .algolia-docsearch-suggestion
+      border-color $borderColor
+      padding 0
+      .algolia-docsearch-suggestion--category-header
+        padding 5px 10px
+        margin-top 0
+        background $accentColor
+        color #fff
+        font-weight 600
+        .algolia-docsearch-suggestion--highlight
+          background rgba(255, 255, 255, 0.6)
+      .algolia-docsearch-suggestion--wrapper
+        padding 0
+      .algolia-docsearch-suggestion--title
+        font-weight 600
+        margin-bottom 0
+        color $textColor
+      .algolia-docsearch-suggestion--subcategory-column
+        vertical-align top
+        padding 5px 7px 5px 5px
+        border-color $borderColor
+        background #f1f3f5
+        &:after
+          display none
+      .algolia-docsearch-suggestion--subcategory-column-text
+        color #555
+    .algolia-docsearch-footer
+      border-color $borderColor
+    .ds-cursor .algolia-docsearch-suggestion--content
+      background-color #e7edf3 !important
+      color $textColor
+
+@media (min-width: $MQMobile)
+  .algolia-search-wrapper
+    .algolia-autocomplete
+      .algolia-docsearch-suggestion
+        .algolia-docsearch-suggestion--subcategory-column
+          float none
+          width 150px
+          min-width 150px
+          display table-cell
+        .algolia-docsearch-suggestion--content
+          float none
+          display table-cell
+          width 100%
+          vertical-align top
+        .ds-dropdown-menu
+          min-width 515px !important
+
+@media (max-width: $MQMobile)
+  .algolia-search-wrapper
+    .ds-dropdown-menu
+      min-width calc(100vw - 4rem) !important
+      max-width calc(100vw - 4rem) !important
+    .algolia-docsearch-suggestion--wrapper
+      padding 5px 7px 5px 5px !important
+    .algolia-docsearch-suggestion--subcategory-column
+      padding 0 !important
+      background white !important
+    .algolia-docsearch-suggestion--subcategory-column-text:after
+      content " > "
+      font-size 10px
+      line-height 14.4px
+      display inline-block
+      width 5px
+      margin -3px 3px 0
+      vertical-align middle
+
+</style>
diff --git a/docs/.vuepress/theme/src/DropdownLink.vue b/docs/.vuepress/theme/src/DropdownLink.vue
new file mode 100755
index 0000000..e6000a6
--- /dev/null
+++ b/docs/.vuepress/theme/src/DropdownLink.vue
@@ -0,0 +1,181 @@
+<template>
+  <div
+    class="dropdown-wrapper"
+    :class="{ open }"
+  >
+    <a
+      class="dropdown-title"
+      @click="toggle"
+    >
+      <span class="title">{{ item.text }}</span>
+      <span
+        class="arrow"
+        :class="open ? 'down' : 'right'"
+      ></span>
+    </a>
+
+    <DropdownTransition>
+      <ul
+        class="nav-dropdown"
+        v-show="open"
+      >
+        <li
+          class="dropdown-item"
+          :key="subItem.link || index"
+          v-for="(subItem, index) in item.items"
+        >
+          <h4 v-if="subItem.type === 'links'">{{ subItem.text }}</h4>
+
+          <ul
+            class="dropdown-subitem-wrapper"
+            v-if="subItem.type === 'links'"
+          >
+            <li
+              class="dropdown-subitem"
+              :key="childSubItem.link"
+              v-for="childSubItem in subItem.items"
+            >
+              <NavLink :item="childSubItem"/>
+            </li>
+          </ul>
+
+          <NavLink
+            v-else
+            :item="subItem"
+          />
+        </li>
+      </ul>
+    </DropdownTransition>
+  </div>
+</template>
+
+<script>
+import NavLink from './NavLink.vue'
+import DropdownTransition from './DropdownTransition.vue'
+
+export default {
+  components: { NavLink, DropdownTransition },
+
+  data () {
+    return {
+      open: false
+    }
+  },
+
+  props: {
+    item: {
+      required: true
+    }
+  },
+
+  methods: {
+    toggle () {
+      this.open = !this.open
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.dropdown-wrapper
+  cursor pointer
+  .dropdown-title
+    display block
+    &:hover
+      border-color transparent
+    .arrow
+      vertical-align middle
+      margin-top -1px
+      margin-left 0.4rem
+  .nav-dropdown
+    .dropdown-item
+      color inherit
+      line-height 1.7rem
+      h4
+        margin 0.45rem 0 0
+        border-top 1px solid #eee
+        padding 0.45rem 1.5rem 0 1.25rem
+      .dropdown-subitem-wrapper
+        padding 0
+        list-style none
+        .dropdown-subitem
+          font-size 0.9em
+      a
+        display block
+        line-height 1.7rem
+        position relative
+        border-bottom none
+        font-weight 400
+        margin-bottom 0
+        padding 0 1.5rem 0 1.25rem
+        &:hover
+          color $accentColor
+        &.router-link-active
+          color $accentColor
+          &::after
+            content ""
+            width 0
+            height 0
+            border-left 5px solid $accentColor
+            border-top 3px solid transparent
+            border-bottom 3px solid transparent
+            position absolute
+            top calc(50% - 2px)
+            left 9px
+      &:first-child h4
+        margin-top 0
+        padding-top 0
+        border-top 0
+
+@media (max-width: $MQMobile)
+  .dropdown-wrapper
+    &.open .dropdown-title
+      margin-bottom 0.5rem
+    .nav-dropdown
+      transition height .1s ease-out
+      overflow hidden
+      .dropdown-item
+        h4
+          border-top 0
+          margin-top 0
+          padding-top 0
+        h4, & > a
+          font-size 15px
+          line-height 2rem
+        .dropdown-subitem
+          font-size 14px
+          padding-left 1rem
+
+@media (min-width: $MQMobile)
+  .dropdown-wrapper
+    height 1.8rem
+    &:hover .nav-dropdown
+      // override the inline style.
+      display block !important
+    .dropdown-title .arrow
+      // make the arrow always down at desktop
+      border-left 4px solid transparent
+      border-right 4px solid transparent
+      border-top 6px solid $arrowBgColor
+      border-bottom 0
+    .nav-dropdown
+      display none
+      // Avoid height shaked by clicking
+      height auto !important
+      box-sizing border-box;
+      max-height calc(100vh - 2.7rem)
+      overflow-y auto
+      position absolute
+      top 100%
+      right 0
+      background-color #fff
+      padding 0.6rem 0
+      border 1px solid #ddd
+      border-bottom-color #ccc
+      text-align left
+      border-radius 0.25rem
+      white-space nowrap
+      margin 0
+</style>
diff --git a/docs/.vuepress/theme/src/DropdownTransition.vue b/docs/.vuepress/theme/src/DropdownTransition.vue
new file mode 100755
index 0000000..8c711a1
--- /dev/null
+++ b/docs/.vuepress/theme/src/DropdownTransition.vue
@@ -0,0 +1,33 @@
+<template>
+  <transition
+    name="dropdown"
+    @enter="setHeight"
+    @after-enter="unsetHeight"
+    @before-leave="setHeight"
+  >
+    <slot/>
+  </transition>
+</template>
+
+<script>
+export default {
+  name: 'DropdownTransition',
+
+  methods: {
+    setHeight (items) {
+      // explicitly set height so that it can be transitioned
+      items.style.height = items.scrollHeight + 'px'
+    },
+
+    unsetHeight (items) {
+      items.style.height = ''
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+.dropdown-enter, .dropdown-leave-to
+  height 0 !important
+
+</style>
diff --git a/docs/.vuepress/theme/src/Home.vue b/docs/.vuepress/theme/src/Home.vue
new file mode 100755
index 0000000..69ac0e1
--- /dev/null
+++ b/docs/.vuepress/theme/src/Home.vue
@@ -0,0 +1,161 @@
+<template>
+  <div class="home">
+    <div class="hero">
+      <img
+        v-if="data.heroImage"
+        :src="$withBase(data.heroImage)"
+        alt="hero"
+      >
+
+      <h1>{{ data.heroText || $title || 'Hello' }}</h1>
+
+      <p class="description">
+        {{ data.tagline || $description || 'Welcome to your VuePress site' }}
+      </p>
+
+      <p
+        class="action"
+        v-if="data.actionText && data.actionLink"
+      >
+        <NavLink
+          class="action-button"
+          :item="actionLink"
+        />
+      </p>
+    </div>
+
+    <div
+      class="features"
+      v-if="data.features && data.features.length"
+    >
+      <div
+        class="feature"
+        v-for="feature in data.features"
+      >
+        <h2>{{ feature.title }}</h2>
+        <p>{{ feature.details }}</p>
+      </div>
+    </div>
+
+    <Content custom/>
+
+    <div
+      class="footer"
+      v-if="data.footer"
+    >
+      {{ data.footer }}
+    </div>
+  </div>
+</template>
+
+<script>
+import NavLink from './NavLink.vue'
+
+export default {
+  components: { NavLink },
+
+  computed: {
+    data () {
+      return this.$page.frontmatter
+    },
+
+    actionLink () {
+      return {
+        link: this.data.actionLink,
+        text: this.data.actionText
+      }
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.home
+  padding $navbarHeight 2rem 0
+  max-width 960px
+  margin 0px auto
+  .hero
+    text-align center
+    img
+      max-height 280px
+      display block
+      margin 3rem auto 1.5rem
+    h1
+      font-size 3rem
+    h1, .description, .action
+      margin 1.8rem auto
+    .description
+      max-width 35rem
+      font-size 1.6rem
+      line-height 1.3
+      color lighten($textColor, 40%)
+    .action-button
+      display inline-block
+      font-size 1.2rem
+      color #fff
+      background-color $accentColor
+      padding 0.8rem 1.6rem
+      border-radius 4px
+      transition background-color .1s ease
+      box-sizing border-box
+      border-bottom 1px solid darken($accentColor, 10%)
+      &:hover
+        background-color lighten($accentColor, 10%)
+  .features
+    border-top 1px solid $borderColor
+    padding 1.2rem 0
+    margin-top 2.5rem
+    display flex
+    flex-wrap wrap
+    align-items flex-start
+    align-content stretch
+    justify-content space-between
+  .feature
+    flex-grow 1
+    flex-basis 30%
+    max-width 30%
+    h2
+      font-size 1.4rem
+      font-weight 500
+      border-bottom none
+      padding-bottom 0
+      color lighten($textColor, 10%)
+    p
+      color lighten($textColor, 25%)
+  .footer
+    padding 2.5rem
+    border-top 1px solid $borderColor
+    text-align center
+    color lighten($textColor, 25%)
+
+@media (max-width: $MQMobile)
+  .home
+    .features
+      flex-direction column
+    .feature
+      max-width 100%
+      padding 0 2.5rem
+
+@media (max-width: $MQMobileNarrow)
+  .home
+    padding-left 1.5rem
+    padding-right 1.5rem
+    .hero
+      img
+        max-height 210px
+        margin 2rem auto 1.2rem
+      h1
+        font-size 2rem
+      h1, .description, .action
+        margin 1.2rem auto
+      .description
+        font-size 1.2rem
+      .action-button
+        font-size 1rem
+        padding 0.6rem 1.2rem
+    .feature
+      h2
+        font-size 1.25rem
+</style>
diff --git a/docs/.vuepress/theme/src/License.vue b/docs/.vuepress/theme/src/License.vue
new file mode 100644
index 0000000..cb5db72
--- /dev/null
+++ b/docs/.vuepress/theme/src/License.vue
@@ -0,0 +1,170 @@
+<template>
+  <footer class="footer-container">
+    <div class="footer-body">
+      <img class="logo" src="/logo@2x.svg">
+      <img class="apache" src="//img.alicdn.com/tfs/TB11BRiIW6qK1RjSZFmXXX0PFXa-365-365.png">
+      <div class="cols-container">
+        <div class="col col-12">
+          <h3>Disclaimer</h3>
+          <p>Apache Weex is an effort undergoing incubation at The Apache Software Foundation (ASF), sponsored by the
+            Incubator. Incubation is required of all newly accepted projects until a further review indicates that the
+            infrastructure, communications, and decision making process have stabilized in a manner consistent with
+            other successful ASF projects. While incubation status is not necessarily a reflection of the completeness
+            or stability of the code, it does indicate that the project has yet to be fully endorsed by the ASF.</p>
+        </div>
+        <div class="col col-4">
+          <dl><dt>ASF</dt>
+            <dd><a href="http://www.apache.org" target="_self">Foundation</a></dd>
+            <dd><a href="http://www.apache.org/licenses/" target="_self">License</a></dd>
+            <dd><a href="http://www.apache.org/events/current-event" target="_self">Events</a></dd>
+            <dd><a href="http://www.apache.org/foundation/sponsorship.html" target="_self">Sponsorship</a></dd>
+            <dd><a href="http://www.apache.org/foundation/thanks.html" target="_self">Thanks</a></dd>
+          </dl>
+        </div>
+        <div class="col col-4">
+          <dl><dt>Documentation</dt>
+            <dd><a href="/guide/develop/create-a-new-app.html" target="_self">Quick start</a></dd>
+            <dd><a href="/guide/develop/setup-develop-environment.html" target="_self">Developer guide</a></dd>
+          </dl>
+        </div>
+        <div class="col col-4">
+          <dl><dt>Resources</dt>
+            <dd><a href="/blog/write-a-blog.html" target="_self">Blog</a></dd>
+            <dd><a href="/community/" target="_self">Community</a></dd>
+            <dd><a href="/guide/contribute/thanks.html" target="_self">Thanks by Weex</a></dd>
+            <dd><a href="https://www.apache.org/security/" target="_self">Security</a></dd>
+          </dl>
+        </div>
+      </div>
+      <div class="copyright"><span>Copyright © 2018-2019 The Apache Software Foundation. Apache and the Apache feather
+          logo are trademarks of The Apache Software Foundation.</span></div>
+    </div>
+  </footer>
+</template>
+
+<style scoped>
+.footer-container {
+    background: #F8F8F8;
+}
+
+.footer-container .footer-body {
+    max-width: 1280px;
+    margin: 0 auto;
+    box-sizing: border-box;
+    padding: 40px 40px 0;
+}
+
+@media screen and (max-width: 640px) {
+    .footer-container .footer-body {
+        padding-left: 20px;
+        padding-right: 20px;
+    }
+}
+
+.footer-container .footer-body img {
+    width: 100px;
+    height: 40px;
+    margin-left: -10px;
+    margin-bottom: 28px;
+    vertical-align: middle;
+}
+
+.footer-container .footer-body .apache {
+    width: 50px;
+    height: 50px;
+    margin-left: 0;
+}
+
+.footer-container .footer-body .cols-container {
+    margin-bottom: 60px;
+}
+
+.footer-container .footer-body .cols-container .col {
+    display: inline-block;
+    box-sizing: border-box;
+    vertical-align: top;
+}
+
+.footer-container .footer-body .cols-container .col-12 {
+    width: 50%;
+    padding-right: 125px;
+}
+
+.footer-container .footer-body .cols-container .col-6 {
+    width: 25%;
+}
+
+.footer-container .footer-body .cols-container .col-4 {
+    width: 16.666667%;
+}
+
+.footer-container .footer-body .cols-container h3 {
+    font-family: Avenir-Heavy;
+    font-size: 18px;
+    color: #333;
+    line-height: 18px;
+    margin-bottom: 20px;
+}
+
+.footer-container .footer-body .cols-container p {
+    font-family: Avenir-Medium;
+    font-size: 12px;
+    color: #999;
+    line-height: 18px;
+}
+
+.footer-container .footer-body .cols-container dl {
+    font-family: Avenir-Heavy;
+    line-height: 18px;
+}
+
+.footer-container .footer-body .cols-container dt {
+    font-weight: bold;
+    font-size: 18px;
+    color: #333;
+    margin-bottom: 20px;
+}
+
+.footer-container .footer-body .cols-container dd {
+    padding: 0;
+    margin: 0;
+}
+
+.footer-container .footer-body .cols-container dd a {
+    text-decoration: none;
+    display: block;
+    font-size: 14px;
+    color: #999;
+    margin: 10px 0;
+}
+
+.footer-container .footer-body .cols-container dd a:hover {
+    color: #2DACEC;
+}
+
+.footer-container .footer-body .copyright {
+    border-top: 1px solid #ccc;
+    min-height: 60px;
+    line-height: 20px;
+    text-align: center;
+    font-family: Avenir-Medium;
+    font-size: 12px;
+    color: #999;
+    display: flex;
+    align-items: center;
+}
+
+.footer-container .footer-body .copyright span {
+    display: inline-block;
+    margin: 0 auto;
+}
+
+@media screen and (max-width: 640px) {
+    .footer-container .footer-body .cols-container .col {
+        width: 100%;
+        text-align: center;
+        padding: 0;
+    }
+}
+</style>
+
diff --git a/docs/.vuepress/theme/src/NavLink.vue b/docs/.vuepress/theme/src/NavLink.vue
new file mode 100755
index 0000000..d9fa488
--- /dev/null
+++ b/docs/.vuepress/theme/src/NavLink.vue
@@ -0,0 +1,49 @@
+<template>
+  <router-link
+    class="nav-link"
+    :to="link"
+    v-if="!isExternal(link)"
+    :exact="exact"
+  >{{ item.text }}</router-link>
+  <a
+    v-else
+    :href="link"
+    class="nav-link external"
+    :target="isMailto(link) || isTel(link) ? null : '_blank'"
+    :rel="isMailto(link) || isTel(link) ? null : 'noopener noreferrer'"
+  >
+    {{ item.text }}
+    <OutboundLink/>
+  </a>
+</template>
+
+<script>
+import { isExternal, isMailto, isTel, ensureExt } from './util'
+
+export default {
+  props: {
+    item: {
+      required: true
+    }
+  },
+
+  computed: {
+    link () {
+      return ensureExt(this.item.link)
+    },
+
+    exact () {
+      if (this.$site.locales) {
+        return Object.keys(this.$site.locales).some(rootLink => rootLink === this.link)
+      }
+      return this.link === '/'
+    }
+  },
+
+  methods: {
+    isExternal,
+    isMailto,
+    isTel
+  }
+}
+</script>
diff --git a/docs/.vuepress/theme/src/NavLinks.vue b/docs/.vuepress/theme/src/NavLinks.vue
new file mode 100755
index 0000000..ba1fa46
--- /dev/null
+++ b/docs/.vuepress/theme/src/NavLinks.vue
@@ -0,0 +1,115 @@
+<template>
+  <nav
+    class="nav-links"
+    v-if="userLinks.length || repoLink"
+  >
+    <!-- user links -->
+    <div
+      class="nav-item"
+      v-for="item in userLinks"
+      :key="item.link"
+    >
+      <DropdownLink
+        v-if="item.type === 'links'"
+        :item="item"
+      />
+      <NavLink
+        v-else
+        :item="item"
+      />
+    </div>
+  </nav>
+</template>
+
+<script>
+import DropdownLink from './DropdownLink.vue'
+import { resolveNavLinkItem } from './util'
+import NavLink from './NavLink.vue'
+
+export default {
+  components: { NavLink, DropdownLink },
+
+  computed: {
+    userNav () {
+      return this.$themeLocaleConfig.nav || this.$site.themeConfig.nav || []
+    },
+
+    nav () {
+      const { locales } = this.$site
+      if (locales && Object.keys(locales).length > 1) {
+        const currentLink = this.$page.path
+        const routes = this.$router.options.routes
+        const themeLocales = this.$site.themeConfig.locales || {}
+        const languageDropdown = {
+          text: this.$themeLocaleConfig.selectText || 'Languages',
+          items: Object.keys(locales).map(path => {
+            const locale = locales[path]
+            const text = themeLocales[path] && themeLocales[path].label || locale.lang
+            let link
+            // Stay on the current page
+            if (locale.lang === this.$lang) {
+              link = currentLink
+            } else {
+              // Try to stay on the same page
+              link = currentLink.replace(this.$localeConfig.path, path)
+              // fallback to homepage
+              if (!routes.some(route => route.path === link)) {
+                link = path
+              }
+            }
+            return { text, link }
+          })
+        }
+        return [...this.userNav, languageDropdown]
+      }
+      return this.userNav
+    },
+
+    userLinks () {
+      return (this.nav || []).map(link => {
+        return Object.assign(resolveNavLinkItem(link), {
+          items: (link.items || []).map(resolveNavLinkItem)
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.nav-links
+  display inline-block
+  a
+    line-height 1.4rem
+    color inherit
+    &:hover, &.router-link-active
+      color $accentColor
+  .nav-item
+    position relative
+    display inline-block
+    margin-left 2rem
+    line-height 2rem
+
+@media (max-width: $MQMobile)
+  .nav-links
+    .nav-item, .repo-link
+      margin-left 0
+
+@media (min-width: $MQMobile)
+  .nav-links a
+    &:hover, &.router-link-active
+      color $textColor
+  .nav-item > a:not(.external)
+    &:hover, &.router-link-active
+      color $accentColor
+    &.router-link-active:after
+      content ''
+      display block
+      width 100%
+      height 3px
+      position absolute
+      bottom -0.77rem
+      background-color $accentColor
+</style>
diff --git a/docs/.vuepress/theme/src/Navbar.vue b/docs/.vuepress/theme/src/Navbar.vue
new file mode 100755
index 0000000..068af9c
--- /dev/null
+++ b/docs/.vuepress/theme/src/Navbar.vue
@@ -0,0 +1,130 @@
+<template>
+  <header class="navbar">
+    <SidebarButton @toggle-sidebar="$emit('toggle-sidebar')"/>
+
+    <router-link
+      :to="$localePath"
+      class="home-link"
+    >
+      <img
+        class="logo"
+        v-if="$site.themeConfig.logo"
+        :src="$withBase($site.themeConfig.logo)"
+        :alt="$siteTitle"
+      >
+    </router-link>
+    <span class="version">
+      <a class="version-bg" :href="$site.themeConfig.versionLink">
+        <img src="./version-bg.svg" :alt="$site.themeConfig.version">
+        <span class="version-no">{{$site.themeConfig.version}}</span>
+      </a>
+    </span>
+
+    <div class="links">
+      <AlgoliaSearchBox
+        v-if="isAlgoliaSearch"
+        :options="algolia"
+      />
+      <SearchBox v-else-if="$site.themeConfig.search !== false"/>
+
+      <NavLinks class="can-hide"/>
+
+      <!-- repo link -->
+      <a
+        v-if="repoLink"
+        :href="repoLink"
+        class="repo-link"
+        target="_blank"
+        rel="noopener noreferrer"
+      >
+        <img class="github-icon" src="./github.svg" alt="github">
+      </a>
+    </div>
+  </header>
+</template>
+
+<script>
+import SidebarButton from './SidebarButton.vue'
+import AlgoliaSearchBox from './AlgoliaSearchBox'
+import SearchBox from './SearchBox.vue'
+import NavLinks from './NavLinks.vue'
+
+export default {
+  components: { SidebarButton, NavLinks, SearchBox, AlgoliaSearchBox },
+
+  computed: {
+    algolia () {
+      return this.$themeLocaleConfig.algolia || this.$site.themeConfig.algolia || {}
+    },
+
+    isAlgoliaSearch () {
+      return this.algolia && this.algolia.apiKey && this.algolia.indexName
+    },
+
+    repoLink () {
+      const { repo } = this.$site.themeConfig
+      if (repo) {
+        return /^https?:/.test(repo)
+          ? repo
+          : `https://github.com/${repo}`
+      }
+    },
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.navbar
+  padding 0.7rem 1.5rem
+  line-height $navbarHeight - 1.4rem
+  position relative
+  a, span, img
+    display inline-block
+  .logo
+    height $navbarHeight - 1.4rem
+    min-width $navbarHeight - 1.4rem
+    margin-right 10px
+    vertical-align top
+  .site-name
+    font-size 1.3rem
+    font-weight 600
+    color $textColor
+    position relative
+  .version
+    position relative
+    .version-bg
+      position absolute
+      line-height 0
+      top -6px
+    .version-no
+      position absolute
+      top 7.5px
+      left 8px
+      font-size 12px
+      transform scale(0.84)
+      color: #fff
+  .links
+    font-size 0.9rem
+    position absolute
+    right 1.5rem
+    top 0.7rem
+    display flex
+    justify-content center
+  .repo-link
+    width 2.2rem
+    height 2.2rem
+    margin-left 1.5rem
+    .github-icon
+      width 2.2rem
+      height 2.2rem
+
+@media (max-width: $MQMobile)
+  .navbar
+    padding-left 4rem
+    .can-hide
+      display none
+    .repo-link
+      display none
+</style>
diff --git a/docs/.vuepress/theme/src/NotFound.vue b/docs/.vuepress/theme/src/NotFound.vue
new file mode 100755
index 0000000..6aefe79
--- /dev/null
+++ b/docs/.vuepress/theme/src/NotFound.vue
@@ -0,0 +1,26 @@
+<template>
+  <div class="theme-container">
+    <div class="content">
+      <h1>404</h1>
+      <blockquote>{{ getMsg() }}</blockquote>
+      <router-link to="/">Take me home.</router-link>
+    </div>
+  </div>
+</template>
+
+<script>
+const msgs = [
+  `There's nothing here.`,
+  `How did we get here?`,
+  `That's a Four-Oh-Four.`,
+  `Looks like we've got some broken links.`
+]
+
+export default {
+  methods: {
+    getMsg () {
+      return msgs[Math.floor(Math.random() * msgs.length)]
+    }
+  }
+}
+</script>
diff --git a/docs/.vuepress/theme/src/Page.vue b/docs/.vuepress/theme/src/Page.vue
new file mode 100755
index 0000000..71b48ef
--- /dev/null
+++ b/docs/.vuepress/theme/src/Page.vue
@@ -0,0 +1,365 @@
+<template>
+  <div class="page">
+    <slot name="top"/>
+
+    <Content :custom="false"/>
+
+    <div class="page-nav" v-if="prev || next">
+      <p class="inner">
+        <span
+          v-if="prev"
+          class="prev"
+        >
+          ←
+          <router-link
+            v-if="prev"
+            class="prev"
+            :to="prev.path"
+          >
+            {{ prev.title || prev.path }}
+          </router-link>
+        </span>
+
+        <span
+          v-if="next"
+          class="next"
+        >
+          <router-link
+            v-if="next"
+            :to="next.path"
+          >
+            {{ next.title || next.path }}
+          </router-link>
+          →
+        </span>
+      </p>
+    </div>
+
+    <div class="page-edit">
+      <div
+        class="last-updated"
+        v-if="lastUpdated"
+      >
+        <span class="prefix">{{ lastUpdatedText }}: </span>
+        <span class="time">{{ lastUpdated }}</span>
+      </div>
+
+      <div
+        class="edit-link"
+        v-if="editLink"
+      >
+        <a
+          :href="editLink"
+          target="_blank"
+          rel="noopener noreferrer"
+        >{{ editLinkText }}</a>
+        <OutboundLink/>
+        <a
+          :href="issueLink()"
+          target="_blank"
+          rel="noopener noreferrer"
+          class="issueText"
+        >{{ openIssueText }}</a>
+        <OutboundLink/>
+      </div>
+
+      <div class="score">
+        <span class="choice"><img @click="scorePlus" class="score-icon" :src="scoreGood ? 'https://img.alicdn.com/tfs/TB1DOvPqCzqK1RjSZPxXXc4tVXa-20-18.svg' : 'https://img.alicdn.com/tfs/TB1h7TSqpYqK1RjSZLeXXbXppXa-20-18.svg'" />{{ scoreText.good }}</span>
+        <span class="choice"><img @click="scoreMinus" class="score-icon bad" :src="scoreBad ? 'https://img.alicdn.com/tfs/TB1DOvPqCzqK1RjSZPxXXc4tVXa-20-18.svg' : 'https://img.alicdn.com/tfs/TB1h7TSqpYqK1RjSZLeXXbXppXa-20-18.svg'" />{{ scoreText.bad }}</span>
+      </div>
+    </div>
+
+    <div v-show="isApache" class="license-wrap">
+      <License />
+    </div>
+
+    <slot name="bottom"/>
+  </div>
+</template>
+
+<script>
+import axios from 'axios';
+import qs from 'qs';
+import { resolvePage, normalize, outboundRE, endingSlashRE } from './util'
+import License from './License.vue';
+
+export default {
+  props: ['sidebarItems'],
+
+  components: {
+    License
+  },
+
+  data() {
+    return {
+      scoreGood: false,
+      scoreBad: false,
+      path: '',
+      isApache: window.location.href.indexOf('weex.apache.org') !== -1
+    }
+  },
+
+  watch: {
+    $route() {
+      if (!this.path) {
+        this.path = this.$route.path;
+      } else if (this.path !== this.$route.path) {
+        // console.log(this.$route.path, this.path)
+        this.scoreGood = false;
+        this.scoreBad = false;
+      }
+    }
+  },
+
+  computed: {
+    lastUpdated () {
+      if (this.$page.lastUpdated) {
+        return new Date(this.$page.lastUpdated).toLocaleDateString(this.$lang)
+      }
+    },
+
+    lastUpdatedText () {
+      if (typeof this.$themeLocaleConfig.lastUpdated === 'string') {
+        return this.$themeLocaleConfig.lastUpdated
+      }
+      if (typeof this.$site.themeConfig.lastUpdated === 'string') {
+        return this.$site.themeConfig.lastUpdated
+      }
+      return 'Last Updated'
+    },
+
+    prev () {
+      const prev = this.$page.frontmatter.prev
+      if (prev === false) {
+        return
+      } else if (prev) {
+        return resolvePage(this.$site.pages, prev, this.$route.path)
+      } else {
+        return resolvePrev(this.$page, this.sidebarItems)
+      }
+    },
+
+    next () {
+      const next = this.$page.frontmatter.next
+      if (next === false) {
+        return
+      } else if (next) {
+        return resolvePage(this.$site.pages, next, this.$route.path)
+      } else {
+        return resolveNext(this.$page, this.sidebarItems)
+      }
+    },
+
+    editLink () {
+      if (this.$page.frontmatter.editLink === false) {
+        return
+      }
+      const {
+        repo,
+        editLinks,
+        docsDir = '',
+        docsBranch = 'master',
+        docsRepo = repo
+      } = this.$site.themeConfig
+
+      let path = normalize(this.$page.path)
+      if (endingSlashRE.test(path)) {
+        path += 'README.md'
+      } else {
+        path += '.md'
+      }
+      if (docsRepo && editLinks) {
+        return this.createEditLink(repo, docsRepo, docsDir, docsBranch, path)
+      }
+    },
+
+    editLinkText () {
+      return (
+        this.$themeLocaleConfig.editLinkText ||
+        this.$site.themeConfig.editLinkText ||
+        `Edit this page`
+      )
+    },
+
+    issueLink() {
+      return function() { 
+        return `https://github.com/apache/incubator-weex-site/issues/new?body=${encodeURIComponent(location.href)}`
+      }
+    },
+
+    openIssueText() {
+      return (
+        this.$themeLocaleConfig.openIssueText ||
+        this.$site.themeConfig.openIssueText ||
+        null
+      )
+    },
+
+    scoreText () {
+      return (
+        this.$themeLocaleConfig.scoreText ||
+        this.$site.themeConfig.scoreText ||
+        null
+      )
+    }
+  },
+
+  methods: {
+    createEditLink (repo, docsRepo, docsDir, docsBranch, path) {
+      const bitbucket = /bitbucket.org/
+      if (bitbucket.test(repo)) {
+        const base = outboundRE.test(docsRepo)
+          ? docsRepo
+          : repo
+        return (
+          base.replace(endingSlashRE, '') +
+           `/${docsBranch}` +
+           (docsDir ? '/' + docsDir.replace(endingSlashRE, '') : '') +
+           path +
+           `?mode=edit&spa=0&at=${docsBranch}&fileviewer=file-view-default`
+        )
+      }
+
+      const base = outboundRE.test(docsRepo)
+        ? docsRepo
+        : `https://github.com/${docsRepo}`
+
+      return (
+        base.replace(endingSlashRE, '') +
+        `/edit/${docsBranch}` +
+        (docsDir ? '/' + docsDir.replace(endingSlashRE, '') : '') +
+        path
+      )
+    },
+
+    scorePlus () {
+      if (this.scoreGood) return;
+      if (axios) {
+        axios.post(`${this.API_PREFIX}/api/like/islike`, qs.stringify({url: this.$route.path, islike: 1 })).then(() => {
+          this.scoreGood = true;
+          this.scoreBad = false;
+        }).catch(err => console.error(err))
+      }
+    },
+
+    scoreMinus () {
+      if (this.scoreBad) return;
+      if (axios) {
+        axios.post(`${this.API_PREFIX}/api/like/islike`, qs.stringify({url: this.$route.path, islike: 0 })).then(() => {
+          this.scoreGood = false;
+          this.scoreBad = true;
+        }).catch(err => console.error(err))
+      }
+    }
+  }
+}
+
+function resolvePrev (page, items) {
+  return find(page, items, -1)
+}
+
+function resolveNext (page, items) {
+  return find(page, items, 1)
+}
+
+function find (page, items, offset) {
+  const res = []
+  items.forEach(item => {
+    if (item.type === 'group') {
+      res.push(...item.children || [])
+    } else {
+      res.push(item)
+    }
+  })
+  for (let i = 0; i < res.length; i++) {
+    const cur = res[i]
+    if (cur.type === 'page' && cur.path === page.path) {
+      return res[i + offset]
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+@require './styles/wrapper.styl'
+
+.page
+  padding-bottom 2rem
+
+.page-edit
+  @extend $wrapper
+  padding-top 1rem
+  padding-bottom 1rem
+  overflow auto
+  .edit-link
+    margin-left 1rem
+    display inline-block
+    a
+      color #606273
+      margin-right 0.25rem
+      text-decoration underline
+      font-style italic
+      font-size 14px
+    .issueText
+      margin-left 16px
+  .last-updated
+    font-size 14px
+    display inline-block
+    .prefix
+      font-weight 500
+      color #373D41
+    .time
+      font-weight 400
+      color #373D41
+  .score
+    float right
+    display flex
+    font-size 14px
+    color #8A94AC
+    .choice 
+      margin-left 1rem
+      display flex
+      align-items center
+      .score-icon
+        margin-right 0.3rem
+        cursor pointer
+      .bad
+        transform rotate(180deg)
+        position relative
+        top 2px
+
+.page-nav
+  @extend $wrapper
+  padding-top 1rem
+  padding-bottom 0
+  .inner
+    min-height 2rem
+    margin-top 0
+    margin-bottom 0
+    border-bottom 1px solid $borderColor
+    overflow auto // clear float
+  .next
+    float right
+
+.license-wrap
+  margin-top 3rem
+  margin-left 11rem
+  margin-right -4rem
+
+@media (max-width: $MQMobile)
+  .license-wrap
+    margin-left 0
+    margin-right 0
+  .page-edit
+    margin-left 0
+  .page-nav
+    margin-left 0
+    .edit-link
+      margin-bottom .5rem
+    .last-updated
+      font-size .8em
+      float none
+      text-align left
+
+</style>
diff --git a/docs/.vuepress/theme/src/SWUpdatePopup.vue b/docs/.vuepress/theme/src/SWUpdatePopup.vue
new file mode 100755
index 0000000..b224db3
--- /dev/null
+++ b/docs/.vuepress/theme/src/SWUpdatePopup.vue
@@ -0,0 +1,85 @@
+<template>
+  <transition name="sw-update-popup">
+    <div
+      v-if="enabled"
+      class="sw-update-popup"
+    >
+      {{message}}<br>
+      <button @click="reload">{{buttonText}}</button>
+    </div>
+  </transition>
+</template>
+
+<script>
+export default {
+  props: {
+    updateEvent: {
+      type: Object,
+      default: null
+    }
+  },
+
+  computed: {
+    popupConfig () {
+      for (const config of [this.$themeLocaleConfig, this.$site.themeConfig]) {
+        const sw = config.serviceWorker
+        if (sw && sw.updatePopup) {
+          return typeof sw.updatePopup === 'object' ? sw.updatePopup : {}
+        }
+      }
+      return null
+    },
+
+    enabled () {
+      return Boolean(this.popupConfig && this.updateEvent)
+    },
+
+    message () {
+      const c = this.popupConfig
+      return (c && c.message) || 'New content is available.'
+    },
+
+    buttonText () {
+      const c = this.popupConfig
+      return (c && c.buttonText) || 'Refresh'
+    }
+  },
+
+  methods: {
+    reload () {
+      if (this.updateEvent) {
+        this.updateEvent.skipWaiting().then(() => {
+          location.reload(true)
+        })
+        this.updateEvent = null
+      }
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.sw-update-popup
+  position fixed
+  right 1em
+  bottom 1em
+  padding 1em
+  border 1px solid $accentColor
+  border-radius 3px
+  background #fff
+  box-shadow 0 4px 16px rgba(0, 0, 0, 0.5)
+  text-align center
+
+  button
+    margin-top 0.5em
+    padding 0.25em 2em
+
+.sw-update-popup-enter-active, .sw-update-popup-leave-active
+  transition opacity 0.3s, transform 0.3s
+
+.sw-update-popup-enter, .sw-update-popup-leave-to
+  opacity 0
+  transform translate(0, 50%) scale(0.5)
+</style>
diff --git a/docs/.vuepress/theme/src/SearchBox.vue b/docs/.vuepress/theme/src/SearchBox.vue
new file mode 100755
index 0000000..a72d036
--- /dev/null
+++ b/docs/.vuepress/theme/src/SearchBox.vue
@@ -0,0 +1,235 @@
+<template>
+  <div class="search-box">
+    <input
+      @input="query = $event.target.value"
+      aria-label="Search"
+      :value="query"
+      autocomplete="off"
+      spellcheck="false"
+      @focus="focused = true"
+      @blur="focused = false"
+      @keyup.enter="go(focusIndex)"
+      @keyup.up="onUp"
+      @keyup.down="onDown"
+    >
+    <ul
+      class="suggestions"
+      v-if="showSuggestions"
+      :class="{ 'align-right': alignRight }"
+      @mouseleave="unfocus"
+    >
+      <li
+        class="suggestion"
+        v-for="(s, i) in suggestions"
+        :class="{ focused: i === focusIndex }"
+        @mousedown="go(i)"
+        @mouseenter="focus(i)"
+      >
+        <a :href="s.path" @click.prevent>
+          <span class="page-title">{{ s.title || s.path }}</span>
+          <span v-if="s.header" class="header">&gt; {{ s.header.title }}</span>
+        </a>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  data () {
+    return {
+      query: '',
+      focused: false,
+      focusIndex: 0
+    }
+  },
+
+  computed: {
+    showSuggestions () {
+      return (
+        this.focused &&
+        this.suggestions &&
+        this.suggestions.length
+      )
+    },
+
+    suggestions () {
+      const query = this.query.trim().toLowerCase()
+      if (!query) {
+        return
+      }
+
+      const { pages, themeConfig } = this.$site
+      const max = themeConfig.searchMaxSuggestions || 5
+      const localePath = this.$localePath
+      const matches = item => (
+        item.title &&
+        item.title.toLowerCase().indexOf(query) > -1
+      )
+      const res = []
+      for (let i = 0; i < pages.length; i++) {
+        if (res.length >= max) break
+        const p = pages[i]
+        // filter out results that do not match current locale
+        if (this.getPageLocalePath(p) !== localePath) {
+          continue
+        }
+        if (matches(p)) {
+          res.push(p)
+        } else if (p.headers) {
+          for (let j = 0; j < p.headers.length; j++) {
+            if (res.length >= max) break
+            const h = p.headers[j]
+            if (matches(h)) {
+              res.push(Object.assign({}, p, {
+                path: p.path + '#' + h.slug,
+                header: h
+              }))
+            }
+          }
+        }
+      }
+      return res
+    },
+
+    // make suggestions align right when there are not enough items
+    alignRight () {
+      const navCount = (this.$site.themeConfig.nav || []).length
+      const repo = this.$site.repo ? 1 : 0
+      return navCount + repo <= 2
+    }
+  },
+
+  methods: {
+    getPageLocalePath (page) {
+      for (const localePath in this.$site.locales || {}) {
+        if (localePath !== '/' && page.path.indexOf(localePath) === 0) {
+          return localePath
+        }
+      }
+      return '/'
+    },
+
+    onUp () {
+      if (this.showSuggestions) {
+        if (this.focusIndex > 0) {
+          this.focusIndex--
+        } else {
+          this.focusIndex = this.suggestions.length - 1
+        }
+      }
+    },
+
+    onDown () {
+      if (this.showSuggestions) {
+        if (this.focusIndex < this.suggestions.length - 1) {
+          this.focusIndex++
+        } else {
+          this.focusIndex = 0
+        }
+      }
+    },
+
+    go (i) {
+      if (!this.showSuggestions) {
+        return
+      }
+      this.$router.push(this.suggestions[i].path)
+      this.query = ''
+      this.focusIndex = 0
+    },
+
+    focus (i) {
+      this.focusIndex = i
+    },
+
+    unfocus () {
+      this.focusIndex = -1
+    }
+  }
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.search-box
+  display inline-block
+  position relative
+  margin-left 1.5rem
+  input
+    cursor text
+    width 10rem
+    color lighten($textColor, 25%)
+    display inline-block
+    border 1px solid darken($borderColor, 10%)
+    border-radius 2rem
+    font-size 0.9rem
+    line-height 2rem
+    padding 0 0.5rem 0 2rem
+    outline none
+    transition all .2s ease
+    background #fff url(./search.svg) 0.6rem 0.5rem no-repeat
+    background-size 1rem
+    &:focus
+      cursor auto
+      border-color $accentColor
+  .suggestions
+    background #fff
+    width 20rem
+    position absolute
+    top 1.5rem
+    border 1px solid darken($borderColor, 10%)
+    border-radius 6px
+    padding 0.4rem
+    list-style-type none
+    &.align-right
+      right 0
+  .suggestion
+    line-height 1.4
+    padding 0.4rem 0.6rem
+    border-radius 4px
+    cursor pointer
+    a
+      color lighten($textColor, 35%)
+      .page-title
+        font-weight 600
+      .header
+        font-size 0.9em
+        margin-left 0.25em
+    &.focused
+      background-color #f3f4f5
+      a
+        color $accentColor
+
+@media (max-width: $MQNarrow)
+  .search-box
+    input
+      cursor pointer
+      width 0
+      border-color transparent
+      position relative
+      // left 1rem
+      &:focus
+        cursor text
+        left 0
+        width 10rem
+
+@media (max-width: $MQNarrow) and (min-width: $MQMobile)
+  .search-box
+    .suggestions
+      left 0
+
+@media (max-width: $MQMobile)
+  .search-box
+    margin-left 0
+    .suggestions
+      right 0
+
+@media (max-width: $MQMobileNarrow)
+  .search-box
+    .suggestions
+      width calc(100vw - 4rem)
+    input:focus
+      width 8rem
+</style>
diff --git a/docs/.vuepress/theme/src/Sidebar.vue b/docs/.vuepress/theme/src/Sidebar.vue
new file mode 100755
index 0000000..616e328
--- /dev/null
+++ b/docs/.vuepress/theme/src/Sidebar.vue
@@ -0,0 +1,152 @@
+<template>
+  <div class="sidebar">
+    <NavLinks/>
+    <slot name="top"/>
+    <ul class="sidebar-links" v-if="items.length" ref="sidebarList">
+      <li v-for="(item, i) in items" :key="i">
+        <SidebarGroup
+          v-if="item.type === 'group'"
+          :item="item"
+          :first="i === 0"
+          :open="i === openGroupIndex"
+          :collapsable="item.collapsable"
+          @toggle="toggleGroup(i)"
+        />
+        <SidebarLink v-else :item="item"/>
+      </li>
+    </ul>
+    <slot name="bottom"/>
+  </div>
+</template>
+
+<script>
+import SidebarGroup from './SidebarGroup.vue'
+import SidebarLink from './SidebarLink.vue'
+import NavLinks from './NavLinks.vue'
+import { isActive } from './util'
+
+export default {
+  components: { SidebarGroup, SidebarLink, NavLinks },
+
+  props: ['items'],
+
+  data () {
+    return {
+      openGroupIndex: 0
+    }
+  },
+
+  created () {
+    this.refreshIndex()
+  },
+
+  mounted() {
+    this.updateEmas();
+  },
+
+  watch: {
+    '$route' () {
+      this.refreshIndex();
+      this.updateEmas();
+    }
+  },
+
+  methods: {
+    refreshIndex () {
+      const index = resolveOpenGroupIndex(
+        this.$route,
+        this.items
+      )
+      if (index > -1) {
+        this.openGroupIndex = index
+      }
+    },
+
+    toggleGroup (index) {
+      this.openGroupIndex = index === this.openGroupIndex ? -1 : index
+    },
+
+    isActive (page) {
+      return isActive(this.$route, page.path)
+    },
+
+    updateEmas() {
+      const url = window.location.href;
+      const path = this.$route.path;
+      const isEmasURL = ((url.indexOf('emas.weex.io') !== -1) || (url.indexOf('weex.emas-poc.com') !== -1));
+      const sidebarList = this.$refs.sidebarList
+      if (isEmasURL) {
+        sidebarList && (sidebarList.style.display = 'block');
+      } else {
+        if (this.$route.path.indexOf('/zh/community/') !== -1) {
+          sidebarList && (sidebarList.style.display = 'none');
+          // 打包编译时会做存在性校验,报 Cannot read property 'lastElementChild' of undefined
+          // 在社区tab下,由英文切换到中文,sidebarList.lastElementChild 并不是 EMAS DOM 节点
+          // 所以需要统筹判断一下 sidebarList.lastElementChild 是不是 EMAS DOM 节点,如果是,则直接隐藏,如果不是,则异步隐藏
+          const isEmas = (sidebarList && sidebarList.lastElementChild.innerText.indexOf('企业级服务') !== -1)
+          if (isEmas) {
+            sidebarList && (sidebarList.lastElementChild.style.display = 'none')
+            sidebarList && (sidebarList.style.display = 'block');
+          } else {
+            setTimeout(function() {
+              sidebarList && (sidebarList.lastElementChild.style.display = 'none')
+              sidebarList && (sidebarList.style.display = 'block');
+            }, 0) 
+          }
+        } else {
+          if (sidebarList && sidebarList.lastElementChild.style.display === 'none') {
+            sidebarList.lastElementChild.style.display = 'block';
+          }
+          sidebarList && (sidebarList.style.display = 'block');
+        }
+      }
+    }
+  }
+}
+
+function resolveOpenGroupIndex (route, items) {
+  for (let i = 0; i < items.length; i++) {
+    const item = items[i]
+    if (item.type === 'group' && item.children.some(c => isActive(route, c.path))) {
+      return i
+    }
+  }
+  return -1
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.sidebar
+  ul
+    padding 0
+    margin 0
+    list-style-type none
+  a
+    display inline-block
+  .nav-links
+    display none
+    border-bottom 1px solid $borderColor
+    padding 0.5rem 0 0.75rem 0
+    a
+      font-weight 600
+    .nav-item, .repo-link
+      display block
+      line-height 1.25rem
+      font-size 1.1em
+      padding 0.5rem 0 0.5rem 1.5rem
+  .sidebar-links
+    display none
+    padding 1.5rem 0
+
+@media (max-width: $MQMobile)
+  .sidebar
+    .nav-links
+      display block
+      .dropdown-wrapper .nav-dropdown .dropdown-item a.router-link-active::after
+        top calc(1rem - 2px)
+    .sidebar-links
+      display none
+      padding 1rem 0
+</style>
diff --git a/docs/.vuepress/theme/src/SidebarButton.vue b/docs/.vuepress/theme/src/SidebarButton.vue
new file mode 100755
index 0000000..0a22243
--- /dev/null
+++ b/docs/.vuepress/theme/src/SidebarButton.vue
@@ -0,0 +1,28 @@
+<template>
+  <div class="sidebar-button" @click="$emit('toggle-sidebar')">
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512">
+      <path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z" class=""></path>
+    </svg>
+  </div>
+</template>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.sidebar-button
+  display none
+  width 1.25rem
+  height 1.25rem
+  position absolute
+  padding 0.6rem
+  top 0.6rem
+  left 1rem
+  .icon
+    display block
+    width 1.25rem
+    height 1.25rem
+
+@media (max-width: $MQMobile)
+  .sidebar-button
+    display block
+</style>
diff --git a/docs/.vuepress/theme/src/SidebarGroup.vue b/docs/.vuepress/theme/src/SidebarGroup.vue
new file mode 100755
index 0000000..119dfa1
--- /dev/null
+++ b/docs/.vuepress/theme/src/SidebarGroup.vue
@@ -0,0 +1,77 @@
+<template>
+  <div
+    class="sidebar-group"
+    :class="{ first, collapsable }"
+  >
+    <p
+      class="sidebar-heading"
+      :class="{ open }"
+      @click="$emit('toggle')"
+    >
+      <span>{{ item.title }}</span>
+      <span
+        class="arrow"
+        v-if="collapsable"
+        :class="open ? 'down' : 'right'">
+      </span>
+    </p>
+
+    <DropdownTransition>
+      <ul
+        ref="items"
+        class="sidebar-group-items"
+        v-if="open || !collapsable"
+      >
+        <li v-for="child in item.children">
+          <SidebarLink :item="child"/>
+        </li>
+      </ul>
+    </DropdownTransition>
+  </div>
+</template>
+
+<script>
+import SidebarLink from './SidebarLink.vue'
+import DropdownTransition from './DropdownTransition.vue'
+
+export default {
+  name: 'SidebarGroup',
+  props: ['item', 'first', 'open', 'collapsable'],
+  components: { SidebarLink, DropdownTransition }
+}
+</script>
+
+<style lang="stylus">
+.sidebar-group
+  &:not(.first)
+    margin-top 1em
+  .sidebar-group
+    padding-left 0.5em
+  &:not(.collapsable)
+    .sidebar-heading
+      cursor auto
+      color inherit
+
+.sidebar-heading
+  color #999
+  transition color .15s ease
+  cursor pointer
+  font-size 1.1em
+  font-weight bold
+  // text-transform uppercase
+  padding 0 1.5rem
+  margin-top 0
+  margin-bottom 0.5rem
+  &.open, &:hover
+    color inherit
+  .arrow
+    position relative
+    top -0.12em
+    left 0.5em
+  &:.open .arrow
+    top -0.18em
+
+.sidebar-group-items
+  transition height .1s ease-out
+  overflow hidden
+</style>
diff --git a/docs/.vuepress/theme/src/SidebarLink.vue b/docs/.vuepress/theme/src/SidebarLink.vue
new file mode 100755
index 0000000..8288bf9
--- /dev/null
+++ b/docs/.vuepress/theme/src/SidebarLink.vue
@@ -0,0 +1,91 @@
+<script>
+import { isActive, hashRE, groupHeaders } from './util'
+
+export default {
+  functional: true,
+
+  props: ['item'],
+
+  render (h, { parent: { $page, $site, $route }, props: { item }}) {
+    // use custom active class matching logic
+    // due to edge case of paths ending with / + hash
+    const selfActive = isActive($route, item.path)
+    // for sidebar: auto pages, a hash link should be active if one of its child
+    // matches
+    const active = item.type === 'auto'
+      ? selfActive || item.children.some(c => isActive($route, item.basePath + '#' + c.slug))
+      : selfActive
+    const link = renderLink(h, item.path, item.title || item.path, active)
+    const configDepth = $page.frontmatter.sidebarDepth != null
+      ? $page.frontmatter.sidebarDepth
+      : $site.themeConfig.sidebarDepth
+    const maxDepth = configDepth == null ? 1 : configDepth
+    const displayAllHeaders = !!$site.themeConfig.displayAllHeaders
+    if (item.type === 'auto') {
+      return [link, renderChildren(h, item.children, item.basePath, $route, maxDepth)]
+    } else if ((active || displayAllHeaders) && item.headers && !hashRE.test(item.path)) {
+      const children = groupHeaders(item.headers)
+      return [link, renderChildren(h, children, item.path, $route, maxDepth)]
+    } else {
+      return link
+    }
+  }
+}
+
+function renderLink (h, to, text, active) {
+  return h('router-link', {
+    props: {
+      to,
+      activeClass: '',
+      exactActiveClass: ''
+    },
+    class: {
+      active,
+      'sidebar-link': true
+    }
+  }, text)
+}
+
+function renderChildren (h, children, path, route, maxDepth, depth = 1) {
+  if (!children || depth > maxDepth) return null
+  return h('ul', { class: 'sidebar-sub-headers' }, children.map(c => {
+    const active = isActive(route, path + '#' + c.slug)
+    return h('li', { class: 'sidebar-sub-header' }, [
+      renderLink(h, path + '#' + c.slug, c.title, active),
+      renderChildren(h, c.children, path, route, maxDepth, depth + 1)
+    ])
+  }))
+}
+</script>
+
+<style lang="stylus">
+@import './styles/config.styl'
+
+.sidebar .sidebar-sub-headers
+  padding-left 1rem
+  font-size 0.95em
+
+a.sidebar-link
+  font-weight 400
+  display inline-block
+  color $textColor
+  border-left 0.25rem solid transparent
+  padding 0.35rem 1rem 0.35rem 1.25rem
+  line-height 1.4
+  width: 100%
+  box-sizing: border-box
+  &:hover
+    color $accentColor
+  &.active
+    font-weight 600
+    color $accentColor
+    border-left-color $accentColor
+  .sidebar-group &
+    padding-left 2rem
+  .sidebar-sub-headers &
+    padding-top 0.25rem
+    padding-bottom 0.25rem
+    border-left none
+    &.active
+      font-weight 500
+</style>
diff --git a/docs/.vuepress/theme/src/github.svg b/docs/.vuepress/theme/src/github.svg
new file mode 100644
index 0000000..34d5df1
--- /dev/null
+++ b/docs/.vuepress/theme/src/github.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:#A3ABBE;}
+	.st1{fill:#FFFFFF;}
+</style>
+<g>
+	<circle class="st0" cx="506.7" cy="512.2" r="448"/>
+	<path class="st1" d="M744.9,360.2c34.2-37.7,0-113.2,0-113.2c-39.5-5.4-118.6,46.7-118.6,46.7c-97.1-23.4-226.5,0-226.5,0
+		c-60.2-50.3-123.1-51.2-123.1-51.2c-43.1,61.1-5.4,119.5-5.4,119.5c-60.2,53.5-52.1,132.1-52.1,132.1
+		C230,715.2,426.8,714.3,426.8,714.3c-21.6,8.1-42.2,62-42.2,62h-68.3c-22.5,1.8-51.2-51.2-51.2-51.2
+		c-20.7-35.1-61.6-35.7-61.6-35.7c-25.1-0.2-25.7,6.6-21.5,12.6c3.3,4.6,15.6,10.4,18.5,12c31,15.9,52,75.8,52,75.8
+		c18,44.9,66.5,46.7,66.5,46.7h80.9v110.7c34.3,8.4,70.1,12.9,106.9,12.9c38.8,0,76.4-4.9,112.3-14.2l-1.7-183.1
+		c-1.8-32.4-34.2-52.1-34.2-52.1c221.1-9,210.3-212.1,210.3-212.1C795.2,419.6,744.9,360.2,744.9,360.2z"/>
+</g>
+</svg>
diff --git a/docs/.vuepress/theme/src/search.svg b/docs/.vuepress/theme/src/search.svg
new file mode 100755
index 0000000..03d8391
--- /dev/null
+++ b/docs/.vuepress/theme/src/search.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg" width="12" height="13"><g stroke-width="2" stroke="#aaa" fill="none"><path d="M11.29 11.71l-4-4"/><circle cx="5" cy="5" r="4"/></g></svg>
diff --git a/docs/.vuepress/theme/src/styles/arrow.styl b/docs/.vuepress/theme/src/styles/arrow.styl
new file mode 100755
index 0000000..20bffc0
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/arrow.styl
@@ -0,0 +1,22 @@
+@require './config'
+
+.arrow
+  display inline-block
+  width 0
+  height 0
+  &.up
+    border-left 4px solid transparent
+    border-right 4px solid transparent
+    border-bottom 6px solid $arrowBgColor
+  &.down
+    border-left 4px solid transparent
+    border-right 4px solid transparent
+    border-top 6px solid $arrowBgColor
+  &.right
+    border-top 4px solid transparent
+    border-bottom 4px solid transparent
+    border-left 6px solid $arrowBgColor
+  &.left
+    border-top 4px solid transparent
+    border-bottom 4px solid transparent
+    border-right 6px solid $arrowBgColor
diff --git a/docs/.vuepress/theme/src/styles/code.styl b/docs/.vuepress/theme/src/styles/code.styl
new file mode 100755
index 0000000..a4b53a0
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/code.styl
@@ -0,0 +1,137 @@
+@require './config'
+
+.content
+  code
+    color $codeTextColor
+    padding 0.25rem 0.5rem
+    margin 0
+    font-size 0.85em
+    background-color rgba(27,31,35,0.05)
+    border-radius 3px
+
+.content
+  pre, pre[class*="language-"]
+    line-height 1.4
+    padding 1.25rem 1.5rem
+    margin 0.85rem 0
+    background-color $codeBgColor
+    border-radius 6px
+    overflow auto
+    code
+      color #525252
+      padding 0
+      background-color transparent
+      border-radius 0
+
+div[class*="language-"]
+  position relative
+  background-color $codeBgColor
+  border-radius 6px
+  .highlight-lines
+    user-select none
+    padding-top 1.3rem
+    position absolute
+    top 0
+    left 0
+    width 100%
+    line-height 1.4
+    .highlighted
+      background-color rgba(0, 0, 0, 66%)
+  pre, pre[class*="language-"]
+    background transparent
+    position relative
+    z-index 1
+  &::before
+    position absolute
+    z-index 3
+    top 0.8em
+    right 1em
+    font-size 0.75rem
+    color rgba(255, 255, 255, 0.4)
+  &:not(.line-numbers-mode)
+    .line-numbers-wrapper
+      display none
+  &.line-numbers-mode
+    .highlight-lines .highlighted
+        position relative
+        &:before
+          content ' '
+          position absolute
+          z-index 3
+          left 0
+          top 0
+          display block
+          width $lineNumbersWrapperWidth
+          height 100%
+          background-color rgba(0, 0, 0, 66%)
+    pre
+      padding-left $lineNumbersWrapperWidth + 1 rem
+      vertical-align middle
+    .line-numbers-wrapper
+      position absolute
+      top 0
+      width $lineNumbersWrapperWidth
+      text-align center
+      color rgba(255, 255, 255, 0.3)
+      padding 1.25rem 0
+      line-height 1.4
+      br
+        user-select none
+      .line-number
+        position relative
+        z-index 4
+        user-select none
+        font-size 0.85em
+    &::after
+      content ''
+      position absolute
+      z-index 2
+      top 0
+      left 0
+      width $lineNumbersWrapperWidth
+      height 100%
+      border-radius 6px 0 0 6px
+      border-right 1px solid rgba(0, 0, 0, 66%)
+      background-color $codeBgColor
+
+
+for lang in js ts html md vue css sass scss less stylus go java c sh yaml
+  div{'[class~="language-' + lang + '"]'}
+    &:before
+      content ('' + lang)
+
+div[class~="language-javascript"]
+  &:before
+    content "js"
+
+div[class~="language-typescript"]
+  &:before
+    content "ts"
+
+div[class~="language-markup"]
+  &:before
+    content "html"
+
+div[class~="language-markdown"]
+  &:before
+    content "md"
+
+div[class~="language-json"]:before
+  content "json"
+
+div[class~="language-ruby"]:before
+  content "rb"
+
+div[class~="language-python"]:before
+  content "py"
+
+div[class~="language-bash"]:before
+  content "sh"
+
+.token
+  color #5081da
+.token.punctuation
+  color #828282
+
+div[class*="language-"] .highlight-lines .highlighted
+  background-color: rgba(220, 220, 220, 0.66);
\ No newline at end of file
diff --git a/docs/.vuepress/theme/src/styles/config.styl b/docs/.vuepress/theme/src/styles/config.styl
new file mode 100755
index 0000000..ddcd699
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/config.styl
@@ -0,0 +1,22 @@
+// colors
+$accentColor = #00B4FF
+$textColor = #2C3E50
+$borderColor = #eaecef
+$codeBgColor = #F8F9FB
+$arrowBgColor = #ccc
+$codeTextColor = #e96900
+
+// layout
+$navbarHeight = 3.6rem
+$sidebarWidth = 20rem
+$contentWidth = 53rem
+
+// responsive breakpoints
+$MQNarrow = 959px
+$MQMobile = 719px
+$MQMobileNarrow = 419px
+
+// code
+$lineNumbersWrapperWidth = 3.5rem
+
+@import '~@temp/override.styl'
diff --git a/docs/.vuepress/theme/src/styles/custom-blocks.styl b/docs/.vuepress/theme/src/styles/custom-blocks.styl
new file mode 100755
index 0000000..3ccc2df
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/custom-blocks.styl
@@ -0,0 +1,28 @@
+.custom-block
+  .custom-block-title
+    font-weight 600
+    margin-bottom -0.4rem
+  &.tip, &.warning, &.danger
+    padding .1rem 1.5rem
+    border-left-width .5rem
+    border-left-style solid
+    margin 1rem 0
+  &.tip
+    background-color #f3f5f7
+    border-color #42b983
+  &.warning
+    background-color rgba(255,229,100,.3)
+    border-color darken(#ffe564, 35%)
+    color darken(#ffe564, 70%)
+    .custom-block-title
+      color darken(#ffe564, 50%)
+    a
+      color $textColor
+  &.danger
+    background-color #ffe6e6
+    border-color darken(red, 20%)
+    color darken(red, 70%)
+    .custom-block-title
+      color darken(red, 40%)
+    a
+      color $textColor
diff --git a/docs/.vuepress/theme/src/styles/mobile.styl b/docs/.vuepress/theme/src/styles/mobile.styl
new file mode 100755
index 0000000..7758305
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/mobile.styl
@@ -0,0 +1,41 @@
+@require './config'
+
+$mobileSidebarWidth = $sidebarWidth * 0.82
+
+// narrow desktop / iPad
+@media (max-width: $MQNarrow)
+  .sidebar
+    font-size 15px
+    width $mobileSidebarWidth
+  .page
+    padding-right 2rem
+    // padding-left $mobileSidebarWidth
+
+// wide mobile
+@media (max-width: $MQMobile)
+  .sidebar
+    top 0
+    padding-top $navbarHeight
+    transform translateX(-100%)
+    transition transform .2s ease
+  .page
+    padding-left 0
+    padding-right 0
+  .content:not(.custom)
+      margin-left 0
+  .theme-container
+    &.sidebar-open
+      .sidebar
+        transform translateX(0)
+    &.no-navbar
+      .sidebar
+        padding-top: 0
+
+// narrow mobile
+@media (max-width: $MQMobileNarrow)
+  h1
+    font-size 1.9rem
+  .content
+    div[class*="language-"]
+      margin 0.85rem -1.5rem
+      border-radius 0
diff --git a/docs/.vuepress/theme/src/styles/nprogress.styl b/docs/.vuepress/theme/src/styles/nprogress.styl
new file mode 100755
index 0000000..f186a67
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/nprogress.styl
@@ -0,0 +1,48 @@
+#nprogress
+  pointer-events none
+  .bar
+    background $accentColor
+    position fixed
+    z-index 1031
+    top 0
+    left 0
+    width 100%
+    height 2px
+  .peg
+    display block
+    position absolute
+    right 0px
+    width 100px
+    height 100%
+    box-shadow 0 0 10px $accentColor, 0 0 5px $accentColor
+    opacity 1.0
+    transform rotate(3deg) translate(0px, -4px)
+  .spinner
+    display block
+    position fixed
+    z-index 1031
+    top 15px
+    right 15px
+  .spinner-icon
+    width 18px
+    height 18px
+    box-sizing border-box
+    border solid 2px transparent
+    border-top-color $accentColor
+    border-left-color $accentColor
+    border-radius 50%
+    animation nprogress-spinner 400ms linear infinite
+
+.nprogress-custom-parent
+  overflow hidden
+  position relative
+
+.nprogress-custom-parent #nprogress .spinner,
+.nprogress-custom-parent #nprogress .bar
+  position absolute
+
+@keyframes nprogress-spinner
+  0%
+    transform rotate(0deg)
+  100%
+    transform rotate(360deg)
diff --git a/docs/.vuepress/theme/src/styles/theme.styl b/docs/.vuepress/theme/src/styles/theme.styl
new file mode 100755
index 0000000..72748c3
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/theme.styl
@@ -0,0 +1,199 @@
+@require './config'
+@require './nprogress'
+@require './code'
+@require './custom-blocks'
+@require './arrow'
+@require './wrapper'
+@require './toc'
+
+html, body
+  padding 0
+  margin 0
+
+body
+  // font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif
+  font-family Roboto, "Helvetica Neue", Helvetica, Tahoma, Arial, FZLanTingHeiS-L-G, "PingFang SC", "Microsoft YaHei"
+  -webkit-font-smoothing antialiased
+  -moz-osx-font-smoothing grayscale
+  font-size 15px
+  color $textColor
+
+.page
+  padding-left 4rem
+  padding-right 4rem
+
+.navbar
+  position fixed
+  z-index 20
+  top 0
+  left 0
+  right 0
+  height $navbarHeight
+  background-color #fff
+  box-sizing border-box
+  box-shadow 0 3px 12px 0 rgba(0,0,0,0.10);
+
+.sidebar-mask
+  position fixed
+  z-index 9
+  top 0
+  left 0
+  width 100vw
+  height 100vh
+  display none
+
+.sidebar
+  font-size 15px
+  background-color #fff
+  width $sidebarWidth
+  position fixed
+  z-index 10
+  margin 0
+  top $navbarHeight
+  left 0
+  bottom 0
+  box-sizing border-box
+  border-right 1px solid #F2F3F4
+  overflow-y auto
+
+.content:not(.custom)
+  @extend $wrapper
+  > *:first-child
+    margin-top $navbarHeight
+  a:hover
+    text-decoration underline
+  p.demo
+    padding 1rem 1.5rem
+    border 1px solid #ddd
+    border-radius 4px
+  img
+    max-width 100%
+
+.content.custom
+  padding 0
+  margin 0
+  img
+    max-width 100%
+
+a
+  font-weight 500
+  color $accentColor
+  text-decoration none
+
+p a code
+  font-weight 400
+  color $accentColor
+
+kbd
+  background #eee
+  border solid 0.15rem #ddd
+  border-bottom solid 0.25rem #ddd
+  border-radius 0.15rem
+  padding 0 0.15em
+
+blockquote
+  font-size 1.2rem
+  color #999
+  border-left .25rem solid #dfe2e5
+  margin-left 0
+  padding-left 1rem
+
+ul, ol
+  padding-left 1.2em
+
+strong
+  font-weight 600
+
+h1, h2, h3, h4, h5, h6
+  font-weight 600
+  line-height 1.25
+  color #04152C
+  .content:not(.custom) > &
+    margin-top (0.5rem - $navbarHeight)
+    padding-top ($navbarHeight + 1rem)
+    margin-bottom 0
+    &:first-child
+      margin-top -1.5rem
+      margin-bottom 1rem
+      + p, + pre, + .custom-block
+        margin-top 2rem
+  &:hover .header-anchor
+    opacity: 1
+
+h1
+  font-size 1.5rem
+
+h2
+  padding-top 6.6rem
+  font-size 1rem
+  padding-bottom .3rem
+  border-bottom 1px solid $borderColor
+
+h3
+  font-size 0.8rem
+
+a.header-anchor
+  font-size 0.85em
+  float left
+  margin-left -0.87em
+  padding-right 0.23em
+  margin-top 0.125em
+  opacity 0
+  &:hover
+    text-decoration none
+
+code, kbd, .line-number
+  font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace
+
+p, ul, ol
+  line-height 25px
+
+hr
+  border 0
+  border-top 1px solid $borderColor
+
+table
+  border-collapse collapse
+  margin 1rem 0
+  display: block
+  overflow-x: auto
+
+tr
+  border-top 1px solid #dfe2e5
+  &:nth-child(2n)
+    background-color #f6f8fa
+
+th, td
+  border 1px solid #dfe2e5
+  padding .6em 1em
+
+.custom-layout
+  padding-top $navbarHeight
+
+.theme-container
+  &.sidebar-open
+    .sidebar-mask
+      display: block
+  &.no-navbar
+    .content:not(.custom) > h1, h2, h3, h4, h5, h6
+        margin-top 1.5rem
+        padding-top 0
+    .sidebar
+      top 0
+    .custom-layout
+      padding-top 0
+
+@media screen and (max-width: 1400px)
+  .content:not(.custom)
+      margin-left 12rem
+
+
+@media (min-width: ($MQMobile + 1px))
+  .theme-container.no-sidebar
+    .sidebar
+      display none
+    .page
+      padding-left 0
+      padding-right 0
+
+@require './mobile.styl'
diff --git a/docs/.vuepress/theme/src/styles/toc.styl b/docs/.vuepress/theme/src/styles/toc.styl
new file mode 100755
index 0000000..d3e7106
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/toc.styl
@@ -0,0 +1,3 @@
+.table-of-contents
+  .badge
+    vertical-align middle
diff --git a/docs/.vuepress/theme/src/styles/wrapper.styl b/docs/.vuepress/theme/src/styles/wrapper.styl
new file mode 100755
index 0000000..cb7f6f8
--- /dev/null
+++ b/docs/.vuepress/theme/src/styles/wrapper.styl
@@ -0,0 +1,11 @@
+$wrapper
+  max-width $contentWidth
+  margin 0 auto
+  padding 2rem 2.5rem
+  @media screen and (max-width: 1400px)
+    margin-left 12rem
+  @media (max-width: $MQNarrow)
+    padding 2rem
+  @media (max-width: $MQMobileNarrow)
+    padding 1.5rem
+
diff --git a/docs/.vuepress/theme/src/util.js b/docs/.vuepress/theme/src/util.js
new file mode 100755
index 0000000..b2ade1c
--- /dev/null
+++ b/docs/.vuepress/theme/src/util.js
@@ -0,0 +1,243 @@
+export const hashRE = /#.*$/
+export const extRE = /\.(md|html)$/
+export const endingSlashRE = /\/$/
+export const outboundRE = /^(https?:|mailto:|tel:)/
+
+export function normalize (path) {
+  return decodeURI(path)
+    .replace(hashRE, '')
+    .replace(extRE, '')
+}
+
+export function getHash (path) {
+  const match = path.match(hashRE)
+  if (match) {
+    return match[0]
+  }
+}
+
+export function isExternal (path) {
+  return outboundRE.test(path)
+}
+
+export function isMailto (path) {
+  return /^mailto:/.test(path)
+}
+
+export function isTel (path) {
+  return /^tel:/.test(path)
+}
+
+export function ensureExt (path) {
+  if (isExternal(path)) {
+    return path
+  }
+  const hashMatch = path.match(hashRE)
+  const hash = hashMatch ? hashMatch[0] : ''
+  const normalized = normalize(path)
+
+  if (endingSlashRE.test(normalized)) {
+    return path
+  }
+  return normalized + '.html' + hash
+}
+
+export function isActive (route, path) {
+  const routeHash = route.hash
+  const linkHash = getHash(path)
+  if (linkHash && routeHash !== linkHash) {
+    return false
+  }
+  const routePath = normalize(route.path)
+  const pagePath = normalize(path)
+  return routePath === pagePath
+}
+
+export function resolvePage (pages, rawPath, base) {
+  if (base) {
+    rawPath = resolvePath(rawPath, base)
+  }
+  const path = normalize(rawPath)
+  for (let i = 0; i < pages.length; i++) {
+    if (normalize(pages[i].path) === path) {
+      return Object.assign({}, pages[i], {
+        type: 'page',
+        path: ensureExt(rawPath)
+      })
+    }
+  }
+  console.error(`[vuepress] No matching page found for sidebar item "${rawPath}"`)
+  return {}
+}
+
+function resolvePath (relative, base, append) {
+  const firstChar = relative.charAt(0)
+  if (firstChar === '/') {
+    return relative
+  }
+
+  if (firstChar === '?' || firstChar === '#') {
+    return base + relative
+  }
+
+  const stack = base.split('/')
+
+  // remove trailing segment if:
+  // - not appending
+  // - appending to trailing slash (last segment is empty)
+  if (!append || !stack[stack.length - 1]) {
+    stack.pop()
+  }
+
+  // resolve relative path
+  const segments = relative.replace(/^\//, '').split('/')
+  for (let i = 0; i < segments.length; i++) {
+    const segment = segments[i]
+    if (segment === '..') {
+      stack.pop()
+    } else if (segment !== '.') {
+      stack.push(segment)
+    }
+  }
+
+  // ensure leading slash
+  if (stack[0] !== '') {
+    stack.unshift('')
+  }
+
+  return stack.join('/')
+}
+
+export function resolveSidebarItems (page, route, site, localePath) {
+  const { pages, themeConfig } = site
+
+  const localeConfig =
+    localePath && themeConfig.locales
+      ? themeConfig.locales[localePath] || themeConfig
+      : themeConfig
+
+  const pageSidebarConfig =
+    page.frontmatter.sidebar || localeConfig.sidebar || themeConfig.sidebar
+  if (pageSidebarConfig === 'auto') {
+    return resolveHeaders(page)
+  }
+
+  const sidebarConfig = localeConfig.sidebar || themeConfig.sidebar
+  if (!sidebarConfig) {
+    return []
+  } else {
+    const { base, config } = resolveMatchingConfig(route, sidebarConfig)
+    return config ? config.map(item => resolveItem(item, pages, base)) : []
+  }
+}
+
+function resolveHeaders (page) {
+  const headers = groupHeaders(page.headers || [])
+  return [
+    {
+      type: 'group',
+      collapsable: false,
+      title: page.title,
+      children: headers.map(h => ({
+        type: 'auto',
+        title: h.title,
+        basePath: page.path,
+        path: page.path + '#' + h.slug,
+        children: h.children || []
+      }))
+    }
+  ]
+}
+
+export function groupHeaders (headers) {
+  // group h3s under h2
+  headers = headers.map(h => Object.assign({}, h))
+  let lastH2
+  headers.forEach(h => {
+    if (h.level === 2) {
+      lastH2 = h
+    } else if (lastH2) {
+      ;(lastH2.children || (lastH2.children = [])).push(h)
+    }
+  })
+  return headers.filter(h => h.level === 2)
+}
+
+export function resolveNavLinkItem (linkItem) {
+  return Object.assign(linkItem, {
+    type: linkItem.items && linkItem.items.length ? 'links' : 'link'
+  })
+}
+
+export function resolveMatchingConfig (route, config) {
+  if (Array.isArray(config)) {
+    return {
+      base: '/',
+      config: config
+    }
+  }
+  for (const base in config) {
+    if (ensureEndingSlash(route.path).indexOf(base) === 0) {
+      return {
+        base,
+        config: config[base]
+      }
+    }
+  }
+  return {}
+}
+
+function ensureEndingSlash (path) {
+  return /(\.html|\/)$/.test(path) ? path : path + '/'
+}
+
+function resolveItem (item, pages, base, isNested) {
+  if (typeof item === 'string') {
+    return resolvePage(pages, item, base)
+  } else if (Array.isArray(item)) {
+    return Object.assign(resolvePage(pages, item[0], base), {
+      title: item[1]
+    })
+  } else {
+    if (isNested) {
+      console.error(
+        '[vuepress] Nested sidebar groups are not supported. ' +
+          'Consider using navbar + categories instead.'
+      )
+    }
+    const children = item.children || []
+    return {
+      type: 'group',
+      title: item.title,
+      children: children.map(child => resolveItem(child, pages, base, true)),
+      collapsable: item.collapsable !== false
+    }
+  }
+}
+
+export const setSpm = spm => {
+  if (!spm) return
+
+  const spmArr = spm.split('.')
+  if (spmArr.length != 2) {
+    console.log('setSpm传入参数不合法, 正确格式: a位.b位')
+    return
+  }
+  const spmA = spmArr[0]
+  const spmB = spmArr[1].replace(/\//g, '-')
+
+  const q = window.goldlog_queue || (window.goldlog_queue = [])
+  q.push({
+    action: 'goldlog.setPageSPM',
+    arguments: [spmA, spmB]
+  })
+  q.push({
+    action: 'goldlog.sendPV',
+    arguments: [
+      // 必填的配置项
+      {
+        is_auto: false
+      }
+    ]
+  })
+}
diff --git a/docs/.vuepress/theme/src/version-bg.svg b/docs/.vuepress/theme/src/version-bg.svg
new file mode 100644
index 0000000..8c5e0f2
--- /dev/null
+++ b/docs/.vuepress/theme/src/version-bg.svg
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="42px" height="15px" viewBox="0 0 42 15" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+    <!-- Generator: Sketch 52.3 (67297) - http://www.bohemiancoding.com/sketch -->
+    <title>Group 15</title>
+    <desc>Created with Sketch.</desc>
+    <g id="weex" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="工具-1" transform="translate(-111.000000, -32.000000)" fill="#515151">
+            <g id="Group-5">
+                <g id="Group-16" transform="translate(110.000000, 32.000000)">
+                    <g id="Group-15">
+                        <rect id="Rectangle-24" x="4" y="0" width="39" height="15" rx="7.5"></rect>
+                        <polygon id="Path-2" points="13.5593872 15.0015869 0.988769531 15.0015869 7.25271206 12.3242188"></polygon>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/package.json b/package.json
index cb137c1..1ccc049 100644
--- a/package.json
+++ b/package.json
@@ -20,7 +20,8 @@
   "homepage": "https://github.com/apache/incubator-weex-site#readme",
   "devDependencies": {
     "vuepress": "^0.14.11",
-    "vuepress-theme-fast": "0.0.11"
+    "nprogress": "^0.2.0",
+    "workbox-build": "^3.1.0"
   },
   "dependencies": {
     "axios": "^0.18.0"