Skip to content

Commit

Permalink
feat: a new header, 'x-applied-polyfills'
Browse files Browse the repository at this point in the history
Polyfiller will now apply a header called 'x-applied-polyfills' to all OK responses.
This header will include a list of all polyfills that has been applied and is part of the payload, including any meta options they may have received.
This makes it possible for you to look at that header and understand what has been polyfilled.
  • Loading branch information
wessberg committed Oct 18, 2018
1 parent 0aa836f commit 50b8010
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 80 deletions.
45 changes: 0 additions & 45 deletions .npmignore

This file was deleted.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "@wessberg/polyfiller",
"version": "0.0.34",
"description": "Never worry about polyfills again.",
"files": [
"dist/**/*.*"
],
"scripts": {
"generate:readme": "scaffold readme --blacklist intro",
"generate:license": "scaffold license",
Expand Down Expand Up @@ -73,6 +76,7 @@
"@wessberg/filesaver": "^1.0.8",
"@wessberg/pointer-events": "^1.0.5",
"@wessberg/rollup-plugin-ts": "1.0.8",
"@wessberg/stringutil": "^1.0.18",
"Base64": "^1.0.1",
"ava": "^0.25.0",
"blob-polyfill": "^3.0.20180112",
Expand Down
7 changes: 7 additions & 0 deletions src/bl/polyfill/i-get-polyfills-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {IRegistryGetResult} from "../../service/registry/polyfill-registry/i-registry-get-result";
import {IPolyfillFeature} from "../../polyfill/i-polyfill-feature";

export interface IGetPolyfillsResult {
result: IRegistryGetResult;
featureSet: Set<IPolyfillFeature>;
}
4 changes: 2 additions & 2 deletions src/bl/polyfill/i-polyfill-bl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {IPolyfillRequest} from "../../polyfill/i-polyfill-request";
import {IRegistryGetResult} from "../../service/registry/polyfill-registry/i-registry-get-result";
import {IGetPolyfillsResult} from "./i-get-polyfills-result";

export interface IPolyfillBl {
getPolyfills (request: IPolyfillRequest): Promise<IRegistryGetResult>;
getPolyfills (request: IPolyfillRequest): Promise<IGetPolyfillsResult>;
}
38 changes: 18 additions & 20 deletions src/bl/polyfill/polyfill-bl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {ICacheRegistryService} from "../../service/registry/cache-registry/i-cac
import {getOrderedPolyfillsWithDependencies} from "../../util/polyfill/polyfill-util";
import {ILoggerService} from "../../service/logger/i-logger-service";
import {ContentEncodingKind} from "../../encoding/content-encoding-kind";
import {IRegistryGetResult} from "../../service/registry/polyfill-registry/i-registry-get-result";
import {IPolyfillBuilderService} from "../../service/polyfill-builder/i-polyfill-builder-service";
import {IGetPolyfillsResult} from "./i-get-polyfills-result";

/**
* Business logic for polyfills
Expand All @@ -21,56 +21,54 @@ export class PolyfillBl implements IPolyfillBl {
/**
* Generates a chunk of polyfills that matches the given request
* @param {IPolyfillRequest} request
* @returns {Promise<IRegistryGetResult>}
* @returns {Promise<IGetPolyfillsResult>}
*/
public async getPolyfills (request: IPolyfillRequest): Promise<IRegistryGetResult> {
public async getPolyfills (request: IPolyfillRequest): Promise<IGetPolyfillsResult> {
// Check if a polyfill set exists within the cache for the request features and the user agent of the request
let polyfillSet = await this.cacheRegistry.getPolyfillFeatureSet(request.features, request.userAgent);
let featureSet = await this.cacheRegistry.getPolyfillFeatureSet(request.features, request.userAgent);

// If not, resolve and order the required polyfills
if (polyfillSet == null) {
polyfillSet = await getOrderedPolyfillsWithDependencies(
if (featureSet == null) {
featureSet = await getOrderedPolyfillsWithDependencies(
new Set([...request.features]
// Take only valid names
.filter(feature => feature.name in constant.polyfill)),
request.userAgent
);
// Store them within the cache
await this.cacheRegistry.setPolyfillFeatureSet(request.features, polyfillSet, request.userAgent);
}

else {
await this.cacheRegistry.setPolyfillFeatureSet(request.features, featureSet, request.userAgent);
} else {
this.logger.debug("Matched Polyfill Set in cache!");
}

this.logger.debug(polyfillSet);
this.logger.debug(featureSet);

// Check if a Set has already been registered for this combination
const existingSet = await this.cacheRegistry.get(polyfillSet, request.encoding);
const existingSet = await this.cacheRegistry.get(featureSet, request.encoding);
if (existingSet != null) {
this.logger.debug("Matched Polyfills in cache!");
// If it has, just return that one
return existingSet;
return {result: existingSet, featureSet};
}

// Otherwise, build the polyfills and store them in the Cache Registry before returning them
const {brotli, minified, zlib} = await this.builder.buildPolyfillSet(polyfillSet);
const {brotli, minified, zlib} = await this.builder.buildPolyfillSet(featureSet);

// Add the polyfills to the registry
const [minifiedResult, brotliResult, zlibResult] = await Promise.all([
this.cacheRegistry.set(polyfillSet, minified),
this.cacheRegistry.set(polyfillSet, brotli, ContentEncodingKind.BROTLI),
this.cacheRegistry.set(polyfillSet, zlib, ContentEncodingKind.GZIP)
this.cacheRegistry.set(featureSet, minified),
this.cacheRegistry.set(featureSet, brotli, ContentEncodingKind.BROTLI),
this.cacheRegistry.set(featureSet, zlib, ContentEncodingKind.GZIP)
]);

// Return the joined Buffer
switch (request.encoding) {
case ContentEncodingKind.BROTLI:
return brotliResult;
return {result: brotliResult, featureSet};
case ContentEncodingKind.GZIP:
return zlibResult;
return {result: zlibResult, featureSet};
case undefined:
return minifiedResult;
return {result: minifiedResult, featureSet};
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/constant/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export const constant: IConstant = {
index: ["/api", "/api/"],
polyfill: "/api/polyfill"
},
header: {
polyfills: "x-applied-polyfills",
maxChars: 600
},
meta: {
name: environment.NPM_PACKAGE_NAME,
version: environment.NPM_PACKAGE_VERSION,
Expand Down
6 changes: 6 additions & 0 deletions src/constant/i-constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ export interface IPathConstant {
cachePackageVersionMap: string;
}

export interface IHeaderConstant {
polyfills: string;
maxChars: number;
}

export interface IConstant {
endpoint: IEndpointConstant;
meta: IMetaConstant;
polyfill: PolyfillDict;
path: IPathConstant;
header: IHeaderConstant;
}
10 changes: 6 additions & 4 deletions src/controller/polyfill/polyfill-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {GET} from "../../server/decorator/get";
import {constants} from "http2";
import {OK, INTERNAL_SERVER_ERROR, NOT_MODIFIED} from "http-status-codes";
import {constant} from "../../constant/constant";
import {getPolyfillRequestFromUrl} from "../../util/polyfill/polyfill-util";
import {encodeFeatureSetForHttpHeader, getPolyfillRequestFromUrl} from "../../util/polyfill/polyfill-util";
import {IPolyfillBl} from "../../bl/polyfill/i-polyfill-bl";
import {pickEncoding} from "../../util/request-util/request-util";
import {generateErrorHtml} from "../../util/html/generate-html";
Expand All @@ -32,15 +32,16 @@ export class PolyfillController extends Controller implements IPolyfillControlle

// Generate polyfill bundle
try {
const {buffer, checksum} = await this.polyfillBl.getPolyfills(polyfillRequest);
const {result: {buffer, checksum}, featureSet} = await this.polyfillBl.getPolyfills(polyfillRequest);

// If the cached checksum (ETag) is identical, respond with NOT_MODIFIED
if (request.cachedChecksum != null && request.cachedChecksum === checksum) {
return {
statusCode: request.http2
? constants.HTTP_STATUS_NOT_MODIFIED
: NOT_MODIFIED,
cacheControl: "public,max-age=31536000,immutable"
cacheControl: "public,max-age=31536000,immutable",
polyfillsHeader: encodeFeatureSetForHttpHeader(featureSet)
};
}

Expand All @@ -53,7 +54,8 @@ export class PolyfillController extends Controller implements IPolyfillControlle
body: buffer,
cacheControl: "public,max-age=31536000,immutable",
contentEncoding: polyfillRequest.encoding,
checksum
checksum,
polyfillsHeader: encodeFeatureSetForHttpHeader(featureSet)
};
} catch (ex) {
// Respond with error code 500
Expand Down
3 changes: 2 additions & 1 deletion src/server/i-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import {ContentEncodingKind} from "../encoding/content-encoding-kind";

export interface IResponse {
statusCode: number;
cacheControl?: string;
polyfillsHeader?: string;
}

export interface IOKResponse extends IResponse {
contentType: string;
cacheControl?: string;
contentEncoding?: ContentEncodingKind;
body: any;
checksum: string;
Expand Down
31 changes: 27 additions & 4 deletions src/util/polyfill/polyfill-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import {polyfillOptionValueSeparator} from "../../polyfill/polyfill-option-value
import {createHash} from "crypto";
import {constant} from "../../constant/constant";
import {userAgentSupportsFeatures} from "@wessberg/browserslist-generator";
import {truncate} from "@wessberg/stringutil";
import {IPolyfillLibraryDictEntry, IPolyfillLocalDictEntry} from "../../polyfill/polyfill-dict";
// @ts-ignore
import toposort from "toposort";
import {IPolyfillLibraryDictEntry, IPolyfillLocalDictEntry} from "../../polyfill/polyfill-dict";

// tslint:disable

Expand Down Expand Up @@ -125,9 +126,7 @@ function getRequiredPolyfillsForUserAgent (polyfillSet: Set<IPolyfillFeatureInpu
const existingIndex = polyfillNameToPolyfillIndexMap.get(polyfill.name);
if (existingIndex != null) {
polyfills[existingIndex].meta = polyfill.meta;
}

else {
} else {
polyfills.push({name: polyfill.name, meta: polyfill.meta});
polyfillNameToPolyfillIndexMap.set(polyfill.name, currentIndex++);
polyfillNames.add(polyfill.name);
Expand Down Expand Up @@ -236,4 +235,28 @@ export function getPolyfillRequestFromUrl (url: URL, userAgent: string, encoding
encoding,
features: featureSet
};
}

/**
* Encodes a PolyfillSet such that it can be embedded in a HTTP header
* @param {Set<IPolyfillFeature>} polyfillSet
* @returns {string}
*/
export function encodeFeatureSetForHttpHeader (polyfillSet: Set<IPolyfillFeature>): string {
return truncate(
[...polyfillSet].map(({meta, name}) => {
let str = name;
if (meta != null) {
const metaEntries = Object.entries(meta);
if (metaEntries.length > 0) {
str += " (";
str += metaEntries.map(([key, value]) => `${key}: ${value}`).join(", ");
str += `)`;
}
}
return str;
}).join(", "),
{length: constant.header.maxChars,
omission: "...[omitted]"}
);
}
12 changes: 11 additions & 1 deletion src/util/request-util/request-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import chalk from "chalk";
import {connect, ClientHttp2Stream, ClientHttp2Session} from "http2";
import {ContentEncodingKind} from "../../encoding/content-encoding-kind";
import * as URLNamespace from "url";
import {constant} from "../../constant/constant";
const {URL} = URLNamespace;

// tslint:disable:no-any
Expand Down Expand Up @@ -103,14 +104,16 @@ export async function sendRequest (rawRequest: IRawRequest): Promise<Response> {
contentType: "text/plain",
statusCode: 0,
body: "",
checksum: ""
checksum: "",
polyfillsHeader: ""
};

let contentType: string|undefined;
let cacheControl: string|undefined;
let statusCode: number|undefined;
let contentEncoding: string|undefined;
let checksum: string|undefined;
let polyfillsHeader: string|undefined;

const setResponseHeaders = () => {
if (statusCode != null) {
Expand All @@ -132,6 +135,10 @@ export async function sendRequest (rawRequest: IRawRequest): Promise<Response> {
if (checksum != null) {
response.checksum = checksum;
}

if (polyfillsHeader != null) {
response.polyfillsHeader = polyfillsHeader;
}
};

const onResponse = (res: IncomingMessage|ClientHttp2Stream, client?: ClientHttp2Session) => {
Expand Down Expand Up @@ -166,10 +173,12 @@ export async function sendRequest (rawRequest: IRawRequest): Promise<Response> {
};

const incomingMessageHandler = (rawResponse: IncomingMessage) => {
console.log(rawResponse);
statusCode = rawResponse.statusCode;
contentType = rawResponse.headers["content-type"];
cacheControl = rawResponse.headers["cache-control"];
contentEncoding = rawResponse.headers["content-encoding"];
polyfillsHeader = <string|undefined> rawResponse.headers[constant.header.polyfills];
checksum = <string|undefined> rawResponse.headers.etag;
setResponseHeaders();
onResponse(rawResponse);
Expand All @@ -194,6 +203,7 @@ export async function sendRequest (rawRequest: IRawRequest): Promise<Response> {
contentType = headers["content-type"];
cacheControl = headers["cache-control"];
contentEncoding = headers["content-encoding"];
polyfillsHeader = <string|undefined> headers[constant.header.polyfills];
checksum = <string|undefined> headers.etag;
setResponseHeaders();
});
Expand Down
5 changes: 5 additions & 0 deletions src/util/response-util/response-util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {Http2ServerResponse} from "http2";
import {ServerResponse as HttpServerResponse} from "http";
import {Response} from "../../server/i-response";
import {constant} from "../../constant/constant";

// tslint:disable:no-any

Expand Down Expand Up @@ -32,6 +33,10 @@ export function respondToRequest (rawResponse: HttpServerResponse|Http2ServerRes
rawResponse.setHeader("ETag", response.checksum);
}

if ("polyfillsHeader" in response && response.polyfillsHeader != null) {
rawResponse.setHeader(constant.header.polyfills, response.polyfillsHeader);
}

// Allow any origin
rawResponse.setHeader("Access-Control-Allow-Origin", "*");

Expand Down
Loading

0 comments on commit 50b8010

Please sign in to comment.