现代 Web 应用 - 基础设施:Vue、Parcel 和 Workbox
单页应用 (SPA) 是指包含在单个网页中的 Web 应用,由于无需下载和解析每个页面的 HTML,因此可提供无缝的导航体验。渐进式 Web 应用 (PWA) 是指使用 Service Worker“代理”和清单文件,提供必要的基础架构,允许浏览器缓存应用,以便在网络状况不佳或无网络的情况下也能使用。所有现代浏览器和操作系统都允许 PWA 在本地“安装”,从而提供类似原生应用的用户体验。
PWA 通常是构建原生应用的可行替代方案,尤其对于小型团队而言,因为现在大多数应用商店都接受 PWA,并且所有主流操作系统(Windows、Android、iOS)都允许安装 PWA 并将其显示在桌面上。PWA 可以立即打开,并且可以控制浏览器隐藏其控件,从而提供类似原生应用的外观和体验。
现代工具可以简化开发,但设置起来可能非常耗时。让我们看看如何设置 SPA 和 PWA 项目。本教程旨在描述设置过程,而非具体介绍每个框架/工具——每个工具都有详尽的文档来解释其工作原理。
框架和工具
Vue.js
我们将使用 Vue 生态系统来完成繁重的工作:
- Vue.js将通过提供声明式方法来定义视图,并将代码分离到单文件组件中,
- VueX将用于状态管理
- Vue Router将用于处理 SPA 路由
Node.js
node.js将为捆绑实用程序和可能需要的所有其他实用程序提供支持
Parcel.js
Parcel bundler 将用于构建和捆绑应用程序
工作箱
Workbox将处理服务人员的详细信息。
文件布局
./src
将包含该项目的所有源代码。./src/web
将包含 Web 应用程序(HTML 客户端)的源代码。./src/db
(可选)将包含任何数据库初始化脚本./src/server
(可选)将包含任何服务器端项目
./dist
将包含所有生成的工件,应在 git 中忽略./dist/web
将包含构建和捆绑的 Web 应用程序。./dist/db
(可选)将包含数据库脚本生成的任何工件./dist/server
(可选)将包含任何服务器端项目(已编译)
./.cache
将由 parcel 生成,应在 git 中忽略./node_modules
将由 npm 或 parcel 生成,并且应该在 git 中被忽略
代码
代码可以在项目的 github repo中找到
JavaScript 依赖项
入口点(index.html)
./src/web/index.html
是我们的切入点,将所有内容链接在一起
<link rel="manifest" href="./manifest.webmanifest">
链接.webmanifest文件<div id="vueapp"></div>
定义 vue 挂载点<script src="./index.js"></script>
加载包含 vue 应用程序的脚本navigator.serviceWorker.register('/service-worker.js');
注册服务工作者脚本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="manifest" href="./manifest.webmanifest">
<title>Vue.js Single Page Application Template</title>
</head>
<body>
<div id="vueapp"></div>
<script src="./index.js"></script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/service-worker.js');
});
}
</script>
</body>
</html>
显现
./src/web/manifest.webmanifest
描述应用程序,并且是应用程序被视为 PWA 所必需的。为了实现 Parcel 兼容性,保留.webmanifest
扩展名 非常重要。
{
"name": "My application name",
"short_name": "app",
"start_url": "/",
"background_color": "#3367D6",
"display": "standalone",
"scope": "/",
"theme_color": "#3367D6",
"icons": [
{
"src": "/res/app-256.png",
"type": "image/png",
"sizes": "256x256"
}
]
}
服务工作者(工作箱)
./src/web/service-worker.js
实现了将应用程序视为 PWA 所需的 Service Worker。使用了 Google 的 Workbox。Workbox 定义了多种策略(网络优先、缓存优先和重新验证时过期)。在本例中,所有资源均使用网络优先策略提供,因为这是响应速度最快的方法,并且能够保持离线工作的能力。
console.log("service-worker.js")
// import service worker script
importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.2.0/workbox-sw.js');
// Network First
[
'/$', // Index
'/*', // Anything in the same host
'.+/*' // Anything in any host
]
.forEach(mask => {
workbox.routing.registerRoute(
new RegExp(mask),
new workbox.strategies.NetworkFirst( { cacheName: 'dynamic' } )
);
});
Vue 绑定
./src/web/index.js
用于绑定 Vue 应用和我们的 CSS(在 scss 中)。它导入了 Vue 框架、我们的 Vue 应用代码(app.vue
)以及样式(styles.scss
)——所有这些文件都位于 中,./src/web/
但我们在导入时使用了相对路径。最后,我们将 Vue 应用挂载到相应的 div 元素上。
import Vue from 'vue';
import App from './app.vue';
import './style.scss'
new Vue(App).$mount('#vueapp')
Vue 应用程序
./src/web/app.vue
包含我们的 vue 应用程序作为单个文件组件。
<template>
我们构建了一个简单的导航菜单和router -view(它是我们单页应用程序的宿主),所有其他页面都挂载在 router-view 元素中。在这个模板中,我们使用了pug
而不是 html。
在<script>
我们导入 vue 框架和两个自定义模块、_router.js
和之后,我们通过使用刚刚加载的存储和路由器_store.js
模块扩展默认 vue 应用程序来创建我们的 vue 应用程序。
我们使用scss(捆绑器将转换为 css)为<style>
菜单提供一些本地(范围)样式
<template lang="pug">
div
nav.navbar
router-link(to="/") home
router-link(to="/profile") profile
router-link(to="/about") about
router-view
</template>
<script>
import Vue from "vue";
import {router} from './_router.js';
import {store} from './_store.js'
export default Vue.extend({
store: store,
router: router
});
</script>
<style lang="scss" scoped>
.navbar {
text-align: center;
* + * {
margin-left: 8px;
}
}
</style>
路由器
./src/web/_router.js
通过加载所有页面并声明其路由来配置和初始化vue-router 。
import Vue from "vue";
import VueRouter from 'vue-router';
Vue.use(VueRouter)
// 1. Import Components
import home from './vues/home.vue'
import about from './vues/about.vue'
import profile from './vues/profile.vue'
// 2. Define some routes
const routes = [
{ path: '/' , component: home },
{ path: '/profile', component: profile },
{ path: '/about' , component: about }
]
// 3. Create & Export the router
export const router = new VueRouter({
routes: routes
})
店铺
./src/web/_store.js
配置并初始化 Vuex 存储模块。它声明全局状态和可用的变更。Vuex 允许所有视图组件(通过变更)修改全局状态,同时保持框架的响应性。(例如,提交变更将更新所有受状态变化影响的组件)。
import Vue from 'vue'
import Vuex from 'vuex';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
name: 'Unknown'
},
// Usege: $store.commit('mutationan', parameter)
mutations: {
setName(state, name) {
Vue.set(state, 'name', name);
}
}
});
页面
我们的示例中有三个页面,主页和关于几乎相同,都呈现商店的名称属性。
profile提供了一个输入框,用户可以在其中输入自己的姓名,并在输入值发生变化时立即更新全局状态。
./src/web/vues/about.vue
<template lang="pug">
div
h1 About
p Welcome: {{$store.state.name}}
</template>
<script>
export default {
}
</script>
./src/web/vues/home.vue
<template>
<div>
<h1>Home</h1>
<p> Welcome: {{$store.state.name}}</p>
</div>
</template>
<script>
export default {
}
</script>
./src/web/profile.vue
<template lang="pug">
div
h1 Profile
p Welcome: {{$store.state.name}}
div.form
table
tr
td Name
td
input(:value="$store.state.name" @input="$store.commit('setName',$event.target.value)")
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
.form {
display: flex;
justify-content: center;
}
</style>
发展
为了在此模板上进行开发,需要执行以下步骤
-
下载或克隆代码
-
安装包裹
npm i -g parcel-bundler
-
安装项目依赖项
npm install
(在项目根目录中) -
运行开发脚本
npm run dev