Giter Site home page Giter Site logo

jerryc8080 / glacierjs Goto Github PK

View Code? Open in Web Editor NEW
183.0 183.0 17.0 94.62 MB

🚀 A progressive framework helps you to build PWA app easier !!!

Home Page: https://jerryc8080.github.io/GlacierJS/

License: MIT License

TypeScript 86.49% JavaScript 13.51%
pwa

glacierjs's Introduction

Wellcome ! It’s a pleasure to meet you !!

💡 A Little Invention - 一些小发明

📖 Thematic Study - 主题研究

glacierjs's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

glacierjs's Issues

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. 方案三:这三个生命周期不做命名空间隔离,因为插件本身就是被安装的。
      • 方案四:进行中间件队列隔离
        1. app.use(scope1, middlewares1); app.use(scope2, middlewares2) 分别创建两个隔离的队列
        2. 事件触发的时候,获取处于 visable 的 client,通过 url 去寻找匹配的多个队列。
        3. 队列按照 app.use 注册时的顺序串行执行。
        4. 最后跟全局队列串行起来
    2. onUninstall: 不做隔离
    3. onFetch: 运行时,判断是哪个 client 发起的请求
      1. 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. 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 的注册和安装

构建升级

构建技术栈选型:

  1. 构建工具:nx、rollup
  2. monorepo: pnpm、changset
  3. 子包命令:build、watch
  4. 主包命令:test、doc、version、publish

Github Action 接入

Github Action 接入

  1. 流水线检测:单测、ESlint
  2. 文档构建
  3. 版本升级
  4. 发布 NPM、github release

P1 - PWA 单测实践

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

img

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

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

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

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

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.