A framework dedicated to making it easier for you to build enterprise-grade PWA applications.

Overview

Introduction

📖 🇨🇳 中文文档

📖 🇬🇧 Document for English

A framework dedicated to making it easier for you to build enterprise-grade PWA applications.

Features

  • 🧳 Out of the box
  • 🗽 Multi-dimensional plug-in system based on onion model, programming is more decoupled and free
  • 🚀 SW security registration and uninstallation
  • 🎡 Static resource caching
  • 🎢 Resource pre-cache
  • 🎠 Remote control
  • ⛲️ Data metrics collection

Motivation

Due to the complexity of Service Worker technology, we need to know a lot of related knowledge in the development of PWA applications.
Google Workbox provides a set of convenient APIs to simplify common SW operations such as SW registration and installation, resource caching, etc., but its positioning is: "Library".

When there are more and more SW program codes, it will cause the code to be bloated, the management is chaotic, and the reuse is difficult.
At the same time, some common PWA implementations, such as: remote control, process communication, data reporting, etc., hope to achieve on-demand pluggable reuse.
We need : "Framework".

Can we use Workbox as the underlying technology and build a higher-level abstraction framework on top of it to solve these problems?
So, I built a basic suite: GlacierJS.
It is based on a core "multi-dimensional onion plug-in system" and multiple plug-ins, allowing you to build an enterprise-level PWA application faster.

Glacier is a tribute to the former Lavas

Simplest example

In Main thread

">
<script src="//cdn.jsdelivr.net/npm/@glacierjs/core/dist/index.min.js" >script>
<script src="//cdn.jsdelivr.net/npm/@glacierjs/window/dist/index.min.js">script>

<script>
    const { GlacierWindow } = window['@glacierjs/window'];
    const glacier = new GlacierWindow('./service-worker.js');

    glacier.register().then((registration) => {
      console.log('Register service-worker succeed', registration);
    }).catch((error) => {
      console.error('Register service-worker failed', error);
    });
script>

In ServiceWorker thread

importScripts("//cdn.jsdelivr.net/npm/@glacierjs/core/dist/index.js");
importScripts('//cdn.jsdelivr.net/npm/@glacierjs/sw/dist/index.js');

const { GlacierSW } = self['@glacierjs/sw'];
const glacierSW = new GlacierSW();
glacierSW.listen();

Design overview

Architecture

logo

It consists of several parts:

  • Core

    • @glacierjs/core: As the core of Glacier, it implements functions such as plug-in system, log system, etc.Generally, you will not use this module directly.
    • @glacierjs/sw: The code running in the SW process encapsulates the SW life cycle and provides a simple programming method.
    • @glacierjs/window: The code running in the main process, in addition to supporting plug-in programming, is also responsible for managing the registration and uninstallation of SW.
  • Plugins

    • @glacierjs/plugin-precache: Implement static resource pre-cache function.
    • @glacierjs/plugin-reporter: Realize basic data reporting function.
    • @glacierjs/plugin-assets: Implement static resource caching.
    • @glacierjs/plugin-remote-controller: Implement remote control function.
  • Related

    • @glacierjs/cli: Supports rapid creation of applications and plugins.
    • @glacierjs/webpack-plugin: support building static resource manifests.

Multidimensional Onion Plugin System

GlacierJS encapsulates traditional ServiceWorker lifecycle hooks to support plug-in. The plug-in system implements an "onion" for each native lifecycle hook according to the onion model, so we call this system:

Multidimensional Onion Plugin System

GlacierJS 多维洋葱插件系统

After encapsulating the traditional life cycle, we provide a more elegant life cycle hook function for each plugin

GlacierJS 生命周期图示

Contact & Support

License

This project is licensed under the MIT license.

Copyright (c) JerryC Huang ([email protected])

You might also like...

A utility package for making discord-bot commands much easier to write with discord.js.

Cordcommand About A utility package for making discord-bot commands much easier to write with discord.js. Usage Example // initiate discord.js client

Dec 13, 2022

A template created with the intention of making my life easier when starting a project, and the lives of other people. :

Express API A simple and improved api with ExpressJS. 📚 | Glossary Dependencies How to Start Routes Controllers & Models License Author 🗃 | Dependec

Sep 22, 2022

