草庐IT

微前端vue项目实战 -- 乾坤qiankun框架 (一)

随便起的名字也被占用 2023-07-26 原文

背景:

公司研发的项目主要是 GIS地图为基础的管理系统,由主体项目(管理业务模块)+ GIS地图应用,由于 GIS地图应用模块 会在多个地方使用,所以单独构建一个项目,分别在不同的业务场景中引用、交互,也在app中通过webview通信使用,之前一直使用iframe的通信方式,iframe基本也可以满足业务需求,应用分割、独立运行、分别部署,iframe方案已经满足并且不断优化已经满足需求,但是作为程序猿还是想尝试一下新技术,或许实战它可能不是最佳方案,那也只有试了才知道,那就研究一下吧。

站在巨人的肩膀,看一下天空,用了阿里的乾坤qiankun框架,尝试一下踩坑,说干就干。

首先贴一下qiankun官网,官网介绍也很详细,也在网上借鉴了很多大神的操作,下面记录一下自己学习、码代码的步骤,就当记个笔记了

一、iframe方案的实现

// 主应用 引入子应用
// 通过 iframe

<iframe webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen="true" id="iframe" :src="url" width="100%" height="100%" scrolling="No" frameborder="0"></iframe>


// 主应用 接口 iframe 子应用页面数据

window.onmessage = params => {

    console.log("子应用传来的数据",params )

}


// 主应用 向iframe 子应用发送数据

 let ele = document.getElementById("iframe").contentWindow;
 ele.postMessage(params, "*");




// 子应用接收主应用传来的数据

  window.addEventListener("message", async params => {
      console.log('接收主应用消息', params)
       
  });

//子应用给主应用发送数据

  let obj = {xxx:xxx}
  window.parent.postMessage(obj, "*");


// 子应用移除监听

 window.removeEventListener("message",()=>{});

二、微前端qiankun实现方案

什么是微前端(取阿里qiankun的介绍)

微前端是一种多个团队通过独立发布功能的方式来共同构建现代化 web 应用的技术手段及方法策略。

微前端架构具备以下几个核心价值:

  • 技术栈无关
    主框架不限制接入应用的技术栈,微应用具备完全自主权

  • 独立开发、独立部署
    微应用仓库独立,前后端可独立开发,部署完成后主框架自动完成同步更新

  • 增量升级

    在面对各种复杂场景时,我们通常很难对一个已经存在的系统做全量的技术栈升级或重构,而微前端是一种非常好的实施渐进式重构的手段和策略

  • 独立运行时
    每个微应用之间状态隔离,运行时状态不共享

微前端架构旨在解决单体应用在一个相对长的时间跨度下,由于参与的人员、团队的增多、变迁,从一个普通应用演变成一个巨石应用后,随之而来的应用不可维护的问题。这类问题在企业级 Web 应用中尤其常见。

将原本把所有功能集中于一个项目中的方式转变为把功能按业务划分成一个主项目和多个子项目,每个子项目负责自身功能,同时具备和其它子项目和主项目进行通信的能力,达到更细化更易于管理的目的。

1、微前端框架搭建--qiankun

主应用不限技术栈,只需要提供一个容器 DOM,然后注册微应用并 start 即可,微应用分为有 webpack 构建和无 webpack 构建项目,有 webpack 的微应用

可以看到官网介绍微应用需要使用到webpack,所以在构建应用时,我们使用vue在选择构建方式时候,主应用可以使用webpack 或vite,子应用必须使用webpack 构建,所以实验学习使用的 

主应用:vue3 使用cli的构建方式;子应用:vue2 使用cli方式

构建的应用大概长这样:

为了方便,贴上学习实践的demo:

主项目vue3:vue-qiankun-master: 微前端qiankun,vue3.x项目,微前端主应用

子项目vue2:vue-qiankun-child: 微前端qiankun,vue2.x项目,微前端子应用

 上面demo只需要下载下来运行就可以查看效果了

2、开始构建项目

主、子项目都具备 登录、侧边菜单、主内容;页面如下

 

 2.1、构建主应用

vue3项目构建过程可以看我的其他文章,此处省略

构建完主应用项目后,在主应用安装qiankun

yarn add qiankun # 或者 npm i qiankun -S

使用qiankun的registerMicroApps注册微应用

在主应用项目中src创建micros,在micros创建index.js、apps.js

应用切换时用到nprogress,先安装nprogress

npm install --save nprogress
# 或者
yarn add nprogress

index.js

// 子应用切换加载进度条
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import Store from "../store"

// 注册微应用主应用
import { registerMicroApps, start, addGlobalUncaughtErrorHandler, initGlobalState } from 'qiankun';
import apps from "./apps";

// 微应用通信 定义全局状态,并返回通信方法
const state = {}
const actions = initGlobalState(state);
actions.setGlobalState({
    globalToken: ''
})

registerMicroApps(apps, {
    beforeLoad: (app) => {
        // 加载微应用前,加载进度条    
        NProgress.start();
        console.log("before load", app.name);

        if (Store.state.token) {
            //  微应用加载检查登录 已登录 子应用直接传参登录
            actions.setGlobalState({ globalToken: Store.state.token })
        }

        return Promise.resolve();
    },
    afterMount: (app) => {
        // 加载微应用前,进度条加载完成    
        NProgress.done();
        console.log("after mount", app.name);

        return Promise.resolve();
    },
});

addGlobalUncaughtErrorHandler((event) => {
    console.error(event);
    const { message: msg } = event
    if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
        console.error("微应用加载失败,请检查应用是否可运行");
    }
});

export default start;
export {
    actions
}

 这里用到qiankun框架的几个api,简单介绍一下

registerMicroApps(apps, lifeCycles?):

  • apps - Array<RegistrableApp> - 必选,微应用的一些注册信息
  • lifeCycles - LifeCycles - 可选,全局的微应用生命周期钩子

微应用注册,包含两个参数,第一个参数是微应用的一些注册信息,第二个参数是全局的微应用生命周期钩子。

start(opts?)

  • opts - Options 可选

我们用来启动qiankun的方法,包含一个参数

addGlobalUncaughtErrorHandler(handler)

  • handler - (...args: any[]) => void - 必选

全局的未捕获异常处理器。

qiankun API具体使用方式请查看官网api

apps.js

const apps = [
  /**
  * name: 微应用名称 - 具有唯一性
  * entry: 微应用入口 - 通过该地址加载微应用
  * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
  * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用
  */

  {
    name: "vue-micro-app",
    entry: "//localhost:8082",
    container: "#micro-container",
    activeRule: "#/vue2-micro-app",
    props: {
      router: router
    }
  },
];
export default apps;

apps导出的参数是registerMicroApps的第一个参数apps

  * name: 微应用名称 - 具有唯一性
  * entry: 微应用入口 - 通过该地址加载微应用
  * container: 微应用挂载节点 - 微应用加载完成后将挂载在该节点上
  * activeRule: 微应用触发的路由规则 - 触发路由规则后将加载该微应用

参数具体规则可以查看官网api介绍

mian.js

在main.js中引用,运行一下 start函数 开启微应用,sandbox: { experimentalStyleIsolation: true }开启沙箱隔离样式

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import store from './store'
import start from '@/micros'

import vueDragging from "./directives/vue-dragging.es5"

const Vue = createApp(App)


Vue.use(store)
Vue.use(router)
Vue.use(ElementPlus, { size: 'small', zIndex: 3000 })
Vue.use(vueDragging)
Vue.mount('#app')

start({ sandbox: { experimentalStyleIsolation: true } })

启动应用,我们会发现和未安装qiankun前没什么区别。

2.2、主应用中构建微应用容器和微应用菜单

主应用App.vue中添加微应用容器

  <div class="app-container">
        <router-view></router-view>
        <!-- 新添加,微应用的容器 -->
        <div id="micro-container"></div>
  </div>

主应用菜单结构如下:

 <div class="app-nav" v-show="appToken">
 
      <router-link class="nav-a-btn" to="/" >主-home</router-link>
      <router-link class="nav-a-btn" to="/master-about" >主-about</router-link>

      <router-link class="nav-a-btn" to="/vue2-micro-app/home" >子-home</router-link>
      <router-link class="nav-a-btn" to="/vue2-micro-app/about" >子-about</router-link>

    </div>

我们可以看到 to中多了上面app.jsactiveRule字段中对应的值(去掉了#号),因为#/vue2-micro-app正是触发启动微应用的条件

改造后的app.vue

<template>
  <div class="app-main">
    <div class="app-nav" v-show="appToken">
      <template v-for="menu in menuList" :key="menu.name">
        <div class="nav-a-btn" :class="{'router-active':menuActive==menu.path}"
          @click="menuChangeRouter(menu)">{{menu.btnName}}</div>
      </template>
    </div>
    <div class="app-content">
      <div class="app-header-content" v-show="appToken">
        <div> {{crumbsRouter}}</div>
        <div>Token: {{appToken}}</div>
        <el-button type="primary" round @click="loginOut">退出登录</el-button>
      </div>
      <div class="app-container">
        <router-view></router-view>
        <!-- 新添加,微应用的容器 -->
        <div id="micro-container"></div>
      </div>

    </div>
  </div>

</template>

<script setup>
import { reactive, toRefs, ref } from "@vue/reactivity";
import { computed, watch, onMounted } from "@vue/runtime-core";
import { useRoute, useRouter } from "vue-router";
import { useStore } from "vuex";
import { actions } from "@/micros"


const Router = useRouter()
const Route = useRoute()
const Store = useStore()

let state = reactive({
  menuList: [
    {
      name: 'home',
      path: '/master-home',
      btnName: '主-home'
    },
    {
      name: 'master-about',
      path: '/master-about',
      btnName: '主-about'
    },
    {
      name: 'micro-home',
      path: '/vue2-micro-app/home',
      btnName: '子-home'
    },
    {
      name: 'micro-about',
      path: '/vue2-micro-app/about',
      btnName: '子-about'
    }
  ],
  menuActive: computed(() => Route.path),
  crumbsRouter: computed(() => {
    let name = ""
    state.menuList.forEach(item => {
      if (state.menuActive == item.path) {
        name = item.btnName
      }
    })
    return name

  }),
  // 从环境变量中取参数
  appId: process.env.VUE_APP_MICRO_ENTRY,
  appToken: computed(() => Store.state.token)
})

watch(() => Route.path, (val, oval) => {
  console.log("监听路由变化", val, oval)
})

onMounted(() => {
  actions.onGlobalStateChange((state) => {
    // state: 变更后的状态; prevState: 变更前的状态
    console.log("主应用观察者:状态改变", state);
    let token = state.globalToken
    Store.commit("setToken", token)
    console.log("kkkkkkkkkkk")

    console.log("jjjjj", Store.state.token)
  })
})

let menuChangeRouter = (row) => {
  state.menuActive = row.name
  // 路由跳转方式
  Router.push({ path: row.path })
  // 跳转方法二 
  //  window.history.pushState({}, '', '/#'+row.path)
}

let loginOut = () => {
  Store.commit("loginOut")
  Router.push('/login')
}

let { menuList, menuActive, crumbsRouter, appId, appToken } = toRefs(state)


</script>


<style lang="scss">
html,
body {
  margin: 0;
  padding: 0;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
$leftWidth: 200px;
.app-main {
  display: flex;
  justify-content: space-between;
  width: 100%;
  height: 100vh;
  background: #f6f7fc;
}
.app-nav {
  width: $leftWidth;
  height: 100%;
  display: flex;
  flex-direction: column;
  box-shadow: 2px 0px 10px 0px rgb(0, 47, 60, 0.2);
  padding: 20px;
  box-sizing: border-box;
  background: #fff;
  z-index: 9;

  .nav-a-btn {
    width: 100%;
    height: 40px;
    line-height: 40px;
    background: #f3f4f5;
    margin-bottom: 10px;
    font-weight: bold;
    cursor: pointer;
  }

  .router-active {
    color: #42b983;
    background: #deeefdde;
  }
}
.app-content {
  width: calc(100% - $leftWidth);
  height: 100%;
  .app-header-content {
    padding: 0 20px;
    width: 100%;
    height: 50px;
    background: #ffffff;
    box-shadow: 0px 0px 8px 0px rgb(0 0 0 / 8%);
    display: flex;
    align-items: center;
    justify-content: space-between;
    box-sizing: border-box;
    border-bottom: 1px solid #ccc;
  }
  .app-container {
    width: 100%;
    height: calc(100% - 50px);
    overflow: auto;
    padding: 20px;
    box-sizing: border-box;
  }
}

/* S 修改滚动条默认样式 */

::-webkit-scrollbar {
  width: 8px;
  background: white;
}

::-webkit-scrollbar-corner,
   /* 滚动条角落 */
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-track {
  /*滚动条的轨道*/
  border-radius: 4px;
}

::-webkit-scrollbar-corner,
::-webkit-scrollbar-track {
  /* 滚动条轨道 */
  background-color: rgba(180, 160, 120, 0.1);
  box-shadow: inset 0 0 1px rgba(180, 160, 120, 0.5);
}

::-webkit-scrollbar-thumb {
  /* 滚动条手柄 */
  background-color: #00adb5;
}

/* E 修改滚动条默认样式 */
</style>

2、微应用子应用搭建

微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用

开始改造子应用

创建qiankun/public-path.js

// 新增:动态设置 webpack publicPath,防止资源加载出错
if (window.__POWERED_BY_QIANKUN__) {
    // eslint-disable-next-line no-undef  
    __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

在main.js 引入 public-path.js  并改造main.js  如下:

import "./qiankun/public-path"
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import routes from './router'
import store from './store'
import MicroActions from './qiankun/qiankun-actions'

Vue.config.productionTip = false

Vue.use(VueRouter)


// 新增:用于保存vue实例
let instance = null;
let router = null;
let microPath = ''


if (window.__POWERED_BY_QIANKUN__) {
  microPath = '/vue2-micro-app'
}



/** * 新增: * 渲染函数 * 两种情况:主应用生命周期钩子中运行 / 微应用单独启动时运行 */
function render(props) {

  console.log("子应用render的参数", props)

  // 新增判断,如果是独立运行不执行onGlobalStateChange
  if (window.__POWERED_BY_QIANKUN__) {
    if (props) {
      // 注入 actions 实例
      MicroActions.setActions(props);
    }
    
    // 挂载主应用传入路由实例 用于子应用跳转主应用
    Vue.prototype.$microRouter = props.router

    props.onGlobalStateChange((state, prevState) => {
      // state: 变更后的状态; prev 变更前的状态
      console.log("通信状态发生改变:", state, prevState);
      store.commit('setToken', state.globalToken)
    }, true);
  }

  // router不再是同一个实例,而是每次mount的时候都会新获取一个实例
  router = new VueRouter({
    routes
  })
  // 路由守卫
  router.beforeEach((to, from, next) => {
    if (to.path !== (microPath + '/login')) {
      if (store.state.token) {
        console.log("已经登录 token=", store.state.token)
        if (window.__POWERED_BY_QIANKUN__ && !to.path.includes('vue2-micro-app')) {
          next(microPath + to.path)
        } else {
          next()
        }
      } else {
        console.log("子应用 - 未登录 请登录")
        next(microPath + '/login')
      }
    } else {
      next()
    }
  })


  // 挂载应用  
  instance = new Vue({
    router,
    store,
    render: (h) => h(App),
  }).$mount("#micro-app");
}


/** 
* 新增: 
* bootstrap 只会在微应用初始化的时候调用一次,
  下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 
*/
export async function bootstrap() {
  console.log("VueMicroApp bootstraped");
}

/** 
* 新增: 
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 
*/
export async function mount(props) {
  console.log("VueMicroApp mount", props);
  render(props);
}
/** 
* 新增: 
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 
*/
export async function unmount() {
  console.log("VueMicroApp unmount");
  instance.$destroy();
  instance = null;
}

// 新增:独立运行时,直接挂载应用
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}


// 原本启动代码
// new Vue({
//   router,
//   store,
//   render: h => h(App)
// }).$mount('#app')

注意:$mount("#micro-app")  挂在的时候修改了根id为了区别主应用和子应用,避免出现问题,对应的入口index.html 的 id="micro-app"

接下来就是改造路由,加入判断是否微应用打开,是就走子应用路径逻辑,否就单独运行

// import Vue from 'vue'
// import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
// import store from '../store'

// Vue.use(VueRouter)


// 判断环境是否是微应用打开
let microPath = ''
if (window.__POWERED_BY_QIANKUN__) {
  microPath = '/vue2-micro-app'
}

const routes = [
  {
    path: microPath + '/',
    redirect: microPath + '/home'
  },
  {
    name: 'Home',
    path: microPath + '/home',
    component: Home
  },
  {
    name: 'About',
    path: microPath + '/about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  },
  {
    name: 'login',
    path: microPath + '/login',
    component: () => import(/* webpackChunkName: "about" */ '../views/login.vue')
  }
]

// const router = new VueRouter({
//   routes
// })

// // 路由守卫  移动到main.js中
// router.beforeEach((to, from, next) => {
//   if (to.path !== (microPath + '/login')) {
//     if (store.state.token) {
//       console.log("已经登录 token=",store.state.token)
//       if (window.__POWERED_BY_QIANKUN__ && !to.path.includes('vue2-micro-app')) {
//         next(microPath + to.path)
//       } else {
//         next()
//       }
//     } else {
//       console.log("子应用 - 未登录 请登录")
//       next(microPath + '/login')
//     }
//   } else {
//     next()
//   }
// })

export default routes

路由主要的改动就是每个path都添加了一个microPath变量,检测是否微应用打开

相应的路由守卫也要添加microPath变量,另外微应用的login跳转的时候也要加上microPath判断(在main.js中查看)

设置webpack,在vue.config.js设置打开配置

const path = require("path");

module.exports = {
  devServer: {
    // 监听端口
    port: 8066,
    // 关闭主机检查,使微应用可以被 fetch
    disableHostCheck: true,
    // 配置跨域请求头,解决开发环境的跨域问题
    headers: {
      "Access-Control-Allow-Origin": "*",
    },
  },
  configureWebpack: {
    resolve: {
      alias: {
        "@": path.resolve(__dirname, "src"),
      },
    },
    output: {
      // 微应用的包名,这里与主应用中注册的微应用名称一致
      library: "vue-micro-app",
      // 将你的 library 暴露为所有的模块定义下都可运行的方式
      libraryTarget: "umd",
      // 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
      jsonpFunction: `webpackJsonp_vue-micro-app`,
    },
  },
};

最后启动主应用和子应用项目到浏览器上可以看到,如下页面:

 

好了此时微应用已经正常启动,上面页面已经做了,主应用登录子应用可以同步登录状态,主应用与子应用通信

下一章继续探索微前端的主应用如何与子应用通信,如何使用initGlobalState进行通信,敬请期待 

有关微前端vue项目实战 -- 乾坤qiankun框架 (一)的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  3. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  4. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  5. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  6. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  7. TimeSformer:抛弃CNN的Transformer视频理解框架 - 2

    Transformers开始在视频识别领域的“猪突猛进”,各种改进和魔改层出不穷。由此作者将开启VideoTransformer系列的讲解,本篇主要介绍了FBAI团队的TimeSformer,这也是第一篇使用纯Transformer结构在视频识别上的文章。如果觉得有用,就请点赞、收藏、关注!paper:https://arxiv.org/abs/2102.05095code(offical):https://github.com/facebookresearch/TimeSformeraccept:ICML2021author:FacebookAI一、前言Transformers(VIT)在图

  8. ruby - 如何在 Ruby 字符串中插入项目符号字符? - 2

    我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195

  9. ruby - 在 Rails 项目中测试本地版本的 gem - 2

    我的Rails站点使用了一个确实不是很好的gem。每次我需要做一些新的事情时,我最终不得不花费与向实际Rails项目添加代码一样多的时间来为gem添加功能。但我不介意,我将我的Gemfile设置为指向我的gem的GitHub分支(我尝试提交PR,但维护者似乎已经下台)。问题是我真的没有找到一种合理的方法来测试我添加到gem的新东西。在railsc中测试它会特别好,但我能想到的唯一方法是a)更改~/.rvm/gems/.../foo。rb,这看起来不对或者b)升级版本,推送到Github,然后运行​​bundleup,这除了耗时之外显然是一场灾难,因为我不确定我所做的promise是否正

  10. ruby - sinatra 框架的 MVC 模式 - 2

    我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho

随机推荐