From 09d313c2d3089f216b22f23184081d331e00f202 Mon Sep 17 00:00:00 2001 From: MomoCow Date: Thu, 9 Feb 2023 00:26:15 +0800 Subject: [PATCH] :sparkles: add ability to generate proxy script. --- lib/plugin.ts | 79 ++++++++++++++++++++++++++++++++---- lib/reducers/base-urls.ts | 7 ++-- lib/reducers/interpolate.ts | 7 +--- lib/reducers/proxy-script.ts | 42 +++++++++++++++++++ lib/types.ts | 6 +++ lib/util.ts | 5 +++ 6 files changed, 130 insertions(+), 16 deletions(-) create mode 100644 lib/reducers/proxy-script.ts create mode 100644 lib/util.ts diff --git a/lib/plugin.ts b/lib/plugin.ts index 8552b96..8adc241 100644 --- a/lib/plugin.ts +++ b/lib/plugin.ts @@ -14,18 +14,23 @@ import { resolveUpdateBaseUrl, setDefaultMatch, } from './reducers'; +import { processProxyScript } from './reducers/proxy-script'; import { FileInfo, HeadersWaterfall, SSRILock, UserscriptOptions, } from './types'; +import { interpolate } from './util'; const { ConcatSource, RawSource } = sources; export class UserscriptPlugin { public readonly hooks = { processHeaders: new AsyncSeriesWaterfallHook(['headers']), + processProxyHeaders: new AsyncSeriesWaterfallHook([ + 'headers', + ]), }; public constructor( @@ -73,7 +78,8 @@ export class UserscriptPlugin { } protected applyHooks(): void { - const { downloadBaseUrl, updateBaseUrl, ssri, headers } = this.options; + const { downloadBaseUrl, updateBaseUrl, ssri, headers, proxyScript } = + this.options; if (typeof headers === 'function') { this.hooks.processHeaders.tapPromise( @@ -114,6 +120,13 @@ export class UserscriptPlugin { interpolateValues.name, wrapHook(interpolateValues), ); + + if (proxyScript) { + this.hooks.processProxyHeaders.tap( + processProxyScript.name, + wrapHook(processProxyScript), + ); + } } protected headersFactory(props: HeadersProps): Readonly { @@ -288,7 +301,8 @@ export class UserscriptPlugin { data: CompilerData, fileInfo: FileInfo, ): Promise { - const { prefix, pretty, suffix, whitelist, strict } = this.options; + const { prefix, pretty, suffix, whitelist, strict, proxyScript, metajs } = + this.options; const { headers: headersProps, ssriLock } = await this.hooks.processHeaders.promise({ @@ -306,12 +320,52 @@ export class UserscriptPlugin { headers.validate({ whitelist: whitelist ?? true }); } + let proxyScriptFile: string | undefined; + let proxyHeaders: Readonly | undefined; + + if (proxyScript) { + const { headers: proxyHeadersProps } = + await this.hooks.processProxyHeaders.promise({ + headers: headersProps, + ssriLock, + fileInfo, + compilation, + buildNo: data.buildNo, + options: this.options, + }); + + proxyHeaders = this.headersFactory(proxyHeadersProps); + + if (strict) { + proxyHeaders.validate({ whitelist: whitelist ?? true }); + } + + if (proxyScript === true || proxyScript.filename === undefined) { + proxyScriptFile = '[basename].proxy.user.js'; + } else { + proxyScriptFile = interpolate(proxyScript.filename, { + chunkName: fileInfo.chunk.name, + file: fileInfo.originalFile, + filename: fileInfo.filename, + basename: fileInfo.basename, + query: fileInfo.query, + buildNo: data.buildNo.toString(), + buildTime: Date.now().toString(), + }); + } + } + const { originalFile, chunk, metajsFile, userjsFile } = fileInfo; const headersStr = headers.render({ prefix, pretty, suffix, }); + const proxyHeadersStr = proxyHeaders?.render({ + prefix, + pretty, + suffix, + }); const sourceAsset = compilation.getAsset(originalFile); if (!sourceAsset) { return; @@ -321,14 +375,25 @@ export class UserscriptPlugin { userjsFile, new ConcatSource(headersStr, '\n', sourceAsset.source), { - related: { metajs: metajsFile }, minimized: true, }, ); - compilation.emitAsset(metajsFile, new RawSource(headersStr), { - related: { userjs: userjsFile }, - minimized: true, - }); + + if (metajs !== false) { + compilation.emitAsset( + metajsFile, + new RawSource(proxyHeadersStr ?? headersStr), + { + minimized: true, + }, + ); + } + + if (proxyHeadersStr !== undefined && proxyScriptFile !== undefined) { + compilation.emitAsset(proxyScriptFile, new RawSource(proxyHeadersStr), { + minimized: true, + }); + } chunk.files.add(userjsFile); chunk.auxiliaryFiles.add(metajsFile); diff --git a/lib/reducers/base-urls.ts b/lib/reducers/base-urls.ts index 6d74e3c..4219219 100644 --- a/lib/reducers/base-urls.ts +++ b/lib/reducers/base-urls.ts @@ -27,9 +27,10 @@ export const resolveUpdateBaseUrl: HeadersReducer = ({ if (headers.updateURL === undefined) { return { ...headers, - updateURL: !options.metajs - ? headers.downloadURL - : new URL(fileInfo.metajsFile, options.updateBaseUrl).toString(), + updateURL: + options.metajs === false + ? headers.downloadURL + : new URL(fileInfo.metajsFile, options.updateBaseUrl).toString(), }; } return headers; diff --git a/lib/reducers/interpolate.ts b/lib/reducers/interpolate.ts index 0040d3e..e6aa5a8 100644 --- a/lib/reducers/interpolate.ts +++ b/lib/reducers/interpolate.ts @@ -1,4 +1,5 @@ import { HeadersReducer } from '../types'; +import { interpolate } from '../util'; export const interpolateValues: HeadersReducer = ({ fileInfo: { chunk, originalFile, filename, basename, query }, @@ -51,9 +52,3 @@ export const interpolateValues: HeadersReducer = ({ return headers; }; - -export function interpolate(str: string, data: Record): string { - return Object.entries(data).reduce((value, [dataKey, dataVal]) => { - return value.replace(new RegExp(`\\[${dataKey}\\]`, 'g'), `${dataVal}`); - }, str); -} diff --git a/lib/reducers/proxy-script.ts b/lib/reducers/proxy-script.ts new file mode 100644 index 0000000..8e30c93 --- /dev/null +++ b/lib/reducers/proxy-script.ts @@ -0,0 +1,42 @@ +import { URL } from 'url'; + +import { HeadersReducer } from '../types'; +import { interpolate } from '../util'; + +export const processProxyScript: HeadersReducer = ({ + headers, + fileInfo: { chunk, originalFile, filename, basename, query, userjsFile }, + buildNo, + options: { proxyScript }, +}) => { + if (proxyScript) { + const devBaseUrl = + proxyScript === true || proxyScript.baseUrl === undefined + ? 'http://localhost:8080/' + : interpolate(proxyScript.baseUrl, { + chunkName: chunk.name, + file: originalFile, + filename, + basename, + query, + buildNo: buildNo.toString(), + buildTime: Date.now().toString(), + }); + + const requireTags = Array.isArray(headers.require) + ? headers.require + : typeof headers.require === 'string' + ? [headers.require] + : []; + + headers = { + ...headers, + require: [...requireTags, new URL(userjsFile, devBaseUrl).toString()], + downloadURL: undefined, + updateURL: undefined, + installURL: undefined, + }; + } + + return headers; +}; diff --git a/lib/types.ts b/lib/types.ts index 1610c0d..9549682 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -16,6 +16,7 @@ export interface UserscriptOptions { downloadBaseUrl?: string; updateBaseUrl?: string; ssri?: true | SSRIOptions; + proxyScript?: true | ProxyScriptOptions; } export type HeadersProvider = HeadersReducer | AsyncHeadersReducer; @@ -41,6 +42,11 @@ export interface SSRIOptions { lock?: boolean | string; } +export interface ProxyScriptOptions { + filename?: string; + baseUrl?: string; +} + export interface FileInfo { chunk: Chunk; originalFile: string; diff --git a/lib/util.ts b/lib/util.ts new file mode 100644 index 0000000..e1d82da --- /dev/null +++ b/lib/util.ts @@ -0,0 +1,5 @@ +export function interpolate(str: string, data: Record): string { + return Object.entries(data).reduce((value, [dataKey, dataVal]) => { + return value.replace(new RegExp(`\\[${dataKey}\\]`, 'g'), `${dataVal}`); + }, str); +}