Chinese documentation: https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
The role of service workers
The effect of referencing a service worker in JavaScript
is to enable offline support for the site. A service worker is a network proxy that sits between the web application and the browser network layer. Service workers intercept and process network requests from web applications and decide how to respond to those requests, including serving data from caches for offline support.
Code that references a service worker JavaScript
typically checks whether the browser supports service workers and registers the service worker so that Web
the application can be installed and activated when it is available. Once a service worker is registered, it can cache application resources for offline support, and perform other advanced operations such as push notifications and background synchronization.
Overall, code that references service workers JavaScript
can Web
provide a richer and more dynamic user experience for applications and improve the performance and usability of websites.
To implement Service Worker in JavaScript, you generally need to follow the following steps:
1. To register Service Worker,
you must first register Service Worker in your web application. The following code can be written in the application javascript file:
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);
});
}
The main function of the above code is to check whether the browser supports Service Worker, and register Service Worker if the browser supports it.
- Defining the Service Worker lifecycle
In a Service Worker, there are three key lifecycle events: install, activate, and fetch.
The install event is used to pre-cache the application's resources after the Service Worker is installed. Cache resources, such as CSS, JS, pictures, etc., by listening to this event. Here is an example:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('myCache')
.then(function(cache) {
return cache.addAll([
'/index.html',
'/bundle.js',
'/styles.css'
]);
})
);
});
The main function of the above code is to pre-cache the specified files when the Service Worker is installed. After the Service Worker is installed, these files can be cached in the browser's local storage.
The activate event is used to clear old caches. You can compare the file names of the new and old cache files in the activate event. If the latest cache file name is inconsistent with the old cache file name, delete the old cache. Here is an example:
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);
})
);
})
);
});
After the Service Worker determines which resources should be cached, it can use the fetch event to intercept network requests and return cached resources. Here is an example:
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
In the preceding code, when a network request matches a cached file, the file is returned directly from the cache. If the request is not cached, it uses JavaScript's fetch function to fetch the resource from the network.
The above is the process and code example of the basic implementation of Service Worker. After Service Worker
the installation is complete, the browser will automatically register and monitor Service Worker
the life cycle events, and implement functions such as offline access and page loading speed optimization according to the code logic.
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}.`));
});
});
service-worker.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 There is a video below
The difference between navigator.serviceWorker.register and new Worker
navigator.serviceWorker.register and new Worker are two completely different APIs.
navigator.serviceWorker.register:
This is the registration API for Service Workers. Through this API, we can register a JavaScript code file as a Service Worker in the browser, and control functions such as web page caching, offline support, push notifications, and background data synchronization.new Worker:
This is the creation API for Web Workers. Through this API, we can register a JavaScript code file as a Worker in the browser and run it in a background thread. Web Workers can run in parallel with the main thread, so they are ideal for handling a lot of computationally intensive operations.
In short, navigator.serviceWorker.register is used to register Service Worker, which is used to control web page caching, offline support and other functions ; while new Worker is to create Web Worker, which is used to run a JavaScript code file in parallel in the background thread .
How to understand the use of new workers, you can read my other article
https://blog.csdn.net/woyebuzhidao321/article/details/126618420