Modern WebApps - Infrastructure: Vue, Parcel & Workbox

2025-06-07

现代 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

希腊开发者的原始帖子
文章来源:https://dev.to/sarmis/single-page-progressive-web-applications-with-vue-js-2op8
PREV
可重复使用的 CSS 变量视差效果
NEXT
如何学习Web开发