@altano/html-cdnify 4.0.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,4 @@
1
1
  import { HtmlAttributeStreamTransformer, TransformDefinition } from "./HtmlAttributeStreamTransformer.js";
2
-
3
2
  //#region src/CDNTransformer.d.ts
4
3
  type CDNTransformFunction = (cdnUrl: string, oldUrl: string, bufferPath: string) => string;
5
4
  interface CDNTransformerOptions {
@@ -9,11 +8,11 @@ interface CDNTransformerOptions {
9
8
  transformFunction?: CDNTransformFunction;
10
9
  }
11
10
  /**
12
- * Wraps a node-trumpet Transform stream, w/ HTML as input and
13
- * HTML as output. The output will have all non-absolute-URL
14
- * attributes that identify CDN-able resources converted to
15
- * use the given CDN URL.
16
- */
11
+ * Wraps a node-trumpet Transform stream, w/ HTML as input and
12
+ * HTML as output. The output will have all non-absolute-URL
13
+ * attributes that identify CDN-able resources converted to
14
+ * use the given CDN URL.
15
+ */
17
16
  declare class CDNTransformer extends HtmlAttributeStreamTransformer {
18
17
  #private;
19
18
  cdnOptions: Required<CDNTransformerOptions>;
@@ -1 +1 @@
1
- {"version":3,"file":"CDNTransformer.js","names":["HtmlAttributeStreamTransformer.HtmlAttributeStreamTransformer","#defaultHtmlAttributeStreamTransformerOptions","#cdnifyAttribute","#defaultCdnOptions","urlConverter"],"sources":["../src/CDNTransformer.ts"],"sourcesContent":["import { unionBy, merge, mergeWith, type MergeWithCustomizer } from \"lodash\";\nimport urlConverter from \"./urlConverter.js\";\n\nimport * as HtmlAttributeStreamTransformer from \"./HtmlAttributeStreamTransformer.js\";\n\nexport type CDNTransformFunction = (\n cdnUrl: string,\n oldUrl: string,\n bufferPath: string,\n) => string;\n\nexport interface CDNTransformerOptions {\n cdnUrl: string;\n bufferPath?: string;\n\n transformDefinitions?: HtmlAttributeStreamTransformer.TransformDefinition[];\n transformFunction?: CDNTransformFunction;\n}\n\n/**\n * Handles merging objects that have TransformDefinition arrays\n */\nclass OptionsMerger {\n static isTransformDefinition(\n a: unknown,\n ): a is HtmlAttributeStreamTransformer.TransformDefinition {\n return (\n a != null &&\n typeof a === \"object\" &&\n \"selector\" in a &&\n \"attribute\" in a &&\n typeof a.selector === \"string\" &&\n typeof a.selector === \"string\" &&\n typeof a.attribute === \"string\"\n );\n }\n\n static isTransformDefinitions(\n a: unknown,\n ): a is HtmlAttributeStreamTransformer.TransformDefinition[] {\n return (\n Array.isArray(a) &&\n a.length > 0 &&\n OptionsMerger.isTransformDefinition(a[0])\n );\n }\n\n static mergeCustomizer: MergeWithCustomizer = (objValue, srcValue) => {\n if (\n OptionsMerger.isTransformDefinitions(objValue) &&\n OptionsMerger.isTransformDefinitions(srcValue)\n ) {\n const result = unionBy(\n srcValue,\n objValue, // reverse order to allow srcValue to take precedence\n (value: HtmlAttributeStreamTransformer.TransformDefinition) =>\n `${value.selector} / ${value.attribute}`,\n );\n\n return result;\n } else {\n return undefined;\n }\n };\n\n static merge<T, U, V, W>(\n target: T,\n source1: U,\n source2: V,\n source3: W,\n ): T & U & V & W {\n return mergeWith(\n target,\n source1,\n source2,\n source3,\n OptionsMerger.mergeCustomizer,\n );\n }\n}\n\n/**\n * Wraps a node-trumpet Transform stream, w/ HTML as input and\n * HTML as output. The output will have all non-absolute-URL\n * attributes that identify CDN-able resources converted to\n * use the given CDN URL.\n */\nexport class CDNTransformer\n extends HtmlAttributeStreamTransformer.HtmlAttributeStreamTransformer\n{\n public cdnOptions: Required<CDNTransformerOptions>;\n\n constructor(cdnOptions: CDNTransformerOptions) {\n super(\n OptionsMerger.merge(\n {},\n CDNTransformer.#defaultHtmlAttributeStreamTransformerOptions,\n {\n transformDefinitions: [],\n transformFunction: (attribute: string) =>\n this.#cdnifyAttribute(attribute),\n },\n {\n transformDefinitions: cdnOptions.transformDefinitions,\n },\n ),\n );\n\n this.cdnOptions = merge(\n {\n transformDefinitions: [],\n },\n CDNTransformer.#defaultCdnOptions,\n cdnOptions,\n );\n\n if (typeof this.cdnOptions.cdnUrl !== \"string\") {\n throw new Error(`Invalid cdnUrl specified.`);\n }\n }\n\n #cdnifyAttribute(oldUrl: string): string {\n return this.cdnOptions.transformFunction(\n this.cdnOptions.cdnUrl,\n oldUrl,\n this.cdnOptions.bufferPath,\n );\n }\n\n static #defaultCdnOptions = {\n bufferPath: \".\",\n transformFunction: urlConverter,\n } as const;\n\n public static defaultTransformFunction: CDNTransformFunction =\n CDNTransformer.#defaultCdnOptions.transformFunction;\n\n static #defaultHtmlAttributeStreamTransformerOptions = {\n attributeToMarkElementToBeIgnored: \"data-cdn-ignore\",\n\n // inspired by https://github.com/callumlocke/grunt-cdnify/blob/master/tasks/cdnify.js#L31\n transformDefinitions: [\n {\n selector: `video[poster]:not([data-cdn-ignore])`,\n attribute: \"poster\",\n },\n {\n selector: `img[data-src]:not([data-cdn-ignore])`,\n attribute: \"data-src\",\n },\n {\n selector: `script[src]:not([data-cdn-ignore])`,\n attribute: \"src\",\n },\n {\n selector: `source[src]:not([data-cdn-ignore])`,\n attribute: \"src\",\n },\n {\n selector: `link[rel=\"apple-touch-icon\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `link[rel=\"icon\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `link[rel=\"shortcut icon\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `link[rel=\"stylesheet\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `img[src]:not([data-cdn-ignore])`,\n attribute: \"src\",\n },\n {\n selector: `img[srcset]:not([data-cdn-ignore])`,\n attribute: \"srcset\",\n attributeParser(\n attr: string,\n transformFunction: HtmlAttributeStreamTransformer.TransformFunction,\n ): string {\n return attr\n .split(\",\")\n .map((imgInfo) => imgInfo.replace(/([^ ]+)/, transformFunction))\n .join(\",\");\n },\n },\n ],\n } as const;\n}\n"],"mappings":";;;;;;;;AAsBA,IAAM,gBAAN,MAAM,cAAc;CAClB,OAAO,sBACL,GACyD;AACzD,SACE,KAAK,QACL,OAAO,MAAM,YACb,cAAc,KACd,eAAe,KACf,OAAO,EAAE,aAAa,YACtB,OAAO,EAAE,aAAa,YACtB,OAAO,EAAE,cAAc;;CAI3B,OAAO,uBACL,GAC2D;AAC3D,SACE,MAAM,QAAQ,EAAE,IAChB,EAAE,SAAS,KACX,cAAc,sBAAsB,EAAE,GAAG;;CAI7C,OAAO,mBAAwC,UAAU,aAAa;AACpE,MACE,cAAc,uBAAuB,SAAS,IAC9C,cAAc,uBAAuB,SAAS,CAS9C,QAPe,QACb,UACA,WACC,UACC,GAAG,MAAM,SAAS,KAAK,MAAM,YAChC;MAID;;CAIJ,OAAO,MACL,QACA,SACA,SACA,SACe;AACf,SAAO,UACL,QACA,SACA,SACA,SACA,cAAc,gBACf;;;;;;;;;AAUL,IAAa,iBAAb,MAAa,uBACHA,+BACV;CACE,AAAO;CAEP,YAAY,YAAmC;AAC7C,QACE,cAAc,MACZ,EAAE,EACF,gBAAeC,8CACf;GACE,sBAAsB,EAAE;GACxB,oBAAoB,cAClB,MAAKC,gBAAiB,UAAU;GACnC,EACD,EACE,sBAAsB,WAAW,sBAClC,CACF,CACF;AAED,OAAK,aAAa,MAChB,EACE,sBAAsB,EAAE,EACzB,EACD,gBAAeC,mBACf,WACD;AAED,MAAI,OAAO,KAAK,WAAW,WAAW,SACpC,OAAM,IAAI,MAAM,4BAA4B;;CAIhD,iBAAiB,QAAwB;AACvC,SAAO,KAAK,WAAW,kBACrB,KAAK,WAAW,QAChB,QACA,KAAK,WAAW,WACjB;;CAGH,QAAOA,oBAAqB;EAC1B,YAAY;EACZ,mBAAmBC;EACpB;CAED,OAAc,2BACZ,gBAAeD,kBAAmB;CAEpC,QAAOF,+CAAgD;EACrD,mCAAmC;EAGnC,sBAAsB;GACpB;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACZ;GACD;IACE,UAAU;IACV,WAAW;IACX,gBACE,MACA,mBACQ;AACR,YAAO,KACJ,MAAM,IAAI,CACV,KAAK,YAAY,QAAQ,QAAQ,WAAW,kBAAkB,CAAC,CAC/D,KAAK,IAAI;;IAEf;GACF;EACF"}
1
+ {"version":3,"file":"CDNTransformer.js","names":["HtmlAttributeStreamTransformer.HtmlAttributeStreamTransformer","#defaultHtmlAttributeStreamTransformerOptions","#cdnifyAttribute","#defaultCdnOptions","urlConverter"],"sources":["../src/CDNTransformer.ts"],"sourcesContent":["import { unionBy, merge, mergeWith, type MergeWithCustomizer } from \"lodash\";\nimport urlConverter from \"./urlConverter.js\";\n\nimport * as HtmlAttributeStreamTransformer from \"./HtmlAttributeStreamTransformer.js\";\n\nexport type CDNTransformFunction = (\n cdnUrl: string,\n oldUrl: string,\n bufferPath: string,\n) => string;\n\nexport interface CDNTransformerOptions {\n cdnUrl: string;\n bufferPath?: string;\n\n transformDefinitions?: HtmlAttributeStreamTransformer.TransformDefinition[];\n transformFunction?: CDNTransformFunction;\n}\n\n/**\n * Handles merging objects that have TransformDefinition arrays\n */\nclass OptionsMerger {\n static isTransformDefinition(\n a: unknown,\n ): a is HtmlAttributeStreamTransformer.TransformDefinition {\n return (\n a != null &&\n typeof a === \"object\" &&\n \"selector\" in a &&\n \"attribute\" in a &&\n typeof a.selector === \"string\" &&\n typeof a.selector === \"string\" &&\n typeof a.attribute === \"string\"\n );\n }\n\n static isTransformDefinitions(\n a: unknown,\n ): a is HtmlAttributeStreamTransformer.TransformDefinition[] {\n return (\n Array.isArray(a) &&\n a.length > 0 &&\n OptionsMerger.isTransformDefinition(a[0])\n );\n }\n\n static mergeCustomizer: MergeWithCustomizer = (objValue, srcValue) => {\n if (\n OptionsMerger.isTransformDefinitions(objValue) &&\n OptionsMerger.isTransformDefinitions(srcValue)\n ) {\n const result = unionBy(\n srcValue,\n objValue, // reverse order to allow srcValue to take precedence\n (value: HtmlAttributeStreamTransformer.TransformDefinition) =>\n `${value.selector} / ${value.attribute}`,\n );\n\n return result;\n } else {\n return undefined;\n }\n };\n\n static merge<T, U, V, W>(\n target: T,\n source1: U,\n source2: V,\n source3: W,\n ): T & U & V & W {\n return mergeWith(\n target,\n source1,\n source2,\n source3,\n OptionsMerger.mergeCustomizer,\n );\n }\n}\n\n/**\n * Wraps a node-trumpet Transform stream, w/ HTML as input and\n * HTML as output. The output will have all non-absolute-URL\n * attributes that identify CDN-able resources converted to\n * use the given CDN URL.\n */\nexport class CDNTransformer\n extends HtmlAttributeStreamTransformer.HtmlAttributeStreamTransformer\n{\n public cdnOptions: Required<CDNTransformerOptions>;\n\n constructor(cdnOptions: CDNTransformerOptions) {\n super(\n OptionsMerger.merge(\n {},\n CDNTransformer.#defaultHtmlAttributeStreamTransformerOptions,\n {\n transformDefinitions: [],\n transformFunction: (attribute: string) =>\n this.#cdnifyAttribute(attribute),\n },\n {\n transformDefinitions: cdnOptions.transformDefinitions,\n },\n ),\n );\n\n this.cdnOptions = merge(\n {\n transformDefinitions: [],\n },\n CDNTransformer.#defaultCdnOptions,\n cdnOptions,\n );\n\n if (typeof this.cdnOptions.cdnUrl !== \"string\") {\n throw new Error(`Invalid cdnUrl specified.`);\n }\n }\n\n #cdnifyAttribute(oldUrl: string): string {\n return this.cdnOptions.transformFunction(\n this.cdnOptions.cdnUrl,\n oldUrl,\n this.cdnOptions.bufferPath,\n );\n }\n\n static #defaultCdnOptions = {\n bufferPath: \".\",\n transformFunction: urlConverter,\n } as const;\n\n public static defaultTransformFunction: CDNTransformFunction =\n CDNTransformer.#defaultCdnOptions.transformFunction;\n\n static #defaultHtmlAttributeStreamTransformerOptions = {\n attributeToMarkElementToBeIgnored: \"data-cdn-ignore\",\n\n // inspired by https://github.com/callumlocke/grunt-cdnify/blob/master/tasks/cdnify.js#L31\n transformDefinitions: [\n {\n selector: `video[poster]:not([data-cdn-ignore])`,\n attribute: \"poster\",\n },\n {\n selector: `img[data-src]:not([data-cdn-ignore])`,\n attribute: \"data-src\",\n },\n {\n selector: `script[src]:not([data-cdn-ignore])`,\n attribute: \"src\",\n },\n {\n selector: `source[src]:not([data-cdn-ignore])`,\n attribute: \"src\",\n },\n {\n selector: `link[rel=\"apple-touch-icon\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `link[rel=\"icon\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `link[rel=\"shortcut icon\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `link[rel=\"stylesheet\"]:not([data-cdn-ignore])`,\n attribute: \"href\",\n },\n {\n selector: `img[src]:not([data-cdn-ignore])`,\n attribute: \"src\",\n },\n {\n selector: `img[srcset]:not([data-cdn-ignore])`,\n attribute: \"srcset\",\n attributeParser(\n attr: string,\n transformFunction: HtmlAttributeStreamTransformer.TransformFunction,\n ): string {\n return attr\n .split(\",\")\n .map((imgInfo) => imgInfo.replace(/([^ ]+)/, transformFunction))\n .join(\",\");\n },\n },\n ],\n } as const;\n}\n"],"mappings":";;;;;;;;AAsBA,IAAM,gBAAN,MAAM,cAAc;CAClB,OAAO,sBACL,GACyD;EACzD,OACE,KAAK,QACL,OAAO,MAAM,YACb,cAAc,KACd,eAAe,KACf,OAAO,EAAE,aAAa,YACtB,OAAO,EAAE,aAAa,YACtB,OAAO,EAAE,cAAc;CAE3B;CAEA,OAAO,uBACL,GAC2D;EAC3D,OACE,MAAM,QAAQ,CAAC,KACf,EAAE,SAAS,KACX,cAAc,sBAAsB,EAAE,EAAE;CAE5C;CAEA,OAAO,mBAAwC,UAAU,aAAa;EACpE,IACE,cAAc,uBAAuB,QAAQ,KAC7C,cAAc,uBAAuB,QAAQ,GAS7C,OAPe,QACb,UACA,WACC,UACC,GAAG,MAAM,SAAS,KAAK,MAAM,WAGrB;OAEZ;CAEJ;CAEA,OAAO,MACL,QACA,SACA,SACA,SACe;EACf,OAAO,UACL,QACA,SACA,SACA,SACA,cAAc,eAChB;CACF;AACF;;;;;;;AAQA,IAAa,iBAAb,MAAa,uBACHA,+BACV;CACE,AAAO;CAEP,YAAY,YAAmC;EAC7C,MACE,cAAc,MACZ,CAAC,GACD,eAAeC,+CACf;GACE,sBAAsB,CAAC;GACvB,oBAAoB,cAClB,KAAKC,iBAAiB,SAAS;EACnC,GACA,EACE,sBAAsB,WAAW,qBACnC,CACF,CACF;EAEA,KAAK,aAAa,MAChB,EACE,sBAAsB,CAAC,EACzB,GACA,eAAeC,oBACf,UACF;EAEA,IAAI,OAAO,KAAK,WAAW,WAAW,UACpC,MAAM,IAAI,MAAM,2BAA2B;CAE/C;CAEA,iBAAiB,QAAwB;EACvC,OAAO,KAAK,WAAW,kBACrB,KAAK,WAAW,QAChB,QACA,KAAK,WAAW,UAClB;CACF;CAEA,OAAOA,qBAAqB;EAC1B,YAAY;EACZ,mBAAmBC;CACrB;CAEA,OAAc,2BACZ,eAAeD,mBAAmB;CAEpC,OAAOF,gDAAgD;EACrD,mCAAmC;EAGnC,sBAAsB;GACpB;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;GACb;GACA;IACE,UAAU;IACV,WAAW;IACX,gBACE,MACA,mBACQ;KACR,OAAO,KACJ,MAAM,GAAG,CAAC,CACV,KAAK,YAAY,QAAQ,QAAQ,WAAW,iBAAiB,CAAC,CAAC,CAC/D,KAAK,GAAG;IACb;GACF;EACF;CACF;AACF"}
@@ -1,5 +1,4 @@
1
1
  import HtmlTransformer from "./HtmlTransformer.js";
2
-
3
2
  //#region src/HtmlAttributeStreamTransformer.d.ts
4
3
  interface HtmlAttributeStreamTransformerOptions {
5
4
  transformDefinitions: TransformDefinition[];
@@ -19,14 +18,14 @@ interface AttributeParsers {
19
18
  [index: string]: AttributeParser;
20
19
  }
21
20
  /**
22
- * Wraps a node-trumpet Transform stream, w/ HTML as input and
23
- * HTML as output. The output will have all non-absolute-URL
24
- * attributes that identify CDN-able resources converted to
25
- * use the given CDN URL.
26
- */
21
+ * Wraps a node-trumpet Transform stream, w/ HTML as input and
22
+ * HTML as output. The output will have all non-absolute-URL
23
+ * attributes that identify CDN-able resources converted to
24
+ * use the given CDN URL.
25
+ */
27
26
  declare class HtmlAttributeStreamTransformer extends HtmlTransformer {
28
- options: HtmlAttributeStreamTransformerOptions;
29
27
  condensedTransformOptions: TransformDefinition[];
28
+ options: HtmlAttributeStreamTransformerOptions;
30
29
  constructor(options: HtmlAttributeStreamTransformerOptions);
31
30
  static attributeParsers: AttributeParsers;
32
31
  protected applyTransforms(): void;
@@ -10,6 +10,7 @@ import { groupBy } from "lodash";
10
10
  */
11
11
  var HtmlAttributeStreamTransformer = class HtmlAttributeStreamTransformer extends HtmlTransformer {
12
12
  condensedTransformOptions = [];
13
+ options;
13
14
  constructor(options) {
14
15
  super();
15
16
  this.options = options;
@@ -1 +1 @@
1
- {"version":3,"file":"HtmlAttributeStreamTransformer.js","names":[],"sources":["../src/HtmlAttributeStreamTransformer.ts"],"sourcesContent":["import { groupBy } from \"lodash\";\nimport HtmlTransformer from \"./HtmlTransformer.js\";\n\nexport interface HtmlAttributeStreamTransformerOptions {\n transformDefinitions: TransformDefinition[];\n transformFunction: TransformFunction;\n attributeToMarkElementToBeIgnored?: string;\n}\n\nexport interface TransformDefinition {\n selector: string;\n attribute: string;\n attributeParser?:\n | undefined\n | ((oldAttribute: string, transformFunction: TransformFunction) => string);\n}\n\nexport type TransformFunction = (oldAttribute: string) => string;\n\nexport interface AttributeParser {\n (oldAttribute: string, transformFunction: TransformFunction): string;\n}\n\nexport interface AttributeParsers {\n [index: string]: AttributeParser;\n}\n\n/**\n * Wraps a node-trumpet Transform stream, w/ HTML as input and\n * HTML as output. The output will have all non-absolute-URL\n * attributes that identify CDN-able resources converted to\n * use the given CDN URL.\n */\nexport class HtmlAttributeStreamTransformer extends HtmlTransformer {\n public condensedTransformOptions: TransformDefinition[] = [];\n\n constructor(public options: HtmlAttributeStreamTransformerOptions) {\n super();\n\n this.processTransforms();\n\n this.applyTransforms();\n }\n\n static attributeParsers: AttributeParsers = {\n default: (attr, transformFunction) => transformFunction(attr),\n };\n\n protected applyTransforms(): void {\n this.condensedTransformOptions.forEach((settings) => {\n const selector = settings.selector;\n const attribute = settings.attribute;\n const parser = settings.attributeParser;\n\n this.stream.selectAll(selector, (elem) => {\n const oldAttributeValue = elem.getAttribute(attribute);\n\n // Check that the attribute is set (which should always be the case) and the\n // typeof=string, which will be false in the case of empty attributes (which\n // cause getAttribute() to return the boolean true)\n if (typeof oldAttributeValue === \"string\") {\n const newAttributeValue =\n parser && parser(oldAttributeValue, this.options.transformFunction);\n if (newAttributeValue && newAttributeValue !== oldAttributeValue) {\n elem.setAttribute(attribute, newAttributeValue);\n }\n }\n });\n });\n\n // Run cleanup transform last\n const { attributeToMarkElementToBeIgnored } = this.options;\n if (attributeToMarkElementToBeIgnored) {\n this.stream.selectAll(\n `[${attributeToMarkElementToBeIgnored}]`,\n (elem) => {\n elem.removeAttribute(attributeToMarkElementToBeIgnored);\n },\n );\n }\n }\n\n private processTransforms(): void {\n this.options.transformDefinitions.forEach((option) => {\n if (!option.attributeParser) {\n option.attributeParser =\n HtmlAttributeStreamTransformer.attributeParsers[\"default\"];\n }\n\n if (typeof option.selector !== \"string\") {\n throw new Error(`Invalid selector specified.`);\n }\n\n if (typeof option.attribute !== \"string\") {\n throw new Error(`Invalid attribute specified.`);\n }\n\n if (!(option.attributeParser instanceof Function)) {\n throw new Error(\"Invalid attributeParser specified.\");\n }\n });\n\n /**\n * As a performance enhancement, we can condense the list of transformDefinitions such that\n * selectors with the same attribute and attributeParser can be combined into a single,\n * comma-delimited CSS selector.\n */\n const groupedTransformOptions = groupBy(\n this.options.transformDefinitions,\n (obj) => {\n return [obj.attribute, obj.attributeParser];\n },\n );\n\n this.condensedTransformOptions = Object.values(groupedTransformOptions).map(\n (likeTransformOptions) => {\n const attribute = likeTransformOptions[0]!.attribute;\n const attributeParser = likeTransformOptions[0]!.attributeParser;\n\n return {\n attribute,\n attributeParser,\n selector: likeTransformOptions\n .map((likeTransformOption) => likeTransformOption.selector)\n .join(\", \"),\n };\n },\n );\n }\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAa,iCAAb,MAAa,uCAAuC,gBAAgB;CAClE,AAAO,4BAAmD,EAAE;CAE5D,YAAY,AAAO,SAAgD;AACjE,SAAO;EADU;AAGjB,OAAK,mBAAmB;AAExB,OAAK,iBAAiB;;CAGxB,OAAO,mBAAqC,EAC1C,UAAU,MAAM,sBAAsB,kBAAkB,KAAK,EAC9D;CAED,AAAU,kBAAwB;AAChC,OAAK,0BAA0B,SAAS,aAAa;GACnD,MAAM,WAAW,SAAS;GAC1B,MAAM,YAAY,SAAS;GAC3B,MAAM,SAAS,SAAS;AAExB,QAAK,OAAO,UAAU,WAAW,SAAS;IACxC,MAAM,oBAAoB,KAAK,aAAa,UAAU;AAKtD,QAAI,OAAO,sBAAsB,UAAU;KACzC,MAAM,oBACJ,UAAU,OAAO,mBAAmB,KAAK,QAAQ,kBAAkB;AACrE,SAAI,qBAAqB,sBAAsB,kBAC7C,MAAK,aAAa,WAAW,kBAAkB;;KAGnD;IACF;EAGF,MAAM,EAAE,sCAAsC,KAAK;AACnD,MAAI,kCACF,MAAK,OAAO,UACV,IAAI,kCAAkC,KACrC,SAAS;AACR,QAAK,gBAAgB,kCAAkC;IAE1D;;CAIL,AAAQ,oBAA0B;AAChC,OAAK,QAAQ,qBAAqB,SAAS,WAAW;AACpD,OAAI,CAAC,OAAO,gBACV,QAAO,kBACL,+BAA+B,iBAAiB;AAGpD,OAAI,OAAO,OAAO,aAAa,SAC7B,OAAM,IAAI,MAAM,8BAA8B;AAGhD,OAAI,OAAO,OAAO,cAAc,SAC9B,OAAM,IAAI,MAAM,+BAA+B;AAGjD,OAAI,EAAE,OAAO,2BAA2B,UACtC,OAAM,IAAI,MAAM,qCAAqC;IAEvD;;;;;;EAOF,MAAM,0BAA0B,QAC9B,KAAK,QAAQ,uBACZ,QAAQ;AACP,UAAO,CAAC,IAAI,WAAW,IAAI,gBAAgB;IAE9C;AAED,OAAK,4BAA4B,OAAO,OAAO,wBAAwB,CAAC,KACrE,yBAAyB;AAIxB,UAAO;IACL,WAJgB,qBAAqB,GAAI;IAKzC,iBAJsB,qBAAqB,GAAI;IAK/C,UAAU,qBACP,KAAK,wBAAwB,oBAAoB,SAAS,CAC1D,KAAK,KAAK;IACd;IAEJ"}
1
+ {"version":3,"file":"HtmlAttributeStreamTransformer.js","names":[],"sources":["../src/HtmlAttributeStreamTransformer.ts"],"sourcesContent":["import { groupBy } from \"lodash\";\nimport HtmlTransformer from \"./HtmlTransformer.js\";\n\nexport interface HtmlAttributeStreamTransformerOptions {\n transformDefinitions: TransformDefinition[];\n transformFunction: TransformFunction;\n attributeToMarkElementToBeIgnored?: string;\n}\n\nexport interface TransformDefinition {\n selector: string;\n attribute: string;\n attributeParser?:\n | undefined\n | ((oldAttribute: string, transformFunction: TransformFunction) => string);\n}\n\nexport type TransformFunction = (oldAttribute: string) => string;\n\nexport interface AttributeParser {\n (oldAttribute: string, transformFunction: TransformFunction): string;\n}\n\nexport interface AttributeParsers {\n [index: string]: AttributeParser;\n}\n\n/**\n * Wraps a node-trumpet Transform stream, w/ HTML as input and\n * HTML as output. The output will have all non-absolute-URL\n * attributes that identify CDN-able resources converted to\n * use the given CDN URL.\n */\nexport class HtmlAttributeStreamTransformer extends HtmlTransformer {\n public condensedTransformOptions: TransformDefinition[] = [];\n public options: HtmlAttributeStreamTransformerOptions;\n\n constructor(options: HtmlAttributeStreamTransformerOptions) {\n super();\n\n this.options = options;\n\n this.processTransforms();\n\n this.applyTransforms();\n }\n\n static attributeParsers: AttributeParsers = {\n default: (attr, transformFunction) => transformFunction(attr),\n };\n\n protected applyTransforms(): void {\n this.condensedTransformOptions.forEach((settings) => {\n const selector = settings.selector;\n const attribute = settings.attribute;\n const parser = settings.attributeParser;\n\n this.stream.selectAll(selector, (elem) => {\n const oldAttributeValue = elem.getAttribute(attribute);\n\n // Check that the attribute is set (which should always be the case) and the\n // typeof=string, which will be false in the case of empty attributes (which\n // cause getAttribute() to return the boolean true)\n if (typeof oldAttributeValue === \"string\") {\n const newAttributeValue =\n parser && parser(oldAttributeValue, this.options.transformFunction);\n if (newAttributeValue && newAttributeValue !== oldAttributeValue) {\n elem.setAttribute(attribute, newAttributeValue);\n }\n }\n });\n });\n\n // Run cleanup transform last\n const { attributeToMarkElementToBeIgnored } = this.options;\n if (attributeToMarkElementToBeIgnored) {\n this.stream.selectAll(\n `[${attributeToMarkElementToBeIgnored}]`,\n (elem) => {\n elem.removeAttribute(attributeToMarkElementToBeIgnored);\n },\n );\n }\n }\n\n private processTransforms(): void {\n this.options.transformDefinitions.forEach((option) => {\n if (!option.attributeParser) {\n option.attributeParser =\n HtmlAttributeStreamTransformer.attributeParsers[\"default\"];\n }\n\n if (typeof option.selector !== \"string\") {\n throw new Error(`Invalid selector specified.`);\n }\n\n if (typeof option.attribute !== \"string\") {\n throw new Error(`Invalid attribute specified.`);\n }\n\n if (!(option.attributeParser instanceof Function)) {\n throw new Error(\"Invalid attributeParser specified.\");\n }\n });\n\n /**\n * As a performance enhancement, we can condense the list of transformDefinitions such that\n * selectors with the same attribute and attributeParser can be combined into a single,\n * comma-delimited CSS selector.\n */\n const groupedTransformOptions = groupBy(\n this.options.transformDefinitions,\n (obj) => {\n return [obj.attribute, obj.attributeParser];\n },\n );\n\n this.condensedTransformOptions = Object.values(groupedTransformOptions).map(\n (likeTransformOptions) => {\n const attribute = likeTransformOptions[0]!.attribute;\n const attributeParser = likeTransformOptions[0]!.attributeParser;\n\n return {\n attribute,\n attributeParser,\n selector: likeTransformOptions\n .map((likeTransformOption) => likeTransformOption.selector)\n .join(\", \"),\n };\n },\n );\n }\n}\n"],"mappings":";;;;;;;;;;AAiCA,IAAa,iCAAb,MAAa,uCAAuC,gBAAgB;CAClE,AAAO,4BAAmD,CAAC;CAC3D,AAAO;CAEP,YAAY,SAAgD;EAC1D,MAAM;EAEN,KAAK,UAAU;EAEf,KAAK,kBAAkB;EAEvB,KAAK,gBAAgB;CACvB;CAEA,OAAO,mBAAqC,EAC1C,UAAU,MAAM,sBAAsB,kBAAkB,IAAI,EAC9D;CAEA,AAAU,kBAAwB;EAChC,KAAK,0BAA0B,SAAS,aAAa;GACnD,MAAM,WAAW,SAAS;GAC1B,MAAM,YAAY,SAAS;GAC3B,MAAM,SAAS,SAAS;GAExB,KAAK,OAAO,UAAU,WAAW,SAAS;IACxC,MAAM,oBAAoB,KAAK,aAAa,SAAS;IAKrD,IAAI,OAAO,sBAAsB,UAAU;KACzC,MAAM,oBACJ,UAAU,OAAO,mBAAmB,KAAK,QAAQ,iBAAiB;KACpE,IAAI,qBAAqB,sBAAsB,mBAC7C,KAAK,aAAa,WAAW,iBAAiB;IAElD;GACF,CAAC;EACH,CAAC;EAGD,MAAM,EAAE,sCAAsC,KAAK;EACnD,IAAI,mCACF,KAAK,OAAO,UACV,IAAI,kCAAkC,KACrC,SAAS;GACR,KAAK,gBAAgB,iCAAiC;EACxD,CACF;CAEJ;CAEA,AAAQ,oBAA0B;EAChC,KAAK,QAAQ,qBAAqB,SAAS,WAAW;GACpD,IAAI,CAAC,OAAO,iBACV,OAAO,kBACL,+BAA+B,iBAAiB;GAGpD,IAAI,OAAO,OAAO,aAAa,UAC7B,MAAM,IAAI,MAAM,6BAA6B;GAG/C,IAAI,OAAO,OAAO,cAAc,UAC9B,MAAM,IAAI,MAAM,8BAA8B;GAGhD,IAAI,EAAE,OAAO,2BAA2B,WACtC,MAAM,IAAI,MAAM,oCAAoC;EAExD,CAAC;;;;;;EAOD,MAAM,0BAA0B,QAC9B,KAAK,QAAQ,uBACZ,QAAQ;GACP,OAAO,CAAC,IAAI,WAAW,IAAI,eAAe;EAC5C,CACF;EAEA,KAAK,4BAA4B,OAAO,OAAO,uBAAuB,CAAC,CAAC,KACrE,yBAAyB;GAIxB,OAAO;IACL,WAJgB,qBAAqB,EAAE,CAAE;IAKzC,iBAJsB,qBAAqB,EAAE,CAAE;IAK/C,UAAU,qBACP,KAAK,wBAAwB,oBAAoB,QAAQ,CAAC,CAC1D,KAAK,IAAI;GACd;EACF,CACF;CACF;AACF"}
@@ -1,17 +1,16 @@
1
1
  import { Trumpet } from "@gofunky/trumpet";
2
-
3
2
  //#region src/HtmlTransformer.d.ts
4
3
  type TrumpetTransformStream = Trumpet;
5
4
  declare class HtmlTransformer {
6
5
  private transformStream;
7
6
  constructor();
8
7
  /**
9
- * Get the underlying transform stream
10
- */
8
+ * Get the underlying transform stream
9
+ */
11
10
  get stream(): Trumpet;
12
11
  /**
13
- * Get a promise to the complete output Buffer (alternative API to dealing with stream)
14
- */
12
+ * Get a promise to the complete output Buffer (alternative API to dealing with stream)
13
+ */
15
14
  get outputBufferPromise(): Promise<Buffer>;
16
15
  }
17
16
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"HtmlTransformer.js","names":[],"sources":["../src/HtmlTransformer.ts"],"sourcesContent":["import trumpet, { type Trumpet } from \"@gofunky/trumpet\";\nimport streamToPromise from \"stream-to-promise\";\n\nexport type TrumpetTransformStream = Trumpet;\n\nexport default class HtmlTransformer {\n private transformStream: TrumpetTransformStream;\n\n constructor(/*public inputPath: string*/) {\n this.transformStream = trumpet();\n }\n\n /**\n * Get the underlying transform stream\n */\n get stream(): Trumpet {\n return this.transformStream;\n }\n\n /**\n * Get a promise to the complete output Buffer (alternative API to dealing with stream)\n */\n get outputBufferPromise(): Promise<Buffer> {\n return streamToPromise(this.stream);\n }\n}\n"],"mappings":";;;;AAKA,IAAqB,kBAArB,MAAqC;CACnC,AAAQ;CAER,cAA0C;AACxC,OAAK,kBAAkB,SAAS;;;;;CAMlC,IAAI,SAAkB;AACpB,SAAO,KAAK;;;;;CAMd,IAAI,sBAAuC;AACzC,SAAO,gBAAgB,KAAK,OAAO"}
1
+ {"version":3,"file":"HtmlTransformer.js","names":[],"sources":["../src/HtmlTransformer.ts"],"sourcesContent":["import trumpet, { type Trumpet } from \"@gofunky/trumpet\";\nimport streamToPromise from \"stream-to-promise\";\n\nexport type TrumpetTransformStream = Trumpet;\n\nexport default class HtmlTransformer {\n private transformStream: TrumpetTransformStream;\n\n constructor(/*public inputPath: string*/) {\n this.transformStream = trumpet();\n }\n\n /**\n * Get the underlying transform stream\n */\n get stream(): Trumpet {\n return this.transformStream;\n }\n\n /**\n * Get a promise to the complete output Buffer (alternative API to dealing with stream)\n */\n get outputBufferPromise(): Promise<Buffer> {\n return streamToPromise(this.stream);\n }\n}\n"],"mappings":";;;;AAKA,IAAqB,kBAArB,MAAqC;CACnC,AAAQ;CAER,cAA0C;EACxC,KAAK,kBAAkB,QAAQ;CACjC;;;;CAKA,IAAI,SAAkB;EACpB,OAAO,KAAK;CACd;;;;CAKA,IAAI,sBAAuC;EACzC,OAAO,gBAAgB,KAAK,MAAM;CACpC;AACF"}
package/dist/cdnify.d.ts CHANGED
@@ -1,12 +1,11 @@
1
1
  import { CDNTransformerOptions } from "./CDNTransformer.js";
2
-
3
2
  //#region src/cdnify.d.ts
4
3
  interface CdnifyOptions extends CDNTransformerOptions {
5
4
  buffer: string | Buffer;
6
5
  }
7
6
  /**
8
- * Alternative wrapper for simple Promise-based use of library (as opposed to transform stream)
9
- */
7
+ * Alternative wrapper for simple Promise-based use of library (as opposed to transform stream)
8
+ */
10
9
  declare function cdnify(options: CdnifyOptions): Promise<Buffer>;
11
10
  //#endregion
12
11
  export { CdnifyOptions, cdnify, cdnify as default };
@@ -1 +1 @@
1
- {"version":3,"file":"cdnify.js","names":[],"sources":["../src/cdnify.ts"],"sourcesContent":["import {\n CDNTransformer,\n type CDNTransformerOptions,\n} from \"./CDNTransformer.js\";\n\nimport streamifier from \"streamifier\";\n\nexport interface CdnifyOptions extends CDNTransformerOptions {\n buffer: string | Buffer;\n}\n\n/**\n * Alternative wrapper for simple Promise-based use of library (as opposed to transform stream)\n */\nexport function cdnify(options: CdnifyOptions): Promise<Buffer> {\n if (typeof options.buffer !== \"string\") {\n options.buffer = options.buffer.toString();\n }\n\n const cdnTransformer = new CDNTransformer(options);\n\n streamifier.createReadStream(options.buffer).pipe(cdnTransformer.stream);\n\n return cdnTransformer.outputBufferPromise;\n}\n\nexport default cdnify;\n"],"mappings":";;;;;;;AAcA,SAAgB,OAAO,SAAyC;AAC9D,KAAI,OAAO,QAAQ,WAAW,SAC5B,SAAQ,SAAS,QAAQ,OAAO,UAAU;CAG5C,MAAM,iBAAiB,IAAI,eAAe,QAAQ;AAElD,aAAY,iBAAiB,QAAQ,OAAO,CAAC,KAAK,eAAe,OAAO;AAExE,QAAO,eAAe"}
1
+ {"version":3,"file":"cdnify.js","names":[],"sources":["../src/cdnify.ts"],"sourcesContent":["import {\n CDNTransformer,\n type CDNTransformerOptions,\n} from \"./CDNTransformer.js\";\n\nimport streamifier from \"streamifier\";\n\nexport interface CdnifyOptions extends CDNTransformerOptions {\n buffer: string | Buffer;\n}\n\n/**\n * Alternative wrapper for simple Promise-based use of library (as opposed to transform stream)\n */\nexport function cdnify(options: CdnifyOptions): Promise<Buffer> {\n if (typeof options.buffer !== \"string\") {\n options.buffer = options.buffer.toString();\n }\n\n const cdnTransformer = new CDNTransformer(options);\n\n streamifier.createReadStream(options.buffer).pipe(cdnTransformer.stream);\n\n return cdnTransformer.outputBufferPromise;\n}\n\nexport default cdnify;\n"],"mappings":";;;;;;;AAcA,SAAgB,OAAO,SAAyC;CAC9D,IAAI,OAAO,QAAQ,WAAW,UAC5B,QAAQ,SAAS,QAAQ,OAAO,SAAS;CAG3C,MAAM,iBAAiB,IAAI,eAAe,OAAO;CAEjD,YAAY,iBAAiB,QAAQ,MAAM,CAAC,CAAC,KAAK,eAAe,MAAM;CAEvE,OAAO,eAAe;AACxB"}
@@ -1,7 +1,7 @@
1
1
  //#region src/urlConverter.d.ts
2
2
  /**
3
- * Resolve oldUrl against pathOldUrlIsRelativeTo, and then prepend newUrlBase
4
- */
3
+ * Resolve oldUrl against pathOldUrlIsRelativeTo, and then prepend newUrlBase
4
+ */
5
5
  declare const _default: (newUrlBase: string, oldUrl: string, pathOldUrlIsRelativeTo?: string) => string;
6
6
  //#endregion
7
7
  export { _default as default };
@@ -1,11 +1,33 @@
1
- import url from "node:url";
2
1
  import path from "node:path";
3
2
 
4
3
  //#region src/urlConverter.ts
4
+ /** Origin used to resolve path-only bases, which `new URL()` can't parse. */
5
+ const PLACEHOLDER_ORIGIN = "http://html-cdnify.invalid";
6
+ const SCHEME_PATTERN = /^[a-z][a-z0-9+\-.]*:/i;
5
7
  function isLocalPath(filePath) {
6
8
  return typeof filePath === "string" && filePath.length > 0 && filePath.indexOf("//") === -1 && filePath.indexOf("data:") !== 0;
7
9
  }
8
10
  /**
11
+ * Resolve `relativeUrl` against `base`, the way the (deprecated) legacy
12
+ * `url.resolve` did, but using the WHATWG URL API.
13
+ *
14
+ * `new URL()` only accepts a fully absolute base, so the two other base shapes
15
+ * this package supports are resolved against a stand-in that is stripped back
16
+ * off afterwards:
17
+ *
18
+ * - protocol-relative (`//cdn.com/cdn/`) borrows a protocol
19
+ * - path-only (`someDir/`) borrows a whole origin
20
+ */
21
+ function resolveUrl(base, relativeUrl) {
22
+ if (base.startsWith("//")) return new URL(relativeUrl, `http:${base}`).href.slice(5);
23
+ else if (SCHEME_PATTERN.test(base)) return new URL(relativeUrl, base).href;
24
+ else {
25
+ const absoluteBase = new URL(base, PLACEHOLDER_ORIGIN);
26
+ const resolved = new URL(relativeUrl, absoluteBase).href.slice(26);
27
+ return base.startsWith("/") || relativeUrl.startsWith("/") ? resolved : resolved.slice(1);
28
+ }
29
+ }
30
+ /**
9
31
  * Resolve oldUrl against pathOldUrlIsRelativeTo, and then prepend newUrlBase
10
32
  */
11
33
  var urlConverter_default = (newUrlBase, oldUrl, pathOldUrlIsRelativeTo) => {
@@ -13,13 +35,11 @@ var urlConverter_default = (newUrlBase, oldUrl, pathOldUrlIsRelativeTo) => {
13
35
  else {
14
36
  oldUrl = oldUrl.trim();
15
37
  let relativePath = ".";
16
- if (pathOldUrlIsRelativeTo) {
17
- const fileDirName = pathOldUrlIsRelativeTo.endsWith("/") ? pathOldUrlIsRelativeTo : path.dirname(pathOldUrlIsRelativeTo) + "/";
18
- relativePath = url.resolve(fileDirName, oldUrl);
19
- } else relativePath = oldUrl;
38
+ if (pathOldUrlIsRelativeTo) relativePath = resolveUrl(pathOldUrlIsRelativeTo.endsWith("/") ? pathOldUrlIsRelativeTo : path.dirname(pathOldUrlIsRelativeTo) + "/", oldUrl);
39
+ else relativePath = oldUrl;
20
40
  if (relativePath.startsWith("/")) relativePath = relativePath.slice(1);
21
41
  if (!newUrlBase.endsWith("/")) newUrlBase += "/";
22
- return url.resolve(newUrlBase, relativePath);
42
+ return resolveUrl(newUrlBase, relativePath);
23
43
  }
24
44
  };
25
45
 
@@ -1 +1 @@
1
- {"version":3,"file":"urlConverter.js","names":[],"sources":["../src/urlConverter.ts"],"sourcesContent":["import url from \"node:url\";\nimport path from \"node:path\";\n\nfunction isLocalPath(filePath: string): boolean {\n return (\n typeof filePath === \"string\" &&\n filePath.length > 0 &&\n filePath.indexOf(\"//\") === -1 &&\n filePath.indexOf(\"data:\") !== 0\n );\n}\n\n/**\n * Resolve oldUrl against pathOldUrlIsRelativeTo, and then prepend newUrlBase\n */\nexport default (\n newUrlBase: string,\n oldUrl: string,\n pathOldUrlIsRelativeTo?: string,\n): string => {\n if (!isLocalPath(oldUrl)) {\n return oldUrl;\n } else {\n oldUrl = oldUrl.trim();\n let relativePath = \".\";\n\n if (pathOldUrlIsRelativeTo) {\n const isDirAlready = pathOldUrlIsRelativeTo.endsWith(\"/\");\n const fileDirName = isDirAlready\n ? pathOldUrlIsRelativeTo\n : path.dirname(pathOldUrlIsRelativeTo) + \"/\";\n relativePath = url.resolve(fileDirName, oldUrl);\n } else {\n relativePath = oldUrl;\n }\n\n // When resolving against the CDN URL, we should always use a relative URL.\n //\n // e.g. http://somecdn.com/cdn/ + /article/some.css => http://somecdn.com/cdn/article/some.css\n // and *NOT*\n // http://somecdn.com/cdn/ + /article/some.css => http://somecdn.com/article/some.css\n //\n if (relativePath.startsWith(\"/\")) {\n relativePath = relativePath.slice(1);\n }\n\n if (!newUrlBase.endsWith(\"/\")) {\n newUrlBase += \"/\";\n }\n\n return url.resolve(newUrlBase, relativePath);\n }\n};\n"],"mappings":";;;;AAGA,SAAS,YAAY,UAA2B;AAC9C,QACE,OAAO,aAAa,YACpB,SAAS,SAAS,KAClB,SAAS,QAAQ,KAAK,KAAK,MAC3B,SAAS,QAAQ,QAAQ,KAAK;;;;;AAOlC,4BACE,YACA,QACA,2BACW;AACX,KAAI,CAAC,YAAY,OAAO,CACtB,QAAO;MACF;AACL,WAAS,OAAO,MAAM;EACtB,IAAI,eAAe;AAEnB,MAAI,wBAAwB;GAE1B,MAAM,cADe,uBAAuB,SAAS,IAAI,GAErD,yBACA,KAAK,QAAQ,uBAAuB,GAAG;AAC3C,kBAAe,IAAI,QAAQ,aAAa,OAAO;QAE/C,gBAAe;AASjB,MAAI,aAAa,WAAW,IAAI,CAC9B,gBAAe,aAAa,MAAM,EAAE;AAGtC,MAAI,CAAC,WAAW,SAAS,IAAI,CAC3B,eAAc;AAGhB,SAAO,IAAI,QAAQ,YAAY,aAAa"}
1
+ {"version":3,"file":"urlConverter.js","names":[],"sources":["../src/urlConverter.ts"],"sourcesContent":["import path from \"node:path\";\n\n/** Origin used to resolve path-only bases, which `new URL()` can't parse. */\nconst PLACEHOLDER_ORIGIN = \"http://html-cdnify.invalid\";\n\nconst SCHEME_PATTERN = /^[a-z][a-z0-9+\\-.]*:/i;\n\nfunction isLocalPath(filePath: string): boolean {\n return (\n typeof filePath === \"string\" &&\n filePath.length > 0 &&\n filePath.indexOf(\"//\") === -1 &&\n filePath.indexOf(\"data:\") !== 0\n );\n}\n\n/**\n * Resolve `relativeUrl` against `base`, the way the (deprecated) legacy\n * `url.resolve` did, but using the WHATWG URL API.\n *\n * `new URL()` only accepts a fully absolute base, so the two other base shapes\n * this package supports are resolved against a stand-in that is stripped back\n * off afterwards:\n *\n * - protocol-relative (`//cdn.com/cdn/`) borrows a protocol\n * - path-only (`someDir/`) borrows a whole origin\n */\nfunction resolveUrl(base: string, relativeUrl: string): string {\n if (base.startsWith(\"//\")) {\n return new URL(relativeUrl, `http:${base}`).href.slice(\"http:\".length);\n } else if (SCHEME_PATTERN.test(base)) {\n return new URL(relativeUrl, base).href;\n } else {\n const absoluteBase = new URL(base, PLACEHOLDER_ORIGIN);\n const resolved = new URL(relativeUrl, absoluteBase).href.slice(\n PLACEHOLDER_ORIGIN.length,\n );\n // The placeholder origin makes everything look rooted at `/`. Only keep\n // that slash if it was already there before resolving.\n const isRooted = base.startsWith(\"/\") || relativeUrl.startsWith(\"/\");\n return isRooted ? resolved : resolved.slice(1);\n }\n}\n\n/**\n * Resolve oldUrl against pathOldUrlIsRelativeTo, and then prepend newUrlBase\n */\nexport default (\n newUrlBase: string,\n oldUrl: string,\n pathOldUrlIsRelativeTo?: string,\n): string => {\n if (!isLocalPath(oldUrl)) {\n return oldUrl;\n } else {\n oldUrl = oldUrl.trim();\n let relativePath = \".\";\n\n if (pathOldUrlIsRelativeTo) {\n const isDirAlready = pathOldUrlIsRelativeTo.endsWith(\"/\");\n const fileDirName = isDirAlready\n ? pathOldUrlIsRelativeTo\n : path.dirname(pathOldUrlIsRelativeTo) + \"/\";\n relativePath = resolveUrl(fileDirName, oldUrl);\n } else {\n relativePath = oldUrl;\n }\n\n // When resolving against the CDN URL, we should always use a relative URL.\n //\n // e.g. http://somecdn.com/cdn/ + /article/some.css => http://somecdn.com/cdn/article/some.css\n // and *NOT*\n // http://somecdn.com/cdn/ + /article/some.css => http://somecdn.com/article/some.css\n //\n if (relativePath.startsWith(\"/\")) {\n relativePath = relativePath.slice(1);\n }\n\n if (!newUrlBase.endsWith(\"/\")) {\n newUrlBase += \"/\";\n }\n\n return resolveUrl(newUrlBase, relativePath);\n }\n};\n"],"mappings":";;;;AAGA,MAAM,qBAAqB;AAE3B,MAAM,iBAAiB;AAEvB,SAAS,YAAY,UAA2B;CAC9C,OACE,OAAO,aAAa,YACpB,SAAS,SAAS,KAClB,SAAS,QAAQ,IAAI,MAAM,MAC3B,SAAS,QAAQ,OAAO,MAAM;AAElC;;;;;;;;;;;;AAaA,SAAS,WAAW,MAAc,aAA6B;CAC7D,IAAI,KAAK,WAAW,IAAI,GACtB,OAAO,IAAI,IAAI,aAAa,QAAQ,MAAM,CAAC,CAAC,KAAK,MAAM,CAAc;MAChE,IAAI,eAAe,KAAK,IAAI,GACjC,OAAO,IAAI,IAAI,aAAa,IAAI,CAAC,CAAC;MAC7B;EACL,MAAM,eAAe,IAAI,IAAI,MAAM,kBAAkB;EACrD,MAAM,WAAW,IAAI,IAAI,aAAa,YAAY,CAAC,CAAC,KAAK,MACvD,EACF;EAIA,OADiB,KAAK,WAAW,GAAG,KAAK,YAAY,WAAW,GAAG,IACjD,WAAW,SAAS,MAAM,CAAC;CAC/C;AACF;;;;AAKA,4BACE,YACA,QACA,2BACW;CACX,IAAI,CAAC,YAAY,MAAM,GACrB,OAAO;MACF;EACL,SAAS,OAAO,KAAK;EACrB,IAAI,eAAe;EAEnB,IAAI,wBAKF,eAAe,WAJM,uBAAuB,SAAS,GACtB,IAC3B,yBACA,KAAK,QAAQ,sBAAsB,IAAI,KACJ,MAAM;OAE7C,eAAe;EASjB,IAAI,aAAa,WAAW,GAAG,GAC7B,eAAe,aAAa,MAAM,CAAC;EAGrC,IAAI,CAAC,WAAW,SAAS,GAAG,GAC1B,cAAc;EAGhB,OAAO,WAAW,YAAY,YAAY;CAC5C;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@altano/html-cdnify",
3
- "version": "4.0.2",
3
+ "version": "5.0.0",
4
4
  "description": "Transform the relative URLs in your HTML markup (e.g. scripts, stylesheets, images) to use your CDN URL.",
5
5
  "keywords": [
6
6
  "cdn",
@@ -17,7 +17,7 @@
17
17
  "license": "ISC",
18
18
  "author": {
19
19
  "name": "Alan Norbauer",
20
- "email": "altano@gmail.com",
20
+ "email": "alan@norbauer.com",
21
21
  "url": "https://alan.norbauer.com"
22
22
  },
23
23
  "sideEffects": false,
@@ -32,24 +32,24 @@
32
32
  ],
33
33
  "dependencies": {
34
34
  "@gofunky/trumpet": "^3.0.3",
35
- "lodash": "^4.17.23",
35
+ "lodash": "^4.18.1",
36
36
  "stream-to-promise": "^3.0.0",
37
37
  "streamifier": "^0.1.1"
38
38
  },
39
39
  "devDependencies": {
40
- "@arethetypeswrong/cli": "^0.18.2",
41
- "@types/lodash": "^4.17.23",
42
- "@types/node": "^25.5.0",
40
+ "@arethetypeswrong/cli": "^0.18.5",
41
+ "@types/lodash": "^4.17.24",
42
+ "@types/node": "^26.1.1",
43
43
  "@types/stream-to-promise": "^2.2.5",
44
44
  "@types/streamifier": "^0.1.2",
45
- "@vitest/coverage-v8": "^4.1.0",
45
+ "@vitest/coverage-v8": "^4.1.10",
46
46
  "depcheck": "^1.4.7",
47
- "eslint": "^9.39.4",
48
- "prettier": "^3.8.1",
49
- "tsdown": "^0.21.4",
50
- "typescript": "^5.9.3",
51
- "vite": "^7.3.1",
52
- "vitest": "^4.1.0",
47
+ "eslint": "^9.39.5",
48
+ "prettier": "3.9.6",
49
+ "tsdown": "^0.22.13",
50
+ "typescript": "^6.0.3",
51
+ "vite": "^8.1.5",
52
+ "vitest": "^4.1.10",
53
53
  "@altano/build-config": "0.0.1",
54
54
  "@altano/testing": "0.0.1",
55
55
  "@altano/tsconfig": "0.0.1"
@@ -61,15 +61,15 @@
61
61
  "access": "public"
62
62
  },
63
63
  "scripts": {
64
- "build": "tsdown --config-loader unrun --config ../build-config/tsdown.config.node.ts",
64
+ "build": "tsdown",
65
65
  "check:exports": "attw --pack . --profile esm-only --quiet",
66
66
  "check:types:src": "tsc --noEmit",
67
67
  "check:types:test": "tsc --noEmit --project ./tests/tsconfig.json",
68
68
  "check:unused-dependencies": "depcheck",
69
69
  "clean": "rm -rf .turbo && rm -rf .tsbuildinfo && rm -rf node_modules && rm -rf dist",
70
70
  "dev": "pnpm run build --watch",
71
- "format": "prettier --check src",
72
- "format:fix": "prettier --write src",
71
+ "format": "prettier --check .",
72
+ "format:fix": "prettier --write .",
73
73
  "lint": "eslint",
74
74
  "lint:fix": "TIMING=1 pnpm lint --fix",
75
75
  "lint:timing": "TIMING=1 pnpm lint",