前言
Youtube上的前端网红「Theo」在React文档仓库发起了一个Pull request,号召React文档不要再默认推荐CRA(create react app),而是应该将Vite作为构建应用的首选。 vite的影响力已经从vue蔓延到了react,可见在前端工程化开发中,它已经越来越流行,是时候该从webpack切换到vite了。
为什么使用vite
Vite 采用了基于ES Module的开发服务器。进行本地开发时,使用HMR,速度快了很多。相较于webpack,有很多优势:
- 更快的热更新机制
- 更快的打包效率
- 更简单的配置
在做kintone开发时,我也很想尝试使用vite进行开发,构建。所以也查询了一些相关文章。 但是只看到了一些介绍如何使用vite(rollup)打包kintone自定义js的文章。但是却没有看到如何利用vite进行kintone开发的文章。所以我想到开发一个vite插件来解决这个问题。
vite-plugin-kintone-dev
这个插件实现的功能:
- 支持使用vite创建kintone自定义js,hmr让你的开发快如闪电
- 支持react,vue等不同的前端框架
- 构建时支持打包并自动上传kintone
实践:kintone手机版自定义
这次我们结合vite插件,以kintone手机版的自定义开发为范例,给大家做演示。 文章有点长,大家可以提前先看下成果图:最后成果图 技术栈:vite4 + vue3 + vant4
1. 使用vite脚手架初始化vue项目
首先通过vite脚手架工具。创建一个vue项目
            
            
              sh
              
              
            
          
          npm create vue@latest设置项目名: kintone-mobile-custom(这是我的预设) 选择vue,TypeScript。然后根据需求进行选择。并进行初始化安装.
            
            
              sh
              
              
            
          
          cd kintone-mobile-custom
npm install2. 安装kintone开发的vite插件
            
            
              sh
              
              
            
          
          npm install -D vite-plugin-kintone-dev第一次启动时,会自动检查你的env文件的设置模版。如果没有配置,会启动命令行交互,让你输入配置信息。同时自动更新你的env文件。
如果你的env文件设置有误,可以自行去修改。 (serve模式下为".env.development"文件, build模式下为".env.production"文件)
插件的参数说明
            
            
              json
              
              
            
          
          {
   outputName: "mobile",   //最后打包的名字
   upload: true
}配置vite
vite.config.ts
            
            
              ts
              
              
            
          
          import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import kintoneDev from "vite-plugin-kintone-dev";
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    kintoneDev({
      outputName: "mobile",
      upload: true
    }),
  ],
});3. 安装其他的一些库
图片库
接下来我们再配置一个图片库。 非常常用的一个插件图库插件Unplugin Icons github.com/unplugin/un...
            
            
              sh
              
              
            
          
          npm install -D unplugin-icons unplugin-vue-components它的具体设置,请参考它的官网。
添加所有icon资源,不用担心,真正打包时,它是按需加载的。
            
            
              sh
              
              
            
          
          npm i -D @iconify/json手机ui库
然后我们使用vant这个库来做为手机开发的ui库。 vant-ui.github.io/vant/#/en-U...
            
            
              sh
              
              
            
          
          npm install vanttsconfig.app.json配置
tsconfig.app.json
            
            
              json
              
              
            
          
          ...
"compilerOptions": {
    "types": ["unplugin-icons/types/vue"],
    ...
}
...vite的最后配置
vite.config.ts
            
            
              ts
              
              
            
          
          import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import Icons from "unplugin-icons/vite";
import IconsResolver from "unplugin-icons/resolver";
import Components from "unplugin-vue-components/vite";
import { FileSystemIconLoader } from "unplugin-icons/loaders";
import kintoneDev from "vite-plugin-kintone-dev";
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    kintoneDev({
      platform: "PORTAL",
      type: "MOBILE",
    }),
    vue(),
    Components({
      resolvers: [IconsResolver()],
    }),
    Icons({
      compiler: "vue3",
      customCollections: {
        "my-icons": FileSystemIconLoader("./src/assets/icons"),
      },
    }),
  ],
  resolve: {
    alias: {
      "@": fileURLToPath(new URL("./src", import.meta.url)),
    },
  },
});代码的开发
1. 修改main.ts
初始化vue,并 将它的根节点挂载在手机的门户上方的空白部分的元素 src/main.ts
            
            
              ts
              
              
            
          
          ...
