开发者指南

开发者指南

  本章中我们将简单介绍如何为 swpp 进行平台适配。

依赖安装

  在 package.jsondependencies 中添加 swpp-backends 的依赖即可完成依赖的安装。

  但是这种方法在 swpp-backends 更新时,平台实现也需要同步更新,使用起来不是特别方便,所以在这里我们推荐使用 peerDependencies

平台适配

  swpp 已经封装了大部分代码,进行适配时只需要线性调用接口即可,您可以参考 swpp-backends 中的 cli.tshexo-swpp 的代码。

  插件必要的初始化部分按照如下流程进行(部分流程之间无先后顺序):

  1. 初始化 swpp-backends 的配置(ConfigLoader
  2. 扫描网站的资源(ResourcesScanner
  3. 生成 sw 文件
  4. 必要时修改已有的 HTML 文件
  5. 将生成的内容写入到指定位置

  通常您可以直接使用 BasicActions 类,该类中已经为您封装了通用的构建流程:

import {BasicActions} from 'swpp-backends'
 
// 其中的路径均相对于项目根目录
const actions = await BasicActions.build(
    'dev',            // 环境类型,填 'dev' 或 'prod'
    '<public path>',  // 网站的根目录
    true,             // 是否生成 sw.js
    '<dom js path>',  // DOM JS 的生成目录,留空表示不生成
    '<diff json path>'// diff json 的生成目录,留空表示不生成
)
 
// 加载配置文件
await actions.loadConfig('<config js/ts path>')
// 您也可以直接从代码中加载
// await actions.loadConfig({xxx})
// 或批量加载
// await actions.loadConfigs([{}, '<config1 js/ts path>', xxx])
 
// 构建配置
actions.buildConfig()
 
// 构建文件
await actions.buildFiles()

  您可以根据自己的需要在函数调用前后添加您的代码,以实现更复杂的功能。如果 BasicActions 无法满足您的需求,您也可以绕过 BasicActions 完全自己编写代码。

  下面我们介绍下常用的一些接口:

加载 swpp 配置

  加载配置相当简单,swpp-backends 已经将所有操作封装到了 ConfigLoader 当中,使用如下代码即可定义一个加载器:

import {ConfigLoader} from 'swpp-backends'
 
// noinspection JSUnusedLocalSymbols
const loader = new ConfigLoader();

  调用 ConfigLoader#load 即可读取指定配置,参数为配置文件(js 或 ts)的绝对路径,越早被加载的配置项优先级越高。

  有些时候,您可能想要直接从代码中设置一些配置,则可以调用 ConfigLoader#loadFromCode 函数,该函数加载的配置的优先级同样取决于调用顺序。如果混用两种加载方式,也是先调用的生效。

  下面,我们给出一个示例:

import {ConfigLoader, AllowNotFoundEnum} from 'swpp-backends'
 
const loader = new ConfigLoader();
await loader.loadFromCode({
    compilationEnv: {
        DOMAIN_HOST: new URL('http://localhost:8080'),
        PUBLIC_PATH: 'publish/'
    }
})
/*
  example.ts:
  import {AllowNotFoundEnum, defineConfig} from 'swpp-backends'
  defineConfig({
      compilationEnv: {
          DOMAIN_HOST: new URL('https://example.com'),
          ALLOW_NOT_FOUND: AllowNotFoundEnum.ALLOW_ALL
      }
  })
 */
await loader.load('config/example.ts')
await loader.loadFromCode({
    compilationEnv: {
        ALLOW_NOT_FOUND: AllowNotFoundEnum.REJECT_ALL
    }
})

  最终生效的配置为:

import {AllowNotFoundEnum, defineConfig} from 'swpp-backends'
 
defineConfig({
    compilationEnv: {
        DOMAIN_HOST: new URL('http://localhost:8080'),
        PUBLIC_PATH: 'publish/',
        ALLOW_NOT_FOUND: AllowNotFoundEnum.ALLOW_ALL
    }
})

读取 swpp 配置

  您或许经常需要读取 swpp 配置,以此决定您是否执行某个操作或其它事情。

  当您使用 ConfigLoader 加载配置后,可以调用 generate 函数生成最终的配置项。generate 函数会返回一个对象,其中包含 compilationruntime 两个字段,前者是构建期可读的配置,后者是运行时可读的配置。这两个字段中会包含若干个 KeyValueDatabase,通过调用 KeyValueDatabaseread 函数即可读取指定配置。

  推荐您使用具有完整类型推导和自动补全的 IDE(或编辑器),比如 WebStorm、vscode 等。因为 swpp 为配置项添加了复杂的数据类型(可能会难以阅读),IDE 可以通过这些类型为您提供自动补全和静态检查,以帮助您获取到更佳的开发体验。

  接下来我们举一个🌰:

// noinspection TypeScriptUnresolvedReference,JSUnusedLocalSymbols
 
import {ConfigLoader} from 'swpp-backends'
 
// 我们假设您已经有了一个 ConfigLoader
let loader: ConfigLoader
const {runtime, compilation} = loader.generate()
 
// 通过这个代码您可以读取 CrossDep 分支下的 matchCacheRule 配置
// 当然,您可以使用 compilation.crossDep 代替 runtime.crossDep,这两者指向同一对象
runtime.crossDep.read('matchCacheRule')

扫描网站资源

  swpp 将资源扫描的操作封装到了 ResourcesScanner 类中,创建该类的对象并调用其中的 scanLocalFile 函数即可完成资源的扫描,该函数接受一个文件路径(相对于项目根目录或绝对路径),该路径应当指向一个文件夹。该函数返回一个 FileUpdateTracker 对象,该对象记录了扫描的结果,除非您知道您在干什么,否则不要修改这个对象的内容。

  调用 FileUpdateTrackerdiff 函数即可获取到当前本地的文件与上一次构建时的差异,调用后 swpp 将自动从网络拉取上一次构建的结果。该函数返回一个 JsonBuilder 对象,除非您知道您在干什么,否则不要修改这个对象的内容。

  调用 JsonBuilderbuildJson 函数,即可生成本次构建的版本文件,通过 JSON.stringify 函数将其转换为字符串。

  调用 FileUpdateTrackerjson 函数可以获取到本次构建的跟踪器(tracker)的 json 序列化字符串,其中包含下一次构建时需要重建的信息,diff 函数强依赖上一次构建生成的 tracker 的 json。

  如果您想要将本次构建与上次构建的文件差异导出到文件,可以调用 JsonBuilder#serialize 函数,该函数将构建器的数据序列化为 json 字符串。

  接下来我们举一个示例:

// noinspection TypeScriptUnresolvedReference
 
import {ConfigLoader, ResourcesScanner} from 'swpp-backends'
 
// 我们假设您已经有了一个 ConfigLoader
let loader: ConfigLoader
 
async function scannerAndGenerate() {
    const {compilation} = loader.generate()
    const scanner = new ResourcesScanner(compilation)
    const tracker = await scanner.scanLocalFile('public')
    const jsonBuilder = await tracker.diff()
    const versionJson = jsonBuilder.buildJson()
    const trackerJson = tracker.json()
    return {versionJson, trackerJson}
}
 
const {versionJson, trackerJson} = await scannerAndGenerate()
// do somethings...

构建 SW JS

  SwCompiler 类用于构建 sw 文件,创建 SwCompiler 的对象并调用 buildSwCode 函数就能生成 sw 文件的 js 代码字符串。

构建 DOM JS

  构建 dom js 的操作相对比较特殊,dom js 的内容被保存到了 domConfig 中,这是一个 KeyValueDatabase,其中有两个用于构建源代码的函数:

  1. buildJsSource - 该函数返回一个完整的 JS 文件的字符串,外部使用 DOMContentLoaded 事件包裹。
  2. buildInnerSource - 该函数返回一个 JS 代码的字符串,内容是 buildJsSource 的事件内的代码,通常使用上者即可。

写入文件

  根据不同平台,您需要自行决定使用什么方法将 swpp 生成的数据写入到文件中。

  下面是各个文件写入的路径的读取方式:

// noinspection TypeScriptUnresolvedReference
 
import nodePath from 'path'
import {ConfigLoader, SwCompiler, UpdateJson} from 'swpp-backends'
 
// 假设已经有了需要的所有数据
let loader: ConfigLoader
const {runtime, compilation} = loader.generate()
let versionJson: UpdateJson
let trackerJson: string
let swJsCode = new SwCompiler()
const jsonInfo = compilation.compilationEnv.read('SWPP_JSON_FILE')
const publicRoot = compilationData.compilationEnv.read('PUBLIC_PATH')
 
// 版本文件的内容
const versionJsonText = JSON.stringify(versionJson)
// tracker 文件的内容
const trackerJsonText = trackerJson
// sw 文件的内容
const swJsText = swJsCode.buildSwCode(runtime)
// dom js 文件的内容
const domJsText = runtime.domConfig.buildJsSource()
 
// 版本文件的路径
const versionJsonPath = nodePath.join(publicRoot, jsonInfo.swppPath, jsonInfo.versionPath)
// tracker 文件的路径
const trackerJsonPath = nodePath.join(publicRoot, jsonInfo.swppPath, jsonInfo.trackerPath)
// sw 文件的路径
const swJsPath = nodePath.join(publicRoot, compilation.compilationEnv.read('SERVICE_WORKER') + '.js')
// dom js 文件的路径
const domJsPath = nodePath.join(publicRoot, '<这里自定义>.js')
// diff 文件(构建器的序列化结果)的路径完全有您自己把控,swpp 不会依赖这个文件的内容