diff --git a/README.md b/README.md
index 453b462..9a49b01 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ The plugin comes with these tasks out of the box:
- [Purgecss](https://github.com/mohatt/gatsby-plugin-postbuild/blob/master/src/tasks/purgecss)
Optimizes HTML/CSS files by removing unused CSS selectors.
- [HTTP Headers](https://github.com/mohatt/gatsby-plugin-postbuild/blob/master/src/tasks/http-headers)
- Transforms HTML link tags with resource hints into HTTP Link headers using configuration file formats for different providers.
+ Automatically generates headers configuration file for different hosting providers
- [Minify](https://github.com/mohatt/gatsby-plugin-postbuild/blob/master/src/tasks/minify)
Minifies HTML inline scripts and styles using [terser](https://github.com/terser/terser) and [cssnano](https://github.com/cssnano/cssnano).
diff --git a/src/index.ts b/src/index.ts
index 905883a..40bf9f7 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,7 +1,7 @@
/**
* Main public interfaces
*/
-export type { IPostbuildArg } from './postbuild'
+export type { IPostbuildArg, IAssetsManifest } from './postbuild'
export type { ITaskApi, ITaskApiOptions, ITaskApiEvents, ITaskOptions, IEvents } from './tasks'
export type { Filesystem, IFilesystemReportMeta, IGlobOptions } from './filesystem'
export type { IOptions, IExtensionOptions, IOptionProcessing, IOptionProcessingStrategy } from './options'
diff --git a/src/tasks/http-headers/README.md b/src/tasks/http-headers/README.md
index 229b88a..966f30d 100644
--- a/src/tasks/http-headers/README.md
+++ b/src/tasks/http-headers/README.md
@@ -1,11 +1,10 @@
# HTTP Headers
-A Postbuild task that transforms HTML link tags with resource hints eg. `` into HTTP Link headers using configuration
-file formats for different providers. Available providers are:
+Automatically generates headers configuration file for different hosting providers. Available providers are:
- [Netlify](https://docs.netlify.com/routing/headers/)
- [Vercel](https://vercel.com/docs/configuration#project/headers)
- [Firebase](https://firebase.google.com/docs/hosting/full-config#headers)
-The task currently supports link tags with these rel types: `preconnect` `dns-prefetch` `preload` `prerender` `prefetch`
+The task also transforms HTML link tags with resource hints eg. `` into HTTP Link headers. Currently supports link tags with these rel types: `preconnect` `dns-prefetch` `preload` `prerender` `prefetch`
## Usage
```typescript
@@ -24,14 +23,16 @@ plugins: [
'[pages]': { // Add headers to all pages generated by Gatsby
'X-Robots-Tag': 'noindex',
'Link': [
- '; rel=preload; as=script',
- '; rel=preload; as=image'
+ '<[asset:/static/logo.png]>; rel=preload; as=image', // Will be translated to ; rel=preload; as=image
+ '; rel=preload; as=script',
],
},
'[assets]': {}, // Add headers to all immutable assets generated by Gatsby
'[page-data]': {}, // Add headers to page-data/* files
'[static]': {}, // Add headers to static/* files
- '/custom-path/*': {} // Add custom path headers
+ '/path/to/unkown': {}, // Add custom path headers
+ '/about': {}, // Overwrite headers for specific page
+ '/static/logo.png': {}, // Overwrite headers for specific asset. Will be translated to `/static/logo-[hash].png`
},
security: true, // Adds some basic security headers
caching: true, // Adds essensial caching headers
diff --git a/src/tasks/http-headers/index.ts b/src/tasks/http-headers/index.ts
index aa0c6c6..a1662b0 100644
--- a/src/tasks/http-headers/index.ts
+++ b/src/tasks/http-headers/index.ts
@@ -11,8 +11,8 @@ let builder: Builder
// @ts-expect-error
export const events: ITaskApiEvents = {
on: {
- postbuild: ({ options, filesystem, gatsby }) => {
- builder = new Builder(options, filesystem, gatsby.pathPrefix)
+ postbuild: ({ options, filesystem, gatsby, assets }) => {
+ builder = new Builder(options, assets, filesystem, gatsby.pathPrefix)
},
shutdown: () => {
return builder.build()
@@ -45,12 +45,6 @@ export const events: ITaskApiEvents = {
// Add the created link to the current page path
builder.addPageLink(path, link)
-
- // If the link's href refers to a local path send it to the builder
- // to decide whether it should be listed as an immutable cached asset
- if (!/^\w+:\/\//.test(link.href)) {
- builder.addCachedAsset(link)
- }
}
// Remove the link node if specified in task options
diff --git a/src/tasks/http-headers/lib/builder.ts b/src/tasks/http-headers/lib/builder.ts
index c262801..fbea81c 100644
--- a/src/tasks/http-headers/lib/builder.ts
+++ b/src/tasks/http-headers/lib/builder.ts
@@ -1,7 +1,7 @@
import _ from 'lodash'
import Link from './link'
import Provider from './providers'
-import type { Filesystem } from '@postbuild'
+import type { Filesystem, IAssetsManifest } from '@postbuild'
import type { IOptions, IHeadersMap, IPathHeadersMap } from '../options'
export enum PathPlaceholder {
@@ -53,18 +53,19 @@ const HEADERS_CACHING: IPathHeadersMap = {
*/
export default class Builder {
headers: IPathHeadersMap
- cachedAssets: string[] = []
pages: {
[path: string]: Link[]
} = {}
readonly provider: Provider
readonly options: IOptions
+ readonly assets: IAssetsManifest
readonly fs: Filesystem
readonly pathPrefix: string
- constructor (options: IOptions, fs: Filesystem, pathPrefix: string) {
+ constructor (options: IOptions, assets: IAssetsManifest, fs: Filesystem, pathPrefix: string) {
this.provider = Provider.factory(options, fs)
this.options = options
+ this.assets = assets
this.fs = fs
this.pathPrefix = pathPrefix
this.headers = {
@@ -73,23 +74,6 @@ export default class Builder {
}
}
- /**
- * Adds a Link's href path as an immutable cached asset if
- * it matches certain critiria
- */
- addCachedAsset (link: Link): void {
- if (
- !this.options.caching ||
- this.options.cachingAssetTypes.length === 0 ||
- link.type !== 'preload' ||
- link.href.indexOf('/page-data/') === 0 ||
- link.href.indexOf('/static/') === 0
- ) return
- if (this.options.cachingAssetTypes.includes(link.attrs.as)) {
- this.cachedAssets.push(link.href)
- }
- }
-
/**
* Adds a Link to a page path
*/
@@ -106,6 +90,32 @@ export default class Builder {
: href
}
+ processHeaderValue (value: string[]): string[]
+ processHeaderValue (value: string): string
+ processHeaderValue (value: string|string[]): string|string[] {
+ if (Array.isArray(value)) {
+ return value.map(this.processHeaderValue.bind(this))
+ }
+
+ const matches = value.match(/\[(\w+):([^\]]+)]/)
+ if (matches?.[1] != null && matches[2]) {
+ const placeholder = matches[1].toLowerCase()
+ const placeholderValue = matches[2]
+
+ if (placeholder === 'asset') {
+ const asset = placeholderValue.slice(1)
+ if (this.assets.has(asset)) {
+ return value.replace(matches[0], '/' + (this.assets.get(asset) as string))
+ }
+ throw new Error(`Unable to find asset "${placeholderValue}" in value "${value}"`)
+ }
+
+ throw new Error(`Invalid dynamic placeholder "${placeholder}" in value "${value}"`)
+ }
+
+ return value
+ }
+
protected isPathPlaceholder (path: string): path is PathPlaceholder {
return /^\[[^\]]+]$/.test(path)
}
@@ -137,7 +147,7 @@ export default class Builder {
`Headers with multi-value support are: ${multiValueHeaders.join(', ')}`
)
}
- dest[path][name] = value
+ dest[path][name] = this.processHeaderValue(value as any)
}
}
@@ -169,13 +179,19 @@ export default class Builder {
this.headers[path] = userHeaders[path]
}
- if (this.options.caching && this.cachedAssets.length) {
- for (const asset of this.cachedAssets) {
- this.headers[asset] = this.mergeHeaders({
- ...this.headers[PathPlaceholder.Assets]
- }, this.headers[asset])
+ this.assets.forEach((hashed, original) => {
+ hashed = `/${hashed}`
+ original = `/${original}`
+ if (hashed.startsWith('/static/') || original === '/styles.css') {
+ return
}
- }
+ this.headers[hashed] = this.mergeHeaders({
+ ...this.headers[PathPlaceholder.Assets]
+ }, this.headers[original])
+ if (original in this.headers) {
+ delete this.headers[original]
+ }
+ })
for (const path in this.pages) {
// @todo: Validate the return value of a user-defined callback
diff --git a/src/tasks/http-headers/options.ts b/src/tasks/http-headers/options.ts
index 3c8501d..a200226 100644
--- a/src/tasks/http-headers/options.ts
+++ b/src/tasks/http-headers/options.ts
@@ -38,7 +38,6 @@ export type IOptions = ITaskOptions & {
headers: IPathHeadersMap
security: boolean
caching: boolean
- cachingAssetTypes: string[]
transformPathLinks: (links: Link[], path: string) => Link[]
removeLinkTags: boolean
}
@@ -54,7 +53,6 @@ export const options: ITaskApiOptions = {
headers: {},
security: true,
caching: true,
- cachingAssetTypes: ['image', 'script', 'style', 'font'],
transformPathLinks: links => links,
removeLinkTags: true
},
@@ -79,9 +77,6 @@ export const options: ITaskApiOptions = {
.description('Adds useful security headers.'),
caching: joi.boolean()
.description('Adds useful caching headers to immutable asset paths.'),
- cachingAssetTypes: joi.array()
- .items(nonEmptyString)
- .description('Specifies the types of assets that are considered immutable.'),
transformPathLinks: joi.function().maxArity(2)
.description('Callback for manipulating links under each path.'),
removeLinkTags: joi.boolean()