# 迁移 Vue 项目到 CML

# 迁移原则:

以小逻辑块为单位,对照老代码,以 cml 语法重写,报错可修正

这样可避免以下问题:

1、大块逻辑迁移会导致报错无法追查

2、直接 copy 会导致语法隐藏 bug 不可控(虽然 ide 没有问题,但真机调试出问题)

请尽量按照 cml 语法或者类 vue 语法重写迁移,避免语法纠错浪费时间

# 项目初始化

cml init project

初始化后,CML 项目如下:

依具体情况配置构建平台和配置平台基础样式。

可修改 chameleon.config.jsplatformsbaseStyle 字段,如下:

假设有下面 👇 结构的 vue 项目(vue-cli 2 版本生成的)

components 下包含各个组件代码,router 下是路由配置,store 是数据管理中心,config 和 build 下是 vue 项目的 webpack 构建的基本配置

接下来就一步步展示如何将这个项目迁移到 CML

# 工程层面的迁移

# 迁移 —— webpack 配置

CML 的工程配置具体参考

CML 命令行工具,提供了 dev build 两种构建模式,可以对应到 Vue 项目中的 dev build

vue 项目 CML 项目
npm run dev cml dev
npm run build cml build

chameleon 内置了对于 webpack 和项目的构建,参考这里修改 CML 内置 webpack 构建

# 迁移 —— store

CML 中的 store 使用参考

CML 项目中的store和 vue 项目中的store文件下是对应的;

假设 vue 项目中某个组件

import { mapState } from 'vuex';
export default {
  computed: mapState(['count']),
};

那么在 CML 项目中

import store from '../path/to/store';

class Index {
  computed = store.mapState(['count']);
}
export default new Index();

# 迁移 —— router

router-view出口的的对应关系

假设vue项目中入口文件 src/App.vue

<template>
  <div id="app">
    <router-view />
  </div>
</template>

那么对应着cml项目中的src/app/app.cml,这里的<app>会渲染成<router-view>对应的某个路由;

<template>
  <app store="{{store}}" router-config="{{routerConfig}}"></app>
</template>

路由配置对应关系

vue项目中的路由 src/router/index.js

import Vue from 'vue';
import Router from 'vue-router';
import HelloWorld from '@/components/HelloWorld';

Vue.use(Router);

export default new Router({
  routes: [
    {
      path: '/helloworld',
      name: 'HelloWorld',
      component: HelloWorld,
    },
  ],
});

对于router.js中配置的一级路由,需要通过 cml init page 去生成对应的组件

cml项目src/router.config.json

{
  "mode": "history",
  "domain": "https://www.chameleon.com",
  "routes":[
    {
      "url": "/helloworld",
      "path": "/pages/HelloWorld/HelloWorld",
      "name": "helloworld",
      "mock": "index.php"
    }
  ]
}

其中:

url字段 对应 vue 中的 path 字段;

path字段对应着 vue 中 import Comp from '/path/to/Comp'中的组件路径

chameleon 会自动引入 component 字段配置的组件,不需要再配置 component 字段;

总结

1 注意 CML 项目中不支持路由嵌套,如果有路由嵌套的情况需要考虑转化成组件去实现

2 在迁移路由的时候,要一个一个路由对应着去迁移

3 vue 项目中的一级路由的组件都通过 cml init page去初始化这个组件

# 迁移页面/组件

假如 vue 项目中 src/components/HelloWorld.vue组件内有个子组件 comp;

首先我们修改下这两个组件,使其有一些简单的新增 todolist 的功能

HelloWorld.vue

<template>
  <div class="demo-com">
    <div class="title">this is helloworld</div>
    <comp @parentClick="handleParentClick"></comp>
  </div>
</template>

<script>
import lodash from 'lodash';