kintone.events.on("mobile.portal.show", (event) => {
  const app = createApp(App);
  app.use(router);
  app.use(Tabbar);
  app.use(TabbarItem);
  app.mount(kintone.mobile.portal.getContentSpaceElement()!);
  return event;
});
...2. 隐藏掉原先的手机画面
src/assets/main.css
            
            
              css
              
              
            
          
          .gaia-mobile-v2-portal-announcement-container,
.gaia-mobile-v2-portal-appwidget-container,
.gaia-mobile-v2-portal-spacewidget-container {
  display: none;
}
.van-hairline--top-bottom::after,
.van-hairline-unset--top-bottom::after {
  border-width: 0;
}
.gaia-mobile-v2-viewpanel-header {
  background-color: #4b4b4b;
}
.gaia-mobile-v2-viewpanel-contents {
  border-radius: 0;
}
.van-tabbar {
  width: 100vw;
}
.van-tabbar--fixed {
  left: unset;
}
.gaia-mobile-v2-portal-header-container .gaia-mobile-v2-portal-header::after {
  background: none;
}
.group-module-background {
  background-color: rgba(255, 255, 255);
  border-radius: 10px;
  box-shadow: 0 0 5px 0 #ced3d4;
}3. 添加tabbar
src/App.vue
            
            
              js
              
              
            
          
          <script setup lang="ts">
import { ref } from "vue"
import type { Ref } from 'vue'
const active: Ref<number> = ref(0)
</script>
<template>
  <router-view v-slot="{ Component }">
    <keep-alive>
      <component :is="Component" />
    </keep-alive>
  </router-view>
  <van-tabbar route v-model="active" active-color="#febf00" inactive-color="#b8b8b5">
    <van-tabbar-item replace to="/">
      <span>Home</span>
      <template #icon="props">
        <i-mdi-home class="tabbar-icon" />
      </template>
    </van-tabbar-item>
    <van-tabbar-item replace to="/contacts">
      <span>Contacts</span>
      <template #icon="props">
        <i-mdi-contacts class="tabbar-icon" />
      </template>
    </van-tabbar-item>
    <van-tabbar-item replace to="/space">
      <span>Space</span>
      <template #icon="props">
        <i-mdi-shape-circle-plus class="tabbar-icon" />
      </template>
    </van-tabbar-item>
    <van-tabbar-item replace to="/star">
      <span>Star</span>
      <template #icon="props">
        <i-mdi-star class="tabbar-icon" />
      </template></van-tabbar-item>
    <van-tabbar-item replace to="/todo">
      <span>Todo</span>
      <template #icon="props">
        <i-mdi-tooltip-edit class="tabbar-icon" />
      </template></van-tabbar-item>
  </van-tabbar>
</template>
<style scoped>
.tabbar-icon {
  font-size: 1em;
}
</style>4. 模拟一个订单列表应用
我们在kintone上创建一个应用。并准备以下字段。
| フィールド名 | フィールドコード | Type | 
|---|---|---|
| title | title | 文字列 (1行) | 
| num | num | 数値 | 
| desc | desc | 文字列 (1行) | 
| price | price | 数値 | 
| thumb | thumb | 文字列 (1行) | 
接着请自行添加一些数据。并且设置【API token】
5. 安装kintone js sdk
            
            
              sh
              
              
            
          
          npm install @kintone/rest-api-client 6. 添加kintone的ts声明
- 安装kintone的ts生成工具
            
            
              sh
              
              
            
          
          npm install -D @kintone/dts-gen- 根据应用生成ts声明 请根据自己的环境输入
            
            
              sh
              
              
            
          
          npx @kintone/dts-gen --base-url https://xxxx.cybozu.com -u  xxxx -p xxxx --app-id xx
            
            
              ts:tsconfig.app.json
              
              
            
          
          {
  ...
  "files": ["./node_modules/@kintone/dts-gen/kintone.d.ts"],
  ...
}7. 封装kintone api请求
src/service/kintoneApi.ts
            
            
              ts
              
              
            
          
          //@ts-ignore
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
const APP_ID = import.meta.env.VITE_APP_ID;
export class KintoneApi {
  client: KintoneRestAPIClient;
  constructor() {
    this.client = new KintoneRestAPIClient({
      baseUrl: `https://${import.meta.env.VITE_KINTONE_URL}`,
      auth: {
        apiToken: import.meta.env.VITE_API_TOKEN,
      },
    });
  }
  public async getAllRecords() {
    try {
      return await this.client.record.getAllRecords({ app: APP_ID });
    } catch (error) {
      return;
    }
  }
}8. env文件添加一些kintone的配置
添加kintone应用的app id和api token .env.development
            
            
              sh
              
              
            
          
          VITE_API_TOKEN=xxx
VITE_APP_ID=xxx9. 创建页面
先删除view下原有的文件添加以下文件。
view/Home.vue view/Contacts.vue view/Space.vue view/Star.vue view/Todo.vue
在这些页面写一些模拟内容
Home页通过获取kintone的数据来模拟订单数据 src/view/Home.vue
            
            
              js
              
              
            
          
          <template>
  <List v-model:loading="loading" :finished="finished" @load="onLoad">
    <Card v-for="(item, index) in list" :key="index" :num="item.num.value" :price="item.price.value"
      :desc="item.desc.value" :title="item.title.value" :thumb="item.thumb.value" />
  </List>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import type { Ref } from 'vue'
import { Card, List } from 'vant'
import { KintoneApi } from '@/service/kintoneApi'
const list: Ref<kintone.types.Fields[]> = ref([])
const loading = ref(false);
const finished = ref(false);
const onLoad = () => {
  const kintoneClient = new KintoneApi()
  kintoneClient.getAllRecords().then((res) => {
    list.value = res
    loading.value = false;
    finished.value = true;
  })
};
</script>其他页面准备一些模拟数据 比如Contacts.vue view/Contacts.vue
            
            
              js
              
              
            
          
          <template>
  <div>
    <h1>Contacts</h1>
  </div>
</template>10. 设置路由
这边需要使用createWebHashHistory这种方式创建路由。因为createWebHistory方式刷新时会被后端路由解析。 src/router/index.ts
            
            
              ts
              
              
            
          
          import { createRouter, createWebHashHistory } from "vue-router";
const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      name: "home",
      component: () => import("../views/Home.vue"),
    },
    {
      path: "/contacts",
      name: "contacts",
      component: () => import("../views/Contacts.vue"),
    },
    {
      path: "/space",
      name: "space",
      component: () => import("../views/Space.vue"),
    },
    {
      path: "/star",
      name: "star",
      component: () => import("../views/Star.vue"),
    },
    {
      path: "/todo",
      name: "todo",
      component: () => import("../views/Todo.vue"),
    },
  ],
});
export default router;启动项目
            
            
              sh
              
              
            
          
          npm run dev启动后,kintone首页自定义会自动上传kintone_module_hack.js文件 
最后的成果图

我们看到一个简单的手机自定义骨架就出来了。 这只是一个最简单的范例,实际上,还需要考虑的地方还有很多,比如集中式状态管理,页面生命周期缓存,数据下拉刷新等等。如果你对手机版感兴趣,我这边有个三年前的项目。不过当时使用的还是vue2。可以看看。 github.com/kintone-sam...
构建阶段
            
            
              sh
              
              
            
          
          npm run build运行完,会自动上传打包后的代码到kintone。
示例项目github地址
在react中使用的范例
本插件同样适用于使用vite在kintone中构建react应用。 使用react范例可参考: github.com/GuSanle/vit...
插件原理
插件的原理是通过js自定义,hack出一个类型为module的script标签。用来加载main.ts文件。 不过基于vue和react的不同,还需要结合vite的文档做一些代码的inject。
可能存在的一些问题
如果开发时遇到イベントハンドラー登録の適切なタイミングについて问题, 可以尝试在使用kintone事件后挂载后,使用如下代码解决问题。 (构建时,可以删除,因为构建时,不再使用esm模式,不存在异步加载问题。) src/main.ts的示例: src/main.ts
            
            
              ts
              
              
            
          
          import { createApp } from "vue";
import App from "./App.vue";
kintone.events.on("app.record.detail.show", (event) => {
  const app = createApp(App);
  app.mount(kintone.app.record.getHeaderMenuSpaceElement()!);
  return event;
});
//通过手动执行kintone事件,来解决异步事件执行时机问题
const event = new Event("load");
// @ts-ignore
cybozu.eventTarget.dispatchEvent(event);总结
使用了vite之后,hmr技术使得我们对代码进行修改后,会马上在页面上体现,非常的迅速,开发体验非常好。 kintone结合vite的开发就讲到这里。如果你对我这个插件或者这个手机范例感兴趣,欢迎一起交流,给个star。谢谢。