中国語ドキュメント: https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
サービスワーカーの役割
でService Worker を参照するとJavaScript
、サイトのオフライン サポートが有効になります。Service Worker は、Web アプリケーションとブラウザーのネットワーク層の間に位置するネットワーク プロキシです。サービス ワーカーは、Web アプリケーションからのネットワーク リクエストをインターセプトして処理し、オフライン サポートのためにキャッシュからデータを提供するなど、それらのリクエストに応答する方法を決定します。
Service Worker を参照するコードJavaScript
は通常、ブラウザが Service Worker をサポートしているかどうかを確認し、Service Worker を登録して、Web
アプリケーションが利用可能になったときにインストールしてアクティブ化できるようにします。Service Worker が登録されると、オフライン サポートのためにアプリケーション リソースをキャッシュしたり、プッシュ通知やバックグラウンド同期などのその他の高度な操作を実行したりできます。
全体として、サービス ワーカーを参照するコードは、アプリケーションにより豊かで動的なユーザー エクスペリエンスを提供し、Web サイトのパフォーマンスと使いやすさを向上させるJavaScript
ことができます。Web
Service Worker を JavaScript で実装するには、通常、次の手順に従う必要があります。
1. Service Worker を登録するには、
まず Web アプリケーションに Service Worker を登録する必要があります。アプリケーションの JavaScript ファイルには次のコードを記述できます。
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
console.log('Service Worker registered with scope:', registration.scope);
}).catch(function(error) {
console.error('Service Worker registration failed:', error);
});
}
上記のコードの主な機能は、ブラウザが Service Worker をサポートしているかどうかを確認し、ブラウザが Service Worker をサポートしている場合は Service Worker を登録することです。
- Service Worker ライフサイクルの定義
Service Worker には、インストール、アクティブ化、フェッチという 3 つの主要なライフサイクル イベントがあります。
インストール イベントは、Service Worker のインストール後にアプリケーションのリソースを事前にキャッシュするために使用されます。このイベントをリッスンして、CSS、JS、画像などのリソースをキャッシュします。以下に例を示します。
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('myCache')
.then(function(cache) {
return cache.addAll([
'/index.html',
'/bundle.js',
'/styles.css'
]);
})
);
});
上記のコードの主な機能は、Service Worker のインストール時に指定されたファイルを事前にキャッシュすることです。Service Worker のインストール後、これらのファイルはブラウザのローカル ストレージにキャッシュできます。
activate イベントは、古いキャッシュをクリアするために使用されます。activate イベントで新旧キャッシュ ファイルのファイル名を比較することができ、最新のキャッシュ ファイル名と古いキャッシュ ファイル名が一致しない場合は、古いキャッシュ ファイルを削除してください。以下に例を示します。
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
return cacheName !== 'myCache';
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
Service Worker は、どのリソースをキャッシュする必要があるかを決定した後、フェッチ イベントを使用してネットワーク リクエストをインターセプトし、キャッシュされたリソースを返すことができます。以下に例を示します。
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
前述のコードでは、ネットワーク リクエストがキャッシュされたファイルと一致すると、ファイルがキャッシュから直接返されます。リクエストがキャッシュされていない場合は、JavaScript のフェッチ関数を使用してネットワークからリソースをフェッチします。
上記はService Workerの基本的な実装のプロセスとコード例です。Service Worker
インストールが完了すると、ブラウザーService Worker
はライフサイクル イベントを自動的に登録および監視し、コード ロジックに従ってオフライン アクセスやページ読み込み速度の最適化などの機能を実装します。
const workerReady = new Promise((resolve, reject) => {
if (!areServiceWorkersEnabled()) {
return reject(new Error('Service Workers are not enabled. Webviews will not work. Try disabling private/incognito mode.'));
}
const swPath = `service-worker.js?v=${
expectedWorkerVersion}&vscode-resource-base-authority=${
searchParams.get('vscode-resource-base-authority')}&remoteAuthority=${
searchParams.get('remoteAuthority') ?? ''}`;
navigator.serviceWorker.register(swPath)
.then(() => navigator.serviceWorker.ready)
.then(async registration => {
/**
* @param {MessageEvent} event
*/
const versionHandler = async (event) => {
if (event.data.channel !== 'version') {
return;
}
navigator.serviceWorker.removeEventListener('message', versionHandler);
if (event.data.version === expectedWorkerVersion) {
return resolve();
} else {
console.log(`Found unexpected service worker version. Found: ${
event.data.version}. Expected: ${
expectedWorkerVersion}`);
console.log(`Attempting to reload service worker`);
// If we have the wrong version, try once (and only once) to unregister and re-register
// Note that `.update` doesn't seem to work desktop electron at the moment so we use
// `unregister` and `register` here.
return registration.unregister()
.then(() => navigator.serviceWorker.register(swPath))
.then(() => navigator.serviceWorker.ready)
.finally(() => {
resolve(); });
}
};
navigator.serviceWorker.addEventListener('message', versionHandler);
const postVersionMessage = (/** @type {ServiceWorker} */ controller) => {
controller.postMessage({
channel: 'version' });
};
// At this point, either the service worker is ready and
// became our controller, or we need to wait for it.
// Note that navigator.serviceWorker.controller could be a
// controller from a previously loaded service worker.
const currentController = navigator.serviceWorker.controller;
if (currentController?.scriptURL.endsWith(swPath)) {
// service worker already loaded & ready to receive messages
postVersionMessage(currentController);
} else {
// either there's no controlling service worker, or it's an old one:
// wait for it to change before posting the message
const onControllerChange = () => {
navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange);
postVersionMessage(navigator.serviceWorker.controller);
};
navigator.serviceWorker.addEventListener('controllerchange', onControllerChange);
}
}).catch(error => {
reject(new Error(`Could not register service workers: ${
error}.`));
});
});
サービスワーカー.js
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// @ts-check
/// <reference no-default-lib="true"/>
/// <reference lib="webworker" />
const sw = /** @type {ServiceWorkerGlobalScope} */ (/** @type {any} */ (self));
const VERSION = 4;
const resourceCacheName = `vscode-resource-cache-${
VERSION}`;
const rootPath = sw.location.pathname.replace(/\/service-worker.js$/, '');
const searchParams = new URL(location.toString()).searchParams;
const remoteAuthority = searchParams.get('remoteAuthority');
/**
* Origin used for resources
*/
const resourceBaseAuthority = searchParams.get('vscode-resource-base-authority');
const resolveTimeout = 30000;
/**
* @template T
* @typedef {
{
* resolve: (x: T) => void,
* promise: Promise<T>
* }} RequestStoreEntry
*/
/**
* Caches
* @template T
*/
class RequestStore {
constructor() {
/** @type {Map<number, RequestStoreEntry<T>>} */
this.map = new Map();
this.requestPool = 0;
}
/**
* @param {number} requestId
* @return {Promise<T> | undefined}
*/
get(requestId) {
const entry = this.map.get(requestId);
return entry && entry.promise;
}
/**
* @returns {
{ requestId: number, promise: Promise<T> }}
*/
create() {
const requestId = ++this.requestPool;
/** @type {undefined | ((x: T) => void)} */
let resolve;
/** @type {Promise<T>} */
const promise = new Promise(r => resolve = r);
/** @type {RequestStoreEntry<T>} */
const entry = {
resolve: /** @type {(x: T) => void} */ (resolve), promise };
this.map.set(requestId, entry);
const dispose = () => {
clearTimeout(timeout);
const existingEntry = this.map.get(requestId);
if (existingEntry === entry) {
return this.map.delete(requestId);
}
};
const timeout = setTimeout(dispose, resolveTimeout);
return {
requestId, promise };
}
/**
* @param {number} requestId
* @param {T} result
* @return {boolean}
*/
resolve(requestId, result) {
const entry = this.map.get(requestId);
if (!entry) {
return false;
}
entry.resolve(result);
this.map.delete(requestId);
return true;
}
}
/**
* @typedef {
{ readonly status: 200; id: number; path: string; mime: string; data: Uint8Array; etag: string | undefined; mtime: number | undefined; }
* | { readonly status: 304; id: number; path: string; mime: string; mtime: number | undefined }
* | { readonly status: 401; id: number; path: string }
* | { readonly status: 404; id: number; path: string }} ResourceResponse
*/
/**
* Map of requested paths to responses.
*
* @type {RequestStore<ResourceResponse>}
*/
const resourceRequestStore = new RequestStore();
/**
* Map of requested localhost origins to optional redirects.
*
* @type {RequestStore<string | undefined>}
*/
const localhostRequestStore = new RequestStore();
const unauthorized = () =>
new Response('Unauthorized', {
status: 401, });
const notFound = () =>
new Response('Not Found', {
status: 404, });
const methodNotAllowed = () =>
new Response('Method Not Allowed', {
status: 405, });
sw.addEventListener('message', async (event) => {
switch (event.data.channel) {
case 'version':
{
const source = /** @type {Client} */ (event.source);
sw.clients.get(source.id).then(client => {
if (client) {
client.postMessage({
channel: 'version',
version: VERSION
});
}
});
return;
}
case 'did-load-resource':
{
/** @type {ResourceResponse} */
const response = event.data.data;
if (!resourceRequestStore.resolve(response.id, response)) {
console.log('Could not resolve unknown resource', response.path);
}
return;
}
case 'did-load-localhost':
{
const data = event.data.data;
if (!localhostRequestStore.resolve(data.id, data.location)) {
console.log('Could not resolve unknown localhost', data.origin);
}
return;
}
default:
console.log('Unknown message');
return;
}
});
sw.addEventListener('fetch', (event) => {
const requestUrl = new URL(event.request.url);
if (requestUrl.protocol === 'https:' && requestUrl.hostname.endsWith('.' + resourceBaseAuthority)) {
switch (event.request.method) {
case 'GET':
case 'HEAD': {
const firstHostSegment = requestUrl.hostname.slice(0, requestUrl.hostname.length - (resourceBaseAuthority.length + 1));
const scheme = firstHostSegment.split('+', 1)[0];
const authority = firstHostSegment.slice(scheme.length + 1); // may be empty
return event.respondWith(processResourceRequest(event, {
scheme,
authority,
path: requestUrl.pathname,
query: requestUrl.search.replace(/^\?/, ''),
}));
}
default:
return event.respondWith(methodNotAllowed());
}
}
// If we're making a request against the remote authority, we want to go
// back through VS Code itself so that we are authenticated properly
if (requestUrl.host === remoteAuthority) {
switch (event.request.method) {
case 'GET':
case 'HEAD':
return event.respondWith(processResourceRequest(event, {
path: requestUrl.pathname,
scheme: requestUrl.protocol.slice(0, requestUrl.protocol.length - 1),
authority: requestUrl.host,
query: requestUrl.search.replace(/^\?/, ''),
}));
default:
return event.respondWith(methodNotAllowed());
}
}
// See if it's a localhost request
if (requestUrl.origin !== sw.origin && requestUrl.host.match(/^(localhost|127.0.0.1|0.0.0.0):(\d+)$/)) {
return event.respondWith(processLocalhostRequest(event, requestUrl));
}
});
sw.addEventListener('install', (event) => {
event.waitUntil(sw.skipWaiting()); // Activate worker immediately
});
sw.addEventListener('activate', (event) => {
event.waitUntil(sw.clients.claim()); // Become available to all pages
});
/**
* @param {FetchEvent} event
* @param {
{
* scheme: string;
* authority: string;
* path: string;
* query: string;
* }} requestUrlComponents
*/
async function processResourceRequest(event, requestUrlComponents) {
const client = await sw.clients.get(event.clientId);
if (!client) {
console.error('Could not find inner client for request');
return notFound();
}
const webviewId = getWebviewIdForClient(client);
if (!webviewId) {
console.error('Could not resolve webview id');
return notFound();
}
const shouldTryCaching = (event.request.method === 'GET');
/**
* @param {ResourceResponse} entry
* @param {Response | undefined} cachedResponse
*/
const resolveResourceEntry = (entry, cachedResponse) => {
if (entry.status === 304) {
// Not modified
if (cachedResponse) {
return cachedResponse.clone();
} else {
throw new Error('No cache found');
}
}
if (entry.status === 401) {
return unauthorized();
}
if (entry.status !== 200) {
return notFound();
}
/** @type {Record<string, string>} */
const headers = {
'Content-Type': entry.mime,
'Content-Length': entry.data.byteLength.toString(),
'Access-Control-Allow-Origin': '*',
};
if (entry.etag) {
headers['ETag'] = entry.etag;
headers['Cache-Control'] = 'no-cache';
}
if (entry.mtime) {
headers['Last-Modified'] = new Date(entry.mtime).toUTCString();
}
const response = new Response(entry.data, {
status: 200,
headers
});
if (shouldTryCaching && entry.etag) {
caches.open(resourceCacheName).then(cache => {
return cache.put(event.request, response);
});
}
return response.clone();
};
const parentClients = await getOuterIframeClient(webviewId);
if (!parentClients.length) {
console.log('Could not find parent client for request');
return notFound();
}
/** @type {Response | undefined} */
let cached;
if (shouldTryCaching) {
const cache = await caches.open(resourceCacheName);
cached = await cache.match(event.request);
}
const {
requestId, promise } = resourceRequestStore.create();
for (const parentClient of parentClients) {
parentClient.postMessage({
channel: 'load-resource',
id: requestId,
scheme: requestUrlComponents.scheme,
authority: requestUrlComponents.authority,
path: requestUrlComponents.path,
query: requestUrlComponents.query,
ifNoneMatch: cached?.headers.get('ETag'),
});
}
return promise.then(entry => resolveResourceEntry(entry, cached));
}
/**
* @param {FetchEvent} event
* @param {URL} requestUrl
* @return {Promise<Response>}
*/
async function processLocalhostRequest(event, requestUrl) {
const client = await sw.clients.get(event.clientId);
if (!client) {
// This is expected when requesting resources on other localhost ports
// that are not spawned by vs code
return fetch(event.request);
}
const webviewId = getWebviewIdForClient(client);
if (!webviewId) {
console.error('Could not resolve webview id');
return fetch(event.request);
}
const origin = requestUrl.origin;
/**
* @param {string | undefined} redirectOrigin
* @return {Promise<Response>}
*/
const resolveRedirect = async (redirectOrigin) => {
if (!redirectOrigin) {
return fetch(event.request);
}
const location = event.request.url.replace(new RegExp(`^${
requestUrl.origin}(/|$)`), `${
redirectOrigin}$1`);
return new Response(null, {
status: 302,
headers: {
Location: location
}
});
};
const parentClients = await getOuterIframeClient(webviewId);
if (!parentClients.length) {
console.log('Could not find parent client for request');
return notFound();
}
const {
requestId, promise } = localhostRequestStore.create();
for (const parentClient of parentClients) {
parentClient.postMessage({
channel: 'load-localhost',
origin: origin,
id: requestId,
});
}
return promise.then(resolveRedirect);
}
/**
* @param {Client} client
* @returns {string | null}
*/
function getWebviewIdForClient(client) {
const requesterClientUrl = new URL(client.url);
return requesterClientUrl.searchParams.get('id');
}
/**
* @param {string} webviewId
* @returns {Promise<Client[]>}
*/
async function getOuterIframeClient(webviewId) {
const allClients = await sw.clients.matchAll({
includeUncontrolled: true });
return allClients.filter(client => {
const clientUrl = new URL(client.url);
const hasExpectedPathName = (clientUrl.pathname === `${
rootPath}/` || clientUrl.pathname === `${
rootPath}/index.html` || clientUrl.pathname === `${
rootPath}/index-no-csp.html`);
return hasExpectedPathName && clientUrl.searchParams.get('id') === webviewId;
});
}
https://blog.csdn.net/qq_41581588/article/details/126739689
https://www.cnblogs.com/dojo-lzz/p/8047336.html以下にビデオがあります
navigator.serviceWorker.register と新しい Worker の違い
navigator.serviceWorker.register と new Worker は、まったく異なる 2 つの API です。
navigator.serviceWorker.register:
Service Worker の登録 API です。この API を通じて、JavaScript コード ファイルを Service Worker としてブラウザに登録し、Web ページのキャッシュ、オフライン サポート、プッシュ通知、バックグラウンド データの同期などの機能を制御できます。new Worker:
これは、Web Worker 用の作成 API です。この API を通じて、JavaScript コード ファイルをブラウザーにワーカーとして登録し、バックグラウンド スレッドで実行できます。Web ワーカーはメインスレッドと並行して実行できるため、多くの計算負荷の高い操作を処理するのに最適です。
つまり、navigator.serviceWorker.register は、Web ページのキャッシュ、オフライン サポート、その他の機能の制御に使用されるService Worker の登録に使用され、新しい Worker は、JavaScript コード ファイルを並列実行するために使用される Web Worker の作成に使用されます。バックグラウンド スレッドで。
新しいワーカーの使用を理解する方法については、私の他の記事をご覧ください
https://blog.csdn.net/woyebuzhidao321/article/details/126618420