METADATA
title: 使用Vue JS,Webpack&Material Design开发渐进式web应用(PWA)[Part 1] date: 2018-01-03 21:30 tags: [前端,PWA] categories: 技术
原文地址:
https://blog.sicara.com/a-progressive-web-application-with-vue-js-webpack-material-design-part-1-c243e2e6e402
PWA相关的中文文章比较少,这篇手把手教程非常适合当做pwa入门教程,因此翻译一哈。作者写了一系列手把手教程,这是part 1。
渐进式web应用(PWA)是web未来的趋势,越来越多的大公司们开始使用这项技术(如推特...)。
想象一下:当你在乘坐地铁时浏览一款web应用,这款web应用通过推送通知、实时更新数据和提供原生app一般的导航等功能让你沉醉其中。而这些功能就是PWA所包含的一些特性。
PWA就是一款能为用户提供类似于原生app般体验的web应用。得益于现代web技术的不断创新(如Service Worker,Native APIS,JS frameworks等),PWA可以提升web应用的质量标准。
如果想了解更多关于PWA的消息,你可以访问Google developer page
看一哈下面的PWA,它看起来很像一款原生的app吧!
从开发者的角度来看,PWA相比于原生的应用有巨大的优点。它本质上是一个网页,所以:
- 你可以使用任何你喜欢的框架进行开发
- 编写一次代码就可以让它跨平台、跨设备运行:因为它是由用户的浏览器来运行的
- 易于传播:它不需要通过应用商店来下载
然而在2017年早些时候,PWA仍然面临着一些限制:
- Safari并不支持PWA的一些基本特性,如Service Workers,但看起来Apple已经开始着手去实现它了。
- 一些原生的方法仍然不被支持,你可以在What web can do看到更多信息。
教程目标
本教程旨在使用VueJS和Webpack从头编写一款基本但功能完善的PWA。这款应用将会实现所有在介绍中提到的特性:渐进式的,响应式的,独立连接的等等。我想让你对PWA能实现的功能有了大体的了解:流畅的类原生应用体验,离线行为,设备原生功能的接口,通知推送等。
为了让这件事充满挑战性,我们将会完成一款猫咪图片交流的app:CropChat!CropChat的用户可以浏览各用户上传的猫咪图片,点击图片阅读更多细节,并且可以发表新的猫咪图片(图片来源可以是互联网,也可以从相册中选取或拍照)。
本教程将分为如下几部分,各部分会陆续发布:
- [Part 1] 使用VueJS、Webpack和Material DesignLite创建一个单页应用
- [Part 2]使用Vue-Resource和VueFire连接App和远程API
- [Part 3] 使用Service Workers实现离线模式
- [Part 4] 访问设备相机进行拍照
- [Part 5] 访问设备存储实现图片上传
- [Part 6] 实现消息推送
- [Part 7] 获取设备位置
这款PWA的用到的基本组件(工具)
我们的PWA基于现代的开发工具,你将会爱上他们!
- VueJS 2 视图层:使用Material Design作为UI框架
- Vue-Router:单页应用的前端路由工具
- Vue-Resource&Vuefire:处理和数据库Firebase之间的通信
- Service Workers:实现离线模式和数据更新
- Webpack&Vue-loader:构建这款应用的工具,提供诸如热重载、ES2016和预处理器等支持。
接下来就让我们开始part 1吧!
[PART 1] 使用VueJS、Webpack和Material DesignLite创建一个单页应用
如果你对VueJS 2还不是很熟悉的话,我(作者)强烈建议你看一哈官方教程。
构建一个最基本的VueJS APP
我们将会使用Vue-cli作为脚手架工具构建这个应用:
npm install -g vue-cli
Vue-cli自带了几个模板,我们将会选择pwa template。Vue-cli会使用Webpack,vue-loader(热重载),一个manifest文件和基于service workers实现基本的离线功能创建一个虚拟的VueJS应用。
然后介绍了Webpack,就是网上很多文章都说的那种,大家可以点击了解一哈。敲下面的命令初始化Cropchat这个应用:
vue init pwa cropchat
初始化的过程中终端会问你几个问题,我(作者)通常使用如下的配置:
? Project name cropchat ? Project short name: fewer than 12 characters to not be truncated on homescreens (default: same as name) cropchat ? Project description A cat pictures messaging application ? Author Charles BOCHET <[email protected]> ? Vue build standalone ? Install vue-router? Yes ? Use ESLint to lint your code? Yes ? Pick an ESLint preset Standard ? Setup unit tests with Karma + Mocha? Yes ? Setup e2e tests with Nightwatch? No vue-cli · Generated "cropchat".
这波骚操作就把项目的文件夹创建好了,各级目录结构如下说明:
- build:包含webpack和vue-loader的配置文件
- config:包含该app的配置(environments,parameters...)
- src:应用的源码所在
- static:像图片啊css文件还有别的静态文件都丢这里面
- test:通过Karma & Mocha创建的单元测试文件
说了这么多再就跑一跑下面的命令先把初始化完的项目跑起来再说:
cd cropchat npm install npm run dev
然后访问地址localhost:8080就能看到了:
Manifest.json: 让你的应用可安装
PWA最大的优点之一便是应用可以很方便地安装和分享。所有提供了合法的
manifest.json
文件并在index.html
中引入的web应用都可以安装。Vue pwa模板提供了一个默认的
manifest.json
文件。编辑默认的
static/manifest.json
文件使之能应用于本次的项目:{ "name": "cropchat", "short_name": "cropchat", "icons": [ { "src": "/static/img/icons/cropchat-icon-64x64.png", "sizes": "192x192", "type": "image/png" }, { "src": "/static/img/icons/cropchat-icon-128x128.png", "sizes": "128x128", "type": "image/png" }, { "src": "/static/img/icons/cropchat-icon-256x256.png", "sizes": "256x256", "type": "image/png" }, { "src": "/static/img/icons/cropchat-icon-512x512.png", "sizes": "512x512", "type": "image/png" } ], "start_url": "/", "display": "fullscreen", "orientation": "portrait", "background_color": "#2196f3", "theme_color": "#2196f3" }
在manifest.json文件中你可以修改定制一些属性:
- app names;
- app icons,设置该图标使之作为桌面和启动页图标(你可以找到Cropchat官方图标在这个仓库);
- start url
- display和orientation,display主要控制页面的显示方式,是否要隐藏浏览器UI或全屏或像正常网页一般;orientation主要控制页面的初始方向;
- background和theme colors
在Mozilla Developer website中有对清单中各个属性的详细说明。
然后看一哈
index.html
,发现manifest.json文件已经被引入了:<link rel="manifest" href="<%= htmlWebpackPlugin.files.publicPath %>static/manifest.json">
同时确认一哈在
index.html
中有对viewport的声明:<meta name="viewport" content="width=device-width, initial-scale=1">
这样搞完我们就要试试把Cropchat安装到手机上了。由许多方法可以访问到本地的localhost:8080,作者最喜欢的是ngrok,这里就不翻译了大家各显神通,跟着原文敲一哈命令也可以。
然后当我们用手机访问这个页面的时候,奇迹出现了:
Cropchat的源码在GitHub可以看到。
要想了解更多关于ngrok的信息,你可以看看Matthieu Auger的文章: Expose your local environment to the world with ngrok。
处理视图页面和路由
既然我们已经成功地将项目搭建并运行起来,那接下来就是去完成Cropchat的更多功能了。Cropchat有三个视图页面:
- Home View:以列表的形式显示猫咪图片
- Detail View:显示特定猫咪图片的具体信息(通过首页点击图片进入)
- Post View:用户可以在该页面发表新的图片
那我们首先就先搞一把HomeView页面。创建
src/components/HomeView.vue
文件,整体结构如下:<template> <ul class="list"> </ul> </template> <script> export default { } </script> <style scoped> .list { width: 100%; padding: 0; } </style>
同理可得
src/components/DetailView.vue
页面:<template> <div class="card-image"> </div> </template> <script> export default { } </script> <style scoped> </style>
同理可得
src/components/PostView.vue
页面:<template> <div class="waiting"> Not yet available </div> </template> <script> export default { } </script> <style scoped> .waiting { padding: 10px; color: #555; } </style>
最后更新路由文件
src/router/index.js
:import Vue from 'vue' import Router from 'vue-router' import HomeView from '@/components/HomeView' import DetailView from '@/components/DetailView' import PostView from '@/components/PostView' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'home', component: HomeView }, { path: '/detail/:id', name: 'detail', component: DetailView }, { path: '/post', name: 'post', component: PostView } ] })
移除没使用的日常商业吹捧)
Hello.vue
文件。你现在已经可以看到页面发生改变了(热重载很棒吧安装Material Design Lite
废话不多说,文档看这里:Get MDL.io,总之它很棒是了~~,当然Material Design思想也是超棒的,配合MDL像我们这些屌丝开发者也能快速搭建大体符合materail design规范的网站~~
更新Cropchat依赖:
npm install material-design-lite --save
更新
src/App.vue
组件,导入MDL样式并加载MDL模块:<script> require('material-design-lite') ... </script> <style> @import url('<https://fonts.googleapis.com/icon?family=Material+Icons>'); @import url('<https://code.getmdl.io/1.2.1/material.blue-red.min.css>'); </style>
为单页应用增加一个导航
更新
src/App.vue
文件模板部分:<template> <div class="mdl-layout mdl-js-layout mdl-layout--fixed-header"> <header class="mdl-layout__header"> <div class="mdl-layout__header-row"> <span class="mdl-layout-title">CropChat</span> </div> </header> <div class="mdl-layout__drawer"> <span class="mdl-layout-title">CropChat</span> <nav class="mdl-navigation"> <router-link class="mdl-navigation__link" to="/" @click.native="hideMenu">Home</router-link> <router-link class="mdl-navigation__link" to="/post" @click.native="hideMenu">Post a picture</router-link> </nav> </div> <main class="mdl-layout__content"> <div class="page-content"> <router-view></router-view> </div> </main> </div> </template>
由于MDL不是特别为单页应用设计的,所以当用户点击菜单按钮时需要隐藏burger menu:
<script> ... export default { name: 'app', methods: { hideMenu: function () { document.getElementsByClassName('mdl-layout__drawer')[0].classList.remove('is-visible') document.getElementsByClassName('mdl-layout__obfuscator')[0].classList.remove('is-visible') } } } </script>
效果如图所示
丰富视图并让应用跑起来
到现在为止我们并没有使用后台服务器,而是创建一个data.js文件做一些模拟的数据。
创建
src/data.js
文件:export default { pictures: [ { 'id': 0, 'url': '<https://25.media.tumblr.com/tumblr_m40h4ksiUa1qbyxr0o1_400.gif>', 'comment': 'A cat game', 'info': 'Posted by Kevin on Friday' }, { 'id': 1, 'url': '<https://25.media.tumblr.com/tumblr_lhd7n9Qec01qgnva2o1_500.jpg>', 'comment': 'Tatoo & cat', 'info': 'Posted by Charles on Tuesday' }, { 'id': 2, 'url': '<https://24.media.tumblr.com/tumblr_m4j2atctRm1qejbiro1_1280.jpg>', 'comment': 'Santa cat', 'info': 'Posted by Richard on Monday' }, { 'id': 3, 'url': '<https://25.media.tumblr.com/tumblr_m3rmbwhVB51qhwmnpo1_1280.jpg>', 'comment': 'Mexico cat', 'info': 'Posted by Richard on Monday' }, { 'id': 4, 'url': '<https://24.media.tumblr.com/tumblr_mceknxs4Lo1qd477zo1_500.jpg>', 'comment': 'Curious cat', 'info': 'Posted by Richard on Monday' } ] }
在
HomeView.vue
的script标签中导入数据并将图片连接关联到响应的DetailView页面:<script> import data from '../data' export default { methods: { displayDetails (id) { this.$router.push({name: 'detail', params: { id: id }}) } }, data () { return { 'pictures': data.pictures } } } </script>
更新
HomeView.vue
模板和样式:<template> <div> <div class="mdl-grid"> <div class="mdl-cell mdl-cell--3-col mdl-cell mdl-cell--1-col-tablet mdl-cell--hide-phone"></div> <div class="mdl-cell mdl-cell--6-col mdl-cell--4-col-phone"> <div v-for="picture in this.pictures" class="image-card" @click="displayDetails(picture.id)"> <div class="image-card__picture"> <img :src="picture.url" /> </div> <div class="image-card__comment mdl-card__actions"> <span>{{ picture.comment }}</span> </div> </div> </div> </div> <router-link class="add-picture-button mdl-button mdl-js-button mdl-button--fab mdl-button--colored" to="/post"> <i class="material-icons">add</i> </router-link> </div> </template> ... <style scoped> .add-picture-button { position: fixed; right: 24px; bottom: 24px; z-index: 998; } .image-card { position: relative; margin-bottom: 8px; } .image-card__picture > img { width:100%; } .image-card__comment { position: absolute; bottom: 0; height: 52px; padding: 16px; text-align: right; background: rgba(0, 0, 0, 0.5); } .image-card__comment > span { color: #fff; font-size: 14px; font-weight: bold; } </style>
同理可得
DetailView.vue
页面:<template> <div class="mdl-grid"> <div class="mdl-cell mdl-cell--8-col"> <div class="picture"> <img :src="this.pictures[$route.params.id].url" /> </div> <div class="info"> <span>{{ this.pictures[$route.params.id].info }}</span> </div> </div> <div class="mdl-cell mdl-cell--4-col mdl-cell--8-col-tablet"> <div class="comment"> <span>{{ this.pictures[$route.params.id].comment }}</span> </div> <div class="actions"> <router-link class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" to="/post"> ANSWER </router-link> </div> </div> </div> </template> <script> import data from '../data' export default { data () { return { 'pictures': data.pictures } } } </script> <style scoped> .picture > img { color: #fff; width:100%; } .info { text-align: right; padding: 5px; color: #555; font-size: 10px; } .comment { padding: 10px; color: #555; } .actions { text-align: center; } </style>
终于搞定了!
搞定了,看一哈是怎么个效果:
在这个仓库你可以找到源码噢:
但是Cropchat还不完全是个PWA,我们来看看PWA需要哪些特性:
在之后的教程中我们会不断完善这些特性,我也会继续半吊子翻译教程。谢谢大家,祝各位和这位作者新年快乐!