import comp from './comp.vue';
export default {
  name: 'HelloWorld',
  data() {
    return {};
  },
  methods: {
    handleParentClick(...args) {
      console.log('parentClick', ...args);
    },
  },
  components: {
    comp,
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.demo-com {
  display: flex;
  flex-direction: column;
  align-items: center;
  height: 400px;
  justify-content: center;
}
.title {
  align-self: center;
  color: #61c7fc;
  font-size: 72px;
  margin-bottom: 20px;
}
</style>

注意:如果第三方仓库中的某些 API 依赖该平台的全局变量,那么这些 API 只能在该平台使用,在其他平台是无效的;

comp.vue

<template>
  <div>
    <input type="text" v-model="todo" />
    <div v-for="(item, index) in todos">
      {{ item }}
    </div>
    <div @click="addTodo">addTodo</div>
    <div @click="handleClick">触发父组件事件</div>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
      todo: 'todo1',
      todos: [],
    };
  },
  methods: {
    addTodo() {
      this.todos.push(this.todo);
    },
    handleClick() {
      console.log('click');
      this.$emit('parentClick', {
        value: 1,
      });
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>

# 新建页面/组件

cml init page

输入 HelloWorld

利用命令行命令,在src/pages中生成对应的页面

<template>
  <view><text>HelloCML</text></view>
</template>

<script>
class HelloWorld {
  //...
}

export default new HelloWorld();
</script>

<style></style>

<script cml-type="json">
{
  "base": {
    "usingComponents": {}
  },
  "wx": {
    "navigationBarTitleText": "index",
    "backgroundTextStyle": "dark",
    "backgroundColor": "#E2E2E2"
  },
  "alipay": {
    "defaultTitle": "index",
    "pullRefresh": false,
    "allowsBounceVertical": "YES",
    "titleBarColor": "#ffffff"
  },
  "baidu": {
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "white",
    "navigationBarTitleText": "index",
    "backgroundColor": "#ffffff",
    "backgroundTextStyle": "dark",
    "enablePullDownRefresh": false,
    "onReachBottomDistance": 50
  }
}
</script>
cml init component
选择 Normal component
输入 comp

利用命令行命令,在src/components中生成对应的组件

<template>
  <view><text>HelloCML</text></view>
</template>

<script>
class Comp {
  //...
}

export default new Comp();
</script>
<style></style>
<script cml-type="json">
{
  "base": {
    "usingComponents": {}
  }
}
</script>

# 迁移组件引用

假设 vue 项目src/components/HelloWorld.vue中引用了其他组件 import comp from './comp.vue';

对应到 CML 项目 组件需要在 usingComponents 引用,不需要在配置 components字段

修改src/pages/HelloWorld/HelloWorld.cml 页面配置,如下:

<script cml-type="json">
{
  "base": {
    "usingComponents": {
      "comp":"/components/comp/comp"
    }
  }
}
</script>

总结

1 router.js中对应的组件需要通过 cml init page生成,然后在 router.config.js中配置对应路由

2 组件内部引用的子组件要通过cml init component 生成 ,然后通过usingComponents字段去引用

3 组件内引用的其他 js 库,比如import lodash from 'lodash'仍然通过import的形式引用

# 页面&&组件迁移细节

# template模板迁移

这里以 CML 的 Vue 语法为例:CML 类 Vue 基础语法

# 数据绑定、条件渲染、循环、事件绑定的迁移

假设,原有 vue 项目代码,如下:

<div class="scroller-wrap">
  数据绑定
  <div>{{}}</div>
  条件渲染
  <div v-if="condition">v-if</div>
  <div v-else-if="condition1">v-else-if</div>
  <div v-else>v-else</div>
  循环
  <div v-for="(item ,index) in array"></div>
  事件绑定
  <div id="tapTest" @click="handleClick">Click me!</div>
</div>

那么,使用 CML 的类 Vue 语法后:整体基本上不用变,只需要将标签改成 CML 的内置标签即可。

注意需要声明<template lang="vue"></template>

<template lang="vue">
<view class="scroller-wrap">
    数据绑定
    <view>{{}}</view>
    条件渲染
    <view v-if="condition">v-if</view>
    <view v-else-if="condition1">v-else-if</view>
    <view v-else>v-else</view>
    循环
    <view v-for="(item ,index) in array"></view>
    事件绑定
    <view id="tapTest" @click="handleClick">Click me!</view>
</view></template>

# vue 项目标签 -> cml 标签

vue 项目 cml
div view
text span text
img image
input input组件
button button组件
textarea textarea组件
switch switch组件
radio radio组件
checkbox checkbox组件
image image组件
video video组件

没有列出来的标签比如head p main等等只能在多态组件中使用,不支持跨多端

对于 a标签的 href,如果想要达到跨多端的效果,需要通过绑定事件使用 cml.open() 去跳转。

# CML 对于语法的扩展支持

指令的扩展 c-show、c-model、c-show参考

component is 动态组件的扩展参考

事件绑定支持内联事件传参数参考

# 迁移注意点

  • CML 支持的类 Vue 语法,只有在文档中列出的语法才支持多端,其他没有列出的语法仅可以在 Web 端使用,跨端没有支持,比如 v-html class 的对象语法 数组语法等。

根据以上教程,我们可以迁移 HelloWorld.vuecomp.vue 中的模板内容了

HelloWorld.cml

<template lang="vue">
  <view>
    <text>this is helloworld</text>
    <comp @parentClick="handleParentClick"></comp>
  </view>
</template>

comp.cml

<template lang='vue'>
  <view>
    <input type="text" v-model="todo" ></input>
    <div v-for="(item,index) in todos">
      {{item}}
    </div>
    <div @click="addTodo">addTodo</div>
    <view @click="handleClick"><text>触发父组件事件</text></view>
  </view>
</template>

# JS 内容迁移

# 生命周期迁移 :和 vue 保持一致

# 数据的迁移参考

# vue 项目 API 的迁移

API 迁移包括 http 请求 路由跳转 本地存储等 参考:chameleon-api 的文档

假设,原有 vue 项目代码,如下:

router.push({ path: '/pages/navigateBack/index' });

跨多端的路由仅支持 传入 path 字段进行路由,不支持路由 name字段的路由

那么,使用 CML 语法后:

import cml from 'chameleon-api';
cml.redirectTo({
  path: '/pages/navigateBack/index',
});

# 事件的触发机制,映射如下:

vue 项目 cml
this.$emit(xxx,xxx) this.$cmlEmit(xxx,xxx)

事件对象参数

CML 对 web native wx 各个端的事件对象进行了统一代理参考

对于灰度区组件(多态组件)各个端的事件对象还是对应端的事件对象,CML 框架不会对灰度区origin-开头的标签和第三方组件标签上绑定的事件进行事件代理。

事件冒泡

#chameleon 生成的 Weex 项目默认都是开启了支持事件冒泡的机制

#同时扩展了阻止事件冒泡的语法;

vue 语法(仅仅支持 .stop)

<view @click.stop="handleClick"></view>

cml 语法

<view c-catch:click="handleClick"></view>

总结

1 由于 CML 是跨多端框架,所以在 Web 端特有的全局变量,比如 window document history location等在 CML 中是不支持的

2 对于 vue 的一些全局 API 比如Vue.extend Vue.set以及一些文档中没有列出的指令,比如v-html v-pre等都是不支持跨多端的

根据以上教程,我们可以迁移HelloWorld.vuecomp.vue中的 js 内容了

HelloWorld.cml

<script>
import lodash from 'lodash';
class HelloWorld {
  methods = {
    handleParentClick(...args) {
      console.log('parentClick', ...args);
    },
  };
}

export default new HelloWorld();
</script>

comp.cml

<script>
class Comp {
  data = {
    todo: 'todo1',
    todos: [],
  };

  methods = {
    addTodo() {
      this.todos.push(this.todo);
    },
    handleClick() {
      this.$cmlEmit('parentClick', {
        value: 1,
      });
    },
  };
}

export default new Comp();
</script>

# style 内容的迁移

# 页面布局的迁移

由于 CML 应用是 跨多端 Web Native 小程序框架,如果需要跨 Native,必须使用 flexbox 进行样式布局,其他场景可以参考只跨 Web 和小程序的应用

关于样式的使用教程参考

模板上的样式语法参考

# 样式单位的迁移

如果样式想要适配多端,需要将单位改成cpx;

👉 根据以上教程,我们可以迁移HelloWorld.vuecomp.vue中的 js 内容了

HelloWorld.cml

.demo-com { display: flex; flex-direction: column; align-items: center; height:400cpx;
justify-content: center; } .title { align-self: center; color: #61c7fc; font-size: 72cpx;
margin-bottom: 20cpx; }

以上,简单的介绍了 vue 项目迁移到 CML 的步骤,如果还有任何疑问,欢迎随时在 CML 官方微信和官方 QQ 群里进行反馈,我们将随时解答你的困惑,再次感谢你对 CML 的支持~

​ Best wishes

​ CML 团队