A dedicated desktop app that enables you to move items in and out of storage units in CSGO.

CASEMOVE Casemove is an open-source desktop application that helps you easily move items out of and into Storage Units in Counter-Strike: Global Offen

Dec 24, 2022

Multithread emulator. The wrun allows you to dynamically run a function inside a Web Worker on the client side, without the needing of a dedicated file

wrun This lib allows you to dynamically run a function inside a Web Worker on the client side, without the needing of a dedicated file. This means tha

Nov 5, 2022

A collection of functions and methods to make it easier for you to create applications.

def-helper A collection of functions and methods to make it easier for you to create applications. Install npm install --save def-helper Usage import

Oct 13, 2022

Helps with math up to grade 10/11

Infinite-powers The code was written in p5js and the platform I used was CodePen. What the code does is that it provides help with different math topi

Jun 19, 2022

next-graphql-server is a library for building production-grade GraphQL servers using Next.js with API Routes

next-graphql-server next-graphql-server is an easy to use Next.js library for creating performant GraphQL endpoints on top of Next.js API Routes. Star

Nov 21, 2022

A tool for managing production-grade cloud clusters, infrastructure as code

Cloudy Description Cloudy is an "infrastructure as code" tool for managing production-grade cloud clusters. It's based on Pulumi that mostly using Ter

Jan 1, 2023
Comments
  • P0 - 插件支持作用域隔离

    P0 - 插件支持作用域隔离

    作用域冲突

    我们都知道关于 ServiceWorker 的作用域有两个关键特性:

    1. 默认的作用域是注册时候的 Path。
    2. 同个路径下同时间只能有一个 ServiceWorker 得到控制权。

    作用域缩小与扩大

    关于第一个特性,例如注册 Service Worker 文件为 /a/b/sw.js,则 scope 默认为 /a/b/

    if (navigator.serviceWorker) {
        navigator.serviceWorker.register('/a/b/sw.js').then(function (reg) {
            console.log(reg.scope);
            // scope => https://yourhost/a/b/
        });
    }
    

    当然我们可以在注册的的时候指定 scope 去向下缩小作用域,例如:

    if (navigator.serviceWorker) {
        navigator.serviceWorker.register('/a/b/sw.js', {scope: '/a/b/c/'})
            .then(function (reg) {
                console.log(reg.scope);
                // scope => https://yourhost/a/b/c/
            });
    }
    

    也可以通过服务器对 ServiceWorker 文件的响应设置 Service-Worker-Allowed 头部,去扩大作用域。

    例如 Google Docs 在作用域 https://docs.google.com/document/u/0/ 注册了一个来自于 https://docs.google.com/document/offline/serviceworker.js 的 ServiceWorker

    img

    MPA下的 ServiceWorker 治理

    现代 Web App 项目主要有两种架构形式存在: SPA(Single Page Application)MPA(Multiple Page Application)

    MPA 这种架构的模式在现如今的大型 Web App 非常常见,这种 Web App 相比较于 SPA 能够承受更重的业务体量,并且利于大型 Web App 的后期维护和扩展,它往往会有多个团队去维护。

    假设我们有一个 MPA 的站点:

    .
    |-- app1
    |   |-- app1-service-worker.js
    |   `-- index.html
    |-- app2
    |   `-- index.html
    |-- index.html
    `-- root-service-worker.js
    

    app1app2 分别由不同的团队维护。

    如果我们在根目录 '/' 注册了 root-service-worker.js,去完成一些通用的功能,例如:「日志收集」、「静态资源缓存」等。

    然后 app1 团队利用 ServiceWorker 的能力开发了一些特定的功能需要,例如 app1 的「离线化功能」。

    他们在 app1/index.html目录注册了 app1-service-worker.js

    这时候,访问 app1/* 下的所有页面,ServiceWorker 控制权会交给 app1-service-worker.js,也就是只有app1的「离线化功能」在工作,而原来的「日志收集」、「静态缓存」等功能会失效。

    显然这种情况是我们不希望看到的,并且在实际的开发中发生的概率会很大。

    解决这个问题有两种方案:

    1. 封装「日志收集」、「静态资源缓存」功能,app1-service-worker.js引入并使用这些功能。
    2. 把「离线化功能」整合到 root-service-worker.js,只允许注册该 ServiceWorker。

    关于方案一,封装通用功能这是正确的,但是主域下的功能可能完全没办法一一拆解,并且后续主域的 ServiceWorker 更新了新功能,子域下的 ServiceWorker 还需要主动去更新和升级。

    关于方案二,显然可以解决方案一的问题,但是其他应用,例如 app2 可能不需要「离线化功能」。

    基于此,我们引入方案三:功能整合到主域,支持功能的组合按照作用域隔离。

    基于 GlacierJS 的话代码上可能会是这样的:

    const mainPlugins = [
      new Collector(); // 日志收集功能
      new AssetsCache(); // 静态资源缓存功能
    ];
    
    glacier.use('/', mainPlugins);
    glacier.use('/app1', [
      ...mainPlugins,
      new Offiline(),  // 离线化功能
    ]);
    

    详细设计

    1. MiddlewareQueue 支持拦截器写法

    2. 增加 scope-validator 拦截器

     const scopeWrapped = scopeValidator({
      scope: '/app1/:module',
      getRuntimePath: () => location.pathname,
     })((context) => {
      // ...原有插件的逻辑
     
      // 读取路径匹配参数,假如:locations.pathname = '/app1/goods'
      context.pathParams; // { module: 'goods' }
     });
     
     // 作为中间件,入队 MiddlewareQueue
     middlewareQueue.push(scopeWrapped);
    

    3. getRuntimePath 的来源

    1. window 线程:location.pathname
    2. service-worker 线程:
      1. onInstall & onActivate:
        1. 方案一:获取 self.registration.scope ?
          1. 只局限在注册的时候指定的最高 scope,并不能指的当前资源
        2. 方案二:获取当前 visibilitystate === 'visible' 的 client.url ?
          1. 存在问题:多个窗口打开时,visiable 会存在多个 client 为 true
            1. foucsed 是否能解决?
        3. 方案三:这三个生命周期不做命名空间隔离,因为插件本身就是被安装的。
        • [x] 方案四:进行中间件队列隔离
          1. app.use(scope1, middlewares1); app.use(scope2, middlewares2) 分别创建两个隔离的队列
          2. 事件触发的时候,获取处于 visable 的 client,通过 url 去寻找匹配的多个队列。
          3. 队列按照 app.use 注册时的顺序串行执行。
          4. 最后跟全局队列串行起来
      2. onUninstall: 不做隔离
      3. onFetch: 运行时,判断是哪个 client 发起的请求
        1. [x] fetchEvent.clientId 能用么?
          1. 当 fetchEvent 是 navigation fetch(即在浏览器直接输入地址),clientId 会为空
            1. 这时候就取资源的 url
            2. 是否还有更充分的判断条件来识别 navigation fetch
        2. fetchEvent.resultingClientId 能用么?
          1. id 在 navigation 完成之后会变化
          2. 如果在 navigation fetch 执行 await clients.get(resultingClientId) 会阻塞,猜测应该是要等 onfetch 处理完之后才进行 resultingClientId 的初始化。所以做不到获取 url 去判断是否能阻断当前 onfetch
      4. onMessage:运行时,判断是哪个 client 过来的消息
        1. [x] ExtendableMessageEvent.source.url
      5. 处理方式:
        1. 只支持运行时事件支持 scope :message & fetch
          1. 优点:对于 client 的判断很准确,就算client未被激活。
          2. 缺点:app.use(scope, middlewares),并不像代码表现得那么确切。
        2. 支持全部事件隔离:(存在争议,后续再考虑是否加上)
          1. 优点:app.use(scope, middlewares), 就像代码表示的那样完全隔离。
          2. 思考:
            1. 是否有必要?
              1. 有必要:保持与 window 侧的逻辑一样,实现全部逻辑的隔离
            2. 如果前一次安装,没有命中,后面修改代码,是否也能命中?
              1. 会命中,修改代码会触发 sw 的注册和安装
    feature 
    opened by JerryC8080 4
  • chore: update versions

    chore: update versions

    This PR was opened by the Changesets release GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to master, this PR will be updated.

    Releases

    @glacierjs/[email protected]

    Patch Changes

    • 22b4218: code optimize

    @glacierjs/[email protected]

    Patch Changes

    @glacierjs/[email protected]

    Patch Changes

    @glacierjs/[email protected]

    Patch Changes

    @glacierjs/[email protected]

    Patch Changes

    @glacierjs/[email protected]

    Patch Changes

    @glacierjs/[email protected]

    Patch Changes

    @glacierjs/[email protected]

    Patch Changes

    • 22b4218: code optimize
    opened by github-actions[bot] 0
  • P1 - PWA 单测实践

    P1 - PWA 单测实践

    ServiceWorker 测试可以分解为常见的测试组。

    img

    在顶层的是 「集成测试」,在这一层,我们检查整体的行为,例如:测试页面可加载,ServiceWorker注册,离线功能等。集成测试是最慢的,但是也是最接近现实情况的。

    再往下一层的是 「浏览器单元测试」,由于 ServiceWorker 的生命周期,以及一些 API 只有在浏览器环境下才能有,所以我们使用浏览器去进行单元测试,会减少很多环境的问题。

    接着是 「ServiceWorker 单元测试」,这种测试也是在浏览器环境中注册了测试用的 ServiceWorker 为前提进行的单元测试。

    最后一种是 「模拟 ServiceWorker」,这种测试粒度会更加精细,精细到某个类某个方法,只检测入参和返回。这意味着没有了浏览器启动成本,并且最终是一种可预测的方式测试代码的方式。

    但是模拟 ServiceWorker 是一件困难的事情,如果 mock 的 API 表面不正确,则在集成测试或者浏览器单元测试之前问题不会被发现。我们可以使用 service-worker-mock 或者 MSW 在 NodeJS 环境中进行 ServiceWorker 的单元测试。

    opened by JerryC8080 0
Owner
JerryC
Peace of mind, Code of enjoy
JerryC
Rollup + Babel + Prettier + Strict ESlint + VSCode - Enterprise grade boilerplate

Javascript package boilerplate by HackingBay Rollup + Babel + Prettier + Strict ESlint + VSCode - Enterprise grade boilerplate Minimalist js package b

HackingBay 1 Dec 28, 2021
Rollup + React + Babel + Prettier + Strict ESlint and Stylelint + Sass + VSCode + Playground app - Enterprise grade boilerplate

React package boilerplate by HackingBay Rollup + React 17 + Babel + Prettier + Strict ESlint and Stylelint + Sass + VSCode + Playground app - Enterpri

HackingBay 2 Jan 19, 2022
This repo is dedicated to making minimal repos of existing defi primatives.

Defi Minimal This repo is dedicated to making minimal repos of existing defi primatives. WARNING: None of the contracts are audited! Completed (but un

SmartContract 302 Jan 7, 2023
Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all with IndexedDB. Perfectly suitable for your next (PWA) app.

BrowstorJS ?? ?? ?? Persistent key/value data storage for your Browser and/or PWA, promisified, including file support and service worker support, all

Nullix 8 Aug 5, 2022
Grupprojekt för kurserna 'Javascript med Ramverk' och 'Agil Utveckling'

JavaScript-med-Ramverk-Laboration-3 Grupprojektet för kurserna Javascript med Ramverk och Agil Utveckling. Utvecklingsguide För information om hur utv

Svante Jonsson IT-Högskolan 3 May 18, 2022
Hemsida för personer i Sverige som kan och vill erbjuda boende till människor på flykt

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

null 4 May 3, 2022
Kurs-repo för kursen Webbserver och Databaser

Webbserver och databaser This repository is meant for CME students to access exercises and codealongs that happen throughout the course. I hope you wi

null 14 Jan 3, 2023
A collection of utilities I use when making vanilla js applications. A mini-framework if you will.

R2.js A collection of utilities I use when making vanilla js applications. Installation Copy ./r2.js over to your project. It small. Do wtf u want wit

Ronak Badhe 9 Dec 2, 2022
Solid Forms provides several form control objects useful for making working with forms easier.

Solid Forms Solid Forms provides several form control objects useful for making working with forms easier. Demos and examples below. # solidjs yarn ad

John 28 Jan 2, 2023
An easy-to-use JavaScript library aimed at making it easier to draw on SVG elements.

svg-pen-sketch An easy-to-use JavaScript library aimed at making it easier to draw on SVG elements when using a digital pen (such as the Surface Pen).

Kevin Desousa 8 Jul 27, 2022