mirror of
https://github.com/hanzoai/workbox.git
synced 2026-09-24 17:04:35 +00:00
@@ -46,31 +46,23 @@ async function generateWorkboxBuildJSONSchema(packagePath) {
|
||||
];
|
||||
for (const optionType of optionTypes) {
|
||||
const schema = generator.getSchemaForSymbol(optionType);
|
||||
// Ideally, we'd set typeOfKeyword so that functions could be represented.
|
||||
// Instead, we need to hardcode a few overrides to deal with functions.
|
||||
// See https://github.com/YousefED/typescript-json-schema/issues/424
|
||||
|
||||
if (schema.properties.manifestTransforms) {
|
||||
schema.properties.manifestTransforms.items = {typeof: 'function'};
|
||||
schema.properties.manifestTransforms.items = {};
|
||||
}
|
||||
|
||||
if (schema.properties.exclude) {
|
||||
schema.properties.exclude.items.anyOf = [
|
||||
{'$ref': '#/definitions/RegExp'},
|
||||
{type: 'string'},
|
||||
{typeof: 'function'},
|
||||
];
|
||||
schema.properties.exclude.items = {};
|
||||
}
|
||||
|
||||
if (schema.properties.include) {
|
||||
schema.properties.include.items.anyOf = [
|
||||
{'$ref': '#/definitions/RegExp'},
|
||||
{type: 'string'},
|
||||
{typeof: 'function'},
|
||||
];
|
||||
schema.properties.include.items = {};
|
||||
}
|
||||
|
||||
if (schema.definitions.RouteMatchCallback) {
|
||||
delete schema.definitions.RouteMatchCallback.type;
|
||||
delete schema.definitions.RouteMatchCallback.additionalProperties;
|
||||
schema.definitions.RouteMatchCallback.typeof = 'function';
|
||||
schema.definitions.RouteMatchCallback = {};
|
||||
}
|
||||
|
||||
await fse.writeJSON(upath.join(packagePath, 'src', 'schema',
|
||||
`${optionType}.json`), schema, {spaces: 2});
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
"@rollup/plugin-replace": "^2.4.1",
|
||||
"@surma/rollup-plugin-off-main-thread": "^1.4.1",
|
||||
"ajv": "^8.6.0",
|
||||
"ajv-keywords": "^5.0.0",
|
||||
"common-tags": "^1.8.0",
|
||||
"fast-json-stable-stringify": "^2.1.0",
|
||||
"fs-extra": "^9.0.1",
|
||||
|
||||
@@ -118,4 +118,6 @@ export const errors = {
|
||||
configure a runtimeCaching route that will use the preloaded response.`,
|
||||
'cache-name-required': ol`When using cache expiration, you must also
|
||||
configure a custom cacheName.`,
|
||||
'manifest-transforms': ol`When using manifestTransforms, you must provide
|
||||
an array of functions.`,
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import {betterAjvErrors} from '@apideck/better-ajv-errors';
|
||||
import {oneLine as ol} from 'common-tags';
|
||||
import Ajv, {JSONSchemaType} from 'ajv';
|
||||
import ajvKeywords from 'ajv-keywords';
|
||||
|
||||
import {errors} from './errors';
|
||||
|
||||
@@ -21,15 +20,18 @@ import {
|
||||
WebpackInjectManifestOptions,
|
||||
} from '../types';
|
||||
|
||||
type MethodNames = 'GenerateSW' | 'GetManifest' | 'InjectManifest' |
|
||||
'WebpackGenerateSW' | 'WebpackInjectManifest';
|
||||
type MethodNames =
|
||||
| 'GenerateSW'
|
||||
| 'GetManifest'
|
||||
| 'InjectManifest'
|
||||
| 'WebpackGenerateSW'
|
||||
| 'WebpackInjectManifest';
|
||||
|
||||
const ajv = new Ajv({
|
||||
useDefaults: true,
|
||||
});
|
||||
ajvKeywords(ajv, ['typeof']);
|
||||
|
||||
const DEFAULT_EXCLUDE_VALUE= [/\.map$/, /^manifest.*\.js$/];
|
||||
const DEFAULT_EXCLUDE_VALUE = [/\.map$/, /^manifest.*\.js$/];
|
||||
|
||||
export class WorkboxConfigError extends Error {
|
||||
constructor(message?: string) {
|
||||
@@ -41,10 +43,12 @@ export class WorkboxConfigError extends Error {
|
||||
function validate<T>(input: unknown, methodName: MethodNames): T {
|
||||
// Don't mutate input: https://github.com/GoogleChrome/workbox/issues/2158
|
||||
const inputCopy = Object.assign({}, input);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const jsonSchema: JSONSchemaType<T> = require(`../schema/${methodName}Options.json`);
|
||||
const validate = ajv.compile(jsonSchema);
|
||||
if (validate(inputCopy)) {
|
||||
// All methods support manifestTransforms, so validate it here.
|
||||
ensureValidManifestTransforms(inputCopy);
|
||||
return inputCopy;
|
||||
}
|
||||
|
||||
@@ -56,19 +60,42 @@ function validate<T>(input: unknown, methodName: MethodNames): T {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
schema: jsonSchema as any,
|
||||
});
|
||||
const messages = betterErrors.map((err) => ol`[${err.path}] ${err.message}.
|
||||
${err.suggestion ? err.suggestion : ''}`);
|
||||
const messages = betterErrors.map(
|
||||
(err) => ol`[${err.path}] ${err.message}.
|
||||
${err.suggestion ? err.suggestion : ''}`,
|
||||
);
|
||||
|
||||
throw new WorkboxConfigError(messages.join('\n\n'));
|
||||
}
|
||||
|
||||
function ensureValidManifestTransforms(
|
||||
options:
|
||||
| GenerateSWOptions
|
||||
| GetManifestOptions
|
||||
| InjectManifestOptions
|
||||
| WebpackGenerateSWOptions
|
||||
| WebpackInjectManifestOptions,
|
||||
): void {
|
||||
if (
|
||||
'manifestTransforms' in options &&
|
||||
!(
|
||||
Array.isArray(options.manifestTransforms) &&
|
||||
options.manifestTransforms.every((item) => typeof item === 'function')
|
||||
)
|
||||
) {
|
||||
throw new WorkboxConfigError(errors['manifest-transforms']);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureValidNavigationPreloadConfig(
|
||||
options: GenerateSWOptions | WebpackGenerateSWOptions,
|
||||
): void {
|
||||
if (options.navigationPreload &&
|
||||
(!Array.isArray(options.runtimeCaching) ||
|
||||
options.runtimeCaching.length === 0)) {
|
||||
throw new WorkboxConfigError(errors['nav-preload-runtime-caching']);
|
||||
if (
|
||||
options.navigationPreload &&
|
||||
(!Array.isArray(options.runtimeCaching) ||
|
||||
options.runtimeCaching.length === 0)
|
||||
) {
|
||||
throw new WorkboxConfigError(errors['nav-preload-runtime-caching']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +103,10 @@ function ensureValidCacheExpiration(
|
||||
options: GenerateSWOptions | WebpackGenerateSWOptions,
|
||||
): void {
|
||||
for (const runtimeCaching of options.runtimeCaching || []) {
|
||||
if (runtimeCaching.options?.expiration && !runtimeCaching.options?.cacheName) {
|
||||
if (
|
||||
runtimeCaching.options?.expiration &&
|
||||
!runtimeCaching.options?.cacheName
|
||||
) {
|
||||
throw new WorkboxConfigError(errors['cache-name-required']);
|
||||
}
|
||||
}
|
||||
@@ -85,10 +115,14 @@ function ensureValidCacheExpiration(
|
||||
function ensureValidRuntimeCachingOrGlobDirectory(
|
||||
options: GenerateSWOptions,
|
||||
): void {
|
||||
if (!options.globDirectory &&
|
||||
(!Array.isArray(options.runtimeCaching) ||
|
||||
options.runtimeCaching.length === 0)) {
|
||||
throw new WorkboxConfigError(errors['no-manifest-entries-or-runtime-caching']);
|
||||
if (
|
||||
!options.globDirectory &&
|
||||
(!Array.isArray(options.runtimeCaching) ||
|
||||
options.runtimeCaching.length === 0)
|
||||
) {
|
||||
throw new WorkboxConfigError(
|
||||
errors['no-manifest-entries-or-runtime-caching'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +131,7 @@ export function validateGenerateSWOptions(input: unknown): GenerateSWOptions {
|
||||
ensureValidNavigationPreloadConfig(validatedOptions);
|
||||
ensureValidCacheExpiration(validatedOptions);
|
||||
ensureValidRuntimeCachingOrGlobDirectory(validatedOptions);
|
||||
|
||||
return validatedOptions;
|
||||
}
|
||||
|
||||
@@ -104,27 +139,47 @@ export function validateGetManifestOptions(input: unknown): GetManifestOptions {
|
||||
return validate<GetManifestOptions>(input, 'GetManifest');
|
||||
}
|
||||
|
||||
export function validateInjectManifestOptions(input: unknown): InjectManifestOptions {
|
||||
export function validateInjectManifestOptions(
|
||||
input: unknown,
|
||||
): InjectManifestOptions {
|
||||
return validate<InjectManifestOptions>(input, 'InjectManifest');
|
||||
}
|
||||
|
||||
// The default `exclude: [/\.map$/, /^manifest.*\.js$/]` value can't be
|
||||
// represented in the JSON schema, so manually set it for the webpack options.
|
||||
export function validateWebpackGenerateSWOptions(input: unknown): WebpackGenerateSWOptions {
|
||||
const inputWithExcludeDefault = Object.assign({
|
||||
// Make a copy, as exclude can be mutated when used.
|
||||
exclude: Array.from(DEFAULT_EXCLUDE_VALUE),
|
||||
}, input);
|
||||
const validatedOptions = validate<WebpackGenerateSWOptions>(inputWithExcludeDefault, 'WebpackGenerateSW');
|
||||
export function validateWebpackGenerateSWOptions(
|
||||
input: unknown,
|
||||
): WebpackGenerateSWOptions {
|
||||
const inputWithExcludeDefault = Object.assign(
|
||||
{
|
||||
// Make a copy, as exclude can be mutated when used.
|
||||
exclude: Array.from(DEFAULT_EXCLUDE_VALUE),
|
||||
},
|
||||
input,
|
||||
);
|
||||
const validatedOptions = validate<WebpackGenerateSWOptions>(
|
||||
inputWithExcludeDefault,
|
||||
'WebpackGenerateSW',
|
||||
);
|
||||
|
||||
ensureValidNavigationPreloadConfig(validatedOptions);
|
||||
ensureValidCacheExpiration(validatedOptions);
|
||||
|
||||
return validatedOptions;
|
||||
}
|
||||
|
||||
export function validateWebpackInjectManifestOptions(input: unknown): WebpackInjectManifestOptions {
|
||||
const inputWithExcludeDefault = Object.assign({
|
||||
// Make a copy, as exclude can be mutated when used.
|
||||
exclude: Array.from(DEFAULT_EXCLUDE_VALUE),
|
||||
}, input);
|
||||
return validate<WebpackInjectManifestOptions>(inputWithExcludeDefault, 'WebpackInjectManifest');
|
||||
export function validateWebpackInjectManifestOptions(
|
||||
input: unknown,
|
||||
): WebpackInjectManifestOptions {
|
||||
const inputWithExcludeDefault = Object.assign(
|
||||
{
|
||||
// Make a copy, as exclude can be mutated when used.
|
||||
exclude: Array.from(DEFAULT_EXCLUDE_VALUE),
|
||||
},
|
||||
input,
|
||||
);
|
||||
return validate<WebpackInjectManifestOptions>(
|
||||
inputWithExcludeDefault,
|
||||
'WebpackInjectManifest',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
},
|
||||
"manifestTransforms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"typeof": "function"
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"maximumFileSizeToCacheInBytes": {
|
||||
"default": 2097152,
|
||||
@@ -861,10 +859,7 @@
|
||||
"onabort"
|
||||
]
|
||||
},
|
||||
"RouteMatchCallback": {
|
||||
"description": "The \"match\" callback is used to determine if a `Route` should apply for a\nparticular URL and request. When matching occurs in response to a fetch\nevent from the client, the `event` object is also supplied. However, since\nthe match callback can be invoked outside of a fetch event, matching logic\nshould not assume the `event` object will always be available.\nIf the match callback returns a truthy value, the matching route's\n`RouteHandlerCallback` will be invoked immediately. If the value returned\nis a non-empty array or object, that value will be set on the handler's\n`options.params` argument.",
|
||||
"typeof": "function"
|
||||
}
|
||||
"RouteMatchCallback": {}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
},
|
||||
"manifestTransforms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"typeof": "function"
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"maximumFileSizeToCacheInBytes": {
|
||||
"default": 2097152,
|
||||
@@ -753,10 +751,7 @@
|
||||
"onabort"
|
||||
]
|
||||
},
|
||||
"RouteMatchCallback": {
|
||||
"description": "The \"match\" callback is used to determine if a `Route` should apply for a\nparticular URL and request. When matching occurs in response to a fetch\nevent from the client, the `event` object is also supplied. However, since\nthe match callback can be invoked outside of a fetch event, matching logic\nshould not assume the `event` object will always be available.\nIf the match callback returns a truthy value, the matching route's\n`RouteHandlerCallback` will be invoked immediately. If the value returned\nis a non-empty array or object, that value will be set on the handler's\n`options.params` argument.",
|
||||
"typeof": "function"
|
||||
}
|
||||
"RouteMatchCallback": {}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
},
|
||||
"manifestTransforms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"typeof": "function"
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"maximumFileSizeToCacheInBytes": {
|
||||
"default": 2097152,
|
||||
@@ -765,10 +763,7 @@
|
||||
"onabort"
|
||||
]
|
||||
},
|
||||
"RouteMatchCallback": {
|
||||
"description": "The \"match\" callback is used to determine if a `Route` should apply for a\nparticular URL and request. When matching occurs in response to a fetch\nevent from the client, the `event` object is also supplied. However, since\nthe match callback can be invoked outside of a fetch event, matching logic\nshould not assume the `event` object will always be available.\nIf the match callback returns a truthy value, the matching route's\n`RouteHandlerCallback` will be invoked immediately. If the value returned\nis a non-empty array or object, that value will be set on the handler's\n`options.params` argument.",
|
||||
"typeof": "function"
|
||||
}
|
||||
"RouteMatchCallback": {}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
},
|
||||
"manifestTransforms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"typeof": "function"
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"maximumFileSizeToCacheInBytes": {
|
||||
"default": 2097152,
|
||||
@@ -42,19 +40,7 @@
|
||||
},
|
||||
"exclude": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RegExp"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"typeof": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"excludeChunks": {
|
||||
"type": "array",
|
||||
@@ -64,19 +50,7 @@
|
||||
},
|
||||
"include": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RegExp"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"typeof": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"mode": {
|
||||
"default": "production",
|
||||
@@ -864,10 +838,7 @@
|
||||
"onabort"
|
||||
]
|
||||
},
|
||||
"RouteMatchCallback": {
|
||||
"description": "The \"match\" callback is used to determine if a `Route` should apply for a\nparticular URL and request. When matching occurs in response to a fetch\nevent from the client, the `event` object is also supplied. However, since\nthe match callback can be invoked outside of a fetch event, matching logic\nshould not assume the `event` object will always be available.\nIf the match callback returns a truthy value, the matching route's\n`RouteHandlerCallback` will be invoked immediately. If the value returned\nis a non-empty array or object, that value will be set on the handler's\n`options.params` argument.",
|
||||
"typeof": "function"
|
||||
}
|
||||
"RouteMatchCallback": {}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
},
|
||||
"manifestTransforms": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"typeof": "function"
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"maximumFileSizeToCacheInBytes": {
|
||||
"default": 2097152,
|
||||
@@ -42,19 +40,7 @@
|
||||
},
|
||||
"exclude": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RegExp"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"typeof": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"excludeChunks": {
|
||||
"type": "array",
|
||||
@@ -64,19 +50,7 @@
|
||||
},
|
||||
"include": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/RegExp"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"typeof": "function"
|
||||
}
|
||||
]
|
||||
}
|
||||
"items": {}
|
||||
},
|
||||
"mode": {
|
||||
"type": [
|
||||
@@ -776,10 +750,7 @@
|
||||
"onabort"
|
||||
]
|
||||
},
|
||||
"RouteMatchCallback": {
|
||||
"description": "The \"match\" callback is used to determine if a `Route` should apply for a\nparticular URL and request. When matching occurs in response to a fetch\nevent from the client, the `event` object is also supplied. However, since\nthe match callback can be invoked outside of a fetch event, matching logic\nshould not assume the `event` object will always be available.\nIf the match callback returns a truthy value, the matching route's\n`RouteHandlerCallback` will be invoked immediately. If the value returned\nis a non-empty array or object, that value will be set on the handler's\n`options.params` argument.",
|
||||
"typeof": "function"
|
||||
}
|
||||
"RouteMatchCallback": {}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
arrowParens: 'always',
|
||||
bracketSpacing: false,
|
||||
printWidth: 80,
|
||||
quoteProps: 'consistent',
|
||||
semi: true,
|
||||
singleQuote: true,
|
||||
tabWidth: 2,
|
||||
trailingComma: 'all',
|
||||
};
|
||||
Reference in New Issue
Block a user