一、相关工具链简介
-
HAML
HAML是专门面向Ruby on Rails模版语法设计的一门标记语言,其结合RoR的views部分模版语法的特点,对原来的
*.html.erb
(嵌入Ruby代码的HTML页面)进行了简化和封装,使得在编写前端页面时能够更加简洁方便。例如,下面是一份嵌入式Ruby的HTML页面的代码:
<div id='content'> <div class='left column'> <h2>Welcome to our site!</h2> <p><%= print_information %></p> </div> <div class="right column"> <%= render :partial => "sidebar" %> </div> </div>
使用HAML改写后,可以写成:
#content .left.column %h2 Welcome to our site! %p= print_information .right.column = render :partial => "sidebar"
可以看到HAML使用缩紧代替了HTML的成对标签,同时更加友好地支持了Ruby代码的嵌入。可以看到HAML方便了前端的编写,因此也很受欢迎,我们所熟知的GitLab的前端代码均是使用HAML进行编写的。
HAML官方文档:传送门
-
Webpacker
- Webpack 是一个 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
- Webpacker基于Webpack,是一个专门为使用Ruby on Rails项目搭建的打包器。
- 由于我们前端需要使用Vue.js前端框架以及ElementUI这一UI框架,因此前端部分使用了很多js以及vue的组件,需要使用webpacker进行编译和打包。
二、相关工具链配置方法
-
HAML
使用Ruby on Rails的项目很方便配置HAML,只要在
Gemfile
中添加:gem 'haml'
然后执行
bundle install
即可。这时,只要在views中创建
*.haml
,在渲染时就会根据HAML语法进行渲染。 -
Webpacker
在
Gemfile
中添加:# Gemfile gem 'webpacker', '~> 5.x'
然后执行
bundle install
,之后执行:bundle exec rails webpacker:install
这时webpacker完成了初始化,可以在
app/
下看到javascript
目录,在里面添加js文件以及修改其中的js文件后,在刷新浏览器重新请求前端页面时会自动触发编译。这里我们的小组成员曾遇到过编译时间过长的情况(数分钟),正常情况下应该能在20-30秒左右完成编译,后来经小组中的wjx同学探究后发现,如果系统(或虚拟机)运行在了机械硬盘上就会导致此问题,将系统(或虚拟机)安装到固态硬盘即可解决此问题。
-
另外,我们使用了Vue.js以及Element UI,在完成上述配置后使用npm进行安装:
npm i vue npm i element-ui -S
关于上述工具链的使用
HAML确实让前端的代码编写变得简洁很多,但是同时我认为也带来了一些不便。在我们Alpha阶段开发时对此很有体会。在使用HAML时,对于原有的HTML标签中的属性以及Vue.js的一些语法均是使用Ruby的hash进行传入的,例如:
%el-card{shadow: 'never', 'v-for': "(blog, index) in blogs", ':key': 'index', class: 'blog-card', ':body-style': "{ padding: '10px' }"}
其中直接使用Ruby的symbol作为key的是HTML标签的标准属性,涉及到Vue.js模版语法的key使用string,而其值如果不是Ruby的变量,则需要写为字符串,这样就导致对于很多语法提示不能使用,比如上述的(blog, index) in blogs
,是Vue.js的列表渲染,如果使用传统的HTML格式,在IDE中安装Vu e.js官方提供的插件则可以实现语法提示与高亮,类似的情况还有很多。这样就导致我们在写前端代码是经常需要在没有任何提示的情况下手写,尤其是在我们还不太熟悉相关技术的阶段时很容易出现一些错误,在开发时花费了很多时间。
对于这一问题,我们在Beta阶段进行了改进,我们进一步研究了Vue的单文件组件以及Webpacker所提供的组织结构,我们选择了只在views中的HAML中保持整体的结构逻辑,而将更多的具体实现封装为Vue组件。以下面一个比较简单的小模块为例:
- content_for :scripts do
= javascript_pack_tag 'new_organization'
- content_for :styles do
= stylesheet_pack_tag 'new_organization'
#new-organization-app.small-container
.page-title-holder
.page-title 创建组织
%org_form{:action => organizations_path}
我们在前端的HAML中只保留了一些架构,而对于表单(org_form)的具体实现放到了Vue组件中,然后使用app/javascript/packs
中的js文件注册该组件,如下所示:
import Vue from 'vue/dist/vue.esm'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import org_form from '../src/organizations/components/new_organizations_form.vue'
Vue.use(ElementUI);
document.addEventListener('DOMContentLoaded', () => {
new Vue({
el: '#new-organization-app',
components: {
org_form,
}
})
})
此时该js相当于为前端提供一个封装好的接口,我们在app/javascript/src/organization/components
中添加对应的组件,如下所示
<template>
<el-form :model="orgForm" :rules="rules" ref="orgForm" :action="action" method="post">
<csrf></csrf>
<el-form-item label="组织名称" prop="name">
<el-input v-model="orgForm.name" name='org[name]'></el-input>
</el-form-item>
... ...
</el-form>
</template>
<script>
export default {
props: ['action'],
data() {
... ...
},
components: {
csrf,
},
methods: {
... ...
}
}
</script>
<style scoped>
... ...
</style>
这样不仅能够实现使用语法高亮与提示减少出错,提高开发效率,同时也对前端界面及其内部包含的模块进行了解耦。
对Web应用结构设计的一点思考
我们现在使用的Ruby on Rails没有进行前后端的分离,我们前端和后端共用了同一套路由,但是这样在进行小组合作时回有些不方便,而且要想实现很好地配合,需要每一位组员都掌握前端和后端的相关技术,然后实行任务导向型分组。但是我认为对于一个小组合作的项目而言,更好的方法或许是进行前后端分离。我参考了这篇文章。
前后端分离即前后端各使用一套路由,然后后端通过提供API、前端通过调用API进行服务,这样在小组进行合作时,可以让一部分的同学专功前端,另一部分专攻后端,然后后端通过撰写API文档的形式提供服务,API文档的撰写可以参考GitLab的API文档,例如:
Remove project
This endpoint either:
- Removes a project including all associated resources (issues, merge requests etc).
- From GitLab 12.6 on Premium or Silver or higher tiers, marks a project for deletion. Actual deletion happens after number of days specified in instance settings.
DELETE /projects/:id
Attribute Type Required Description id
integer/string yes The ID or URL-encoded path of the project
然后后端的路由使用Web框架提供的路由,例如(Django):
from django.urls import path
from rango import views
urlpatterns = [
path('', views.index, name='index'),
path('about/', views.about, name='about'),
path('add_category/', views.add_category, name='add_category'),
path('category/<slug:category_name_slug>/', views.show_category, name='show_category'),
]
然后前端使用前端框架的路由,例如使用Vue.js提供的Vue Router,大致如下:
... ...
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'Index',
component: Index
},
{
path: '/rango',
name: 'Rango',
component: Rango,
children: [
{
path: 'index',
name: 'RangoIndex',
component: RangoIndex,
},
... ...
在前端需要调用后端的API时,可以使用axios等方式:
methods: {
getIndexContent: function () {
return axios.get('http://127.0.0.1:8000/');
}
}
然后在组件挂载时调用该方法,解析后端传来的数据(使用JSON格式),并将其赋值给对应的data即可:
this.getIndexContent().then((result) => {
this.msg = result.data["bold_message"];
this.tableData = result.data["category_list"];
// console.log(this.tableData);
})