本文展示使用传统的前端开发方式使用vuejs,不使用webpack的如何使用vuejs开发前端工程。一方面可以对比两者的差异,一方面学习一种新的思路(或者是老路)。在做一个小型的前端工程时,可以考虑这种轻量化的方案。本示例使用了boostrap、jquery、vue、vue-router组件,创建了一个SPA单页示例。
先闲扯几句。最近vuejs 3.0 开始beta,相信不久(也不好说是多久)就会发布了,性能提升令人期待。由Webpack支撑的前端技术栈,把以往传统的前端开发和发布模式引入了一个新的工程化的阶段。依赖管理、编译、treeshaking等等。对于研发是提升了工程组织维护效率,但是对于用户来说是增加整体拥有成本。
开放共享的互联网正在变得封闭。像jquery、bootstrap这样的组件库都是RELEASE的,为什么还要从源码的层面去引入依赖,然后重新编译和发布。把所有的公共库都打包的vender.js里,明明可以共享CDN的,节省流量的。我注意到这一切好像是从进入移动互联网开始的,也就是用户终端从浏览器更换到了手机。浏览器被组件化到APP中,一切开始私有和封闭起来。
以下是bootstrap v4.x的官方文档,使用CDN直接引入依赖的css和js。
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous">script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous">script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous">script>
好,回到正文。
我们开始搭建一个工程。考虑到我们需要http的调用,还是得有个服务,这里使用nodejs环境,当然你也可以用nginx,或者其他语言的web appserver,比如tomcat。
新建一个目录demo,使用cnpm init初始化一个工程(或者npm),安装express、axios两个组件库。
$ cnpm init
$ cnpm i -S express axios
然后创建一个public目录,用来放置我们的前端代码,因为不需要编译,都是静态资源。
目录如下:
demo/
├── index.js
├── node_modules/
├── package-lock.json
├── package.json
└── public/
创建一个index.js来启动web服务。
const express = require('express');
const bodyParser = require('body-parser');
const port = 8080;
app = express();
app.use(bodyParser.json());
app.use(express.static('public')); //静态资源目录
app.listen(port, () => console.log(`Server running: http://localhost:${port}`));
好了,现在运行web服务:
$ node index.js
可以访问了http://localhost:8080/,只不过是一个空的服务。
工程目录public的一般结构示例:
public/
├── css/
│ └── main.css
├── img/
├── index.html
└── js
├── lib/
├── main.js
└── vue_com.js
创建一个页面index.html,组件全是使用CDN的,当然也可以下载到本地,然后引用。
为了不反复贴代码,下面的代码是完整示例:
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>DEMOtitle>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/main.css">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js">script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js">script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js">script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
<script src="https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.js">script>
head>
<body>
<div id="app">div>
<script src="js/vue_com.js">script>
<script src="js/vue_hello.js">script>
<script src="js/vue_app.js">script>
<script src="js/main.js">script>
body>
html>
接下来我们开始编写各部分的代码,最终的目录和文件如下:
public/
├── css
│ └── main.css
├── index.html
└── js
├── lib
├── main.js
├── vue_app.js
├── vue_com.js
└── vue_hello.js
顺便把自定义css部分,main.css贴一下:
.m-toast-pop {position: fixed; width: 100%;top: 0;bottom: 0;right: 0;overflow: auto;text-align: center; z-index: 9;}
.m-toast-inner {position: absolute;left:50%;top:50%;width: 100%; transform:translate(-50%,-50%);-webkit-transform:translate(-50%,-50%);text-align: center;}
.m-toast-inner-text{display: inline-block;margin: 0 22px; padding: 19px 21px;font-size: 16px;color: #FFFFFF;letter-spacing: 0;line-height: 22px;background: rgba(0,0,0,0.72);border-radius: 10px;}
.fade-enter-active, .fade-leave-active {transition: opacity .5s; }
.fade-enter, .fade-leave-to {opacity: 0; }
.topbar {line-height: 39px; border-bottom: 1px solid #eee; }
.topbar ul{margin: 0; padding: 0 5px;}
.topbar li{display: inline-block; padding: 0 5px;}
.router-link-active {color: #666;}
实现一个简易消息提示框组件,toast,公共组件部分可以都放置在vue_com.js中:
Vue.component('toast', {
template: `
{{msg}}
`,
data() {
return {
show: false,
msg: ''
}
},
methods: {
showMsg(txt) {
this.msg = txt;
this.show = true;
setTimeout(() => this.show = false, 2000);
}
}
});
这个toast提示框,有Android开发经验的人知道,弹出提示,然后过一会儿自动消失。
我们所有的组件注册基本都使用全局注册的方式。模板直接定义在js里,注意引号是这里使用的ES6中的新加入的单反引号,支持多行字符串。
vue中的模块组织都是由组件来完成的,习惯把页面功能的称为页面组件,其他功能性封装组件叫公用组件。
Vue.component('hello', {
template: `
hello world
`,
data() {
return {
msg: ''
}
},
inject: ['toast'],
methods: {
showText() {
this.toast('hello!')
}
}
});
这里页面将会在点击按键时,调用toast组件弹出简易提示框,显示hello
消息。toast组件定义在父组件中,见下文。
主界面app组件,文件为vue_app.js
Vue.component('app', {
template: `
`,
provide() {
return {
toast: this.toast
}
},
methods: {
toast: function(msg) {
this.$refs.toast.showMsg(msg)
}
},
});
可以看到使用vue的provide/inject机制,把toast方法暴露给子组件使用。
入口app组件,里面定义的页面的整体布局,路由页面展示区router-view
和导航部分。
vue和vue-router的初始化部分,main.js
(function() {
var router = new VueRouter({
routes: [
{ path: '/home', component: { template: 'welcome!
' } },
{ path: '/hello', component: Vue.component('hello') }
]
})
new Vue({
router,
el: '#app',
render: h => h(Vue.component('app'))
});
})()
这里初始化了路由和vue,把应用入口挂载到id为app的dom元素上。
对于/home使用了一个组件配置对象,这是vue-route支持的,相对于注册了一个局部的组件,vue的组件注册非常灵活。
这里的初始化用闭包,是不把变量暴露到全局,也就是window对象下。
需要注意的是,js引入顺序,main.js放置在最后,也就是所有组件的js的后面。并且是在body的最后面,保证所有的页面dom元素已加载。还记得jquery的ready方法吗,也是保证页面加载完成后的执行的。
$(document).ready(function() {
//do something
});
现在再到浏览器刷新下刚才的url,可以看到效果了,图就不上了。
下一篇,在这个例子的基础上使用axios组件调用一个后端api。
代码可以在 github上的相应分支vue-demo-no-webpack上找到