webpack-userscript
Version:
A Webpack plugin for userscript projects.
220 lines • 9.79 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.UserscriptPlugin = void 0;
const tslib_1 = require("tslib");
const node_path_1 = tslib_1.__importDefault(require("node:path"));
const tapable_1 = require("tapable");
const webpack_1 = require("webpack");
const const_1 = require("./const");
const features_1 = require("./features");
const utils_1 = require("./utils");
const { ConcatSource, RawSource } = webpack_1.sources;
class UserscriptPlugin {
constructor(options = {}) {
this.name = 'UserscriptPlugin';
this.hooks = {
init: new tapable_1.AsyncParallelHook(['compiler']),
close: new tapable_1.AsyncParallelHook(['compiler']),
preprocess: new tapable_1.AsyncParallelHook([
'compilation',
'context',
]),
process: new tapable_1.AsyncParallelHook([
'compilation',
'context',
]),
headers: new tapable_1.AsyncSeriesWaterfallHook([
'headersProps',
'context',
]),
proxyHeaders: new tapable_1.AsyncSeriesWaterfallHook(['headersProps', 'context']),
proxyScriptFile: new tapable_1.AsyncSeriesWaterfallHook([
'proxyScriptFile',
'context',
]),
renderHeaders: new tapable_1.AsyncSeriesBailHook([
'headersProps',
]),
renderProxyHeaders: new tapable_1.AsyncSeriesBailHook([
'headersProps',
]),
};
this.contexts = new WeakMap();
this.options = {};
const { metajs = true, strict = true } = options;
Object.assign(options, { metajs, strict });
this.features = [
new features_1.LoadHeaders(options),
new features_1.FixTags(options),
new features_1.ResolveBaseURLs(options),
new features_1.ProcessSSRI(options),
new features_1.SetDefaultTags(options),
new features_1.ProcessProxyScript(options),
new features_1.Interpolater(options),
new features_1.ValidateHeaders(options),
new features_1.RenderHeaders(options),
];
this.options = options;
}
apply(compiler) {
const name = this.name;
let buildNo = 0;
const initPromise = new Promise((resolve) => queueMicrotask(() => resolve(this.init(compiler))));
compiler.hooks.beforeCompile.tapPromise(name, () => initPromise);
compiler.hooks.compilation.tap(name, (compilation) => {
this.contexts.set(compilation, {
buildNo: ++buildNo,
buildTime: (0, utils_1.date)(),
fileInfo: [],
});
compilation.hooks.processAssets.tapPromise({
name,
stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS,
}, () => this.preprocess(compilation));
compilation.hooks.processAssets.tapPromise({
name,
// we should generate userscript files
// only if optimization of source files are complete
stage: webpack_1.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
}, () => this.process(compilation));
});
compiler.hooks.done.tapPromise(name, () => this.close(compiler));
for (const feature of this.features) {
feature.apply(this);
}
}
init(compiler) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
yield this.hooks.init.promise(compiler);
});
}
close(compiler) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
yield this.hooks.close.promise(compiler);
});
}
preprocess(compilation) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const context = this.contexts.get(compilation);
/* istanbul ignore next */
if (!context) {
return;
}
context.fileInfo = this.collectFileInfo(compilation);
yield this.hooks.preprocess.promise(compilation, context);
});
}
process(compilation) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const context = this.contexts.get(compilation);
/* istanbul ignore next */
if (!context) {
return;
}
yield Promise.all(context.fileInfo.map((fileInfo) => this.emitUserscript(compilation, context, fileInfo)));
for (const { originalFile, userjsFile } of context.fileInfo) {
if (originalFile !== userjsFile) {
compilation.deleteAsset(originalFile);
}
}
yield this.hooks.process.promise(compilation, context);
});
}
collectFileInfo(compilation) {
var _a, _b, _c;
const fileInfo = [];
for (const entrypoint of compilation.entrypoints.values()) {
const chunk = entrypoint.getEntrypointChunk();
for (const originalFile of chunk.files) {
let q = originalFile.indexOf('?');
if (q < 0) {
q = originalFile.length;
}
const filepath = originalFile.slice(0, q);
const query = originalFile.slice(q);
const dirname = node_path_1.default.dirname(filepath);
const filename = node_path_1.default.basename(filepath);
const basename = filepath.endsWith('.user.js')
? node_path_1.default.basename(filepath, '.user.js')
: filepath.endsWith('.js')
? node_path_1.default.basename(filepath, '.js')
: filepath;
const extname = node_path_1.default.extname(filepath);
const userjsFile = node_path_1.default.join(dirname, basename + '.user.js') + query;
const metajsFile = node_path_1.default.join(dirname, basename + '.meta.js');
const fileInfoEntry = {
chunk,
originalFile,
userjsFile,
metajsFile,
filename,
dirname,
basename,
query,
extname,
};
if ((_c = (_b = (_a = this.options).skip) === null || _b === void 0 ? void 0 : _b.call(_a, fileInfoEntry)) !== null && _c !== void 0 ? _c : extname !== '.js') {
continue;
}
fileInfo.push(fileInfoEntry);
}
}
return fileInfo;
}
emitUserscript(compilation, context, fileInfo) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const { metajs, proxyScript, i18n } = this.options;
const { originalFile, chunk, metajsFile, userjsFile } = fileInfo;
const sourceAsset = compilation.getAsset(originalFile);
const waterfall = Object.assign(Object.assign({}, context), { fileInfo,
compilation });
if (!sourceAsset) {
/* istanbul ignore next */
return;
}
const localizedHeaders = new Map();
const headers = yield this.hooks.headers.promise({}, Object.assign(Object.assign({}, waterfall), { locale: const_1.DEFAULT_LOCALE_KEY }));
localizedHeaders.set(const_1.DEFAULT_LOCALE_KEY, headers);
if (i18n) {
yield Promise.all(Object.keys(i18n).map((locale) => tslib_1.__awaiter(this, void 0, void 0, function* () {
localizedHeaders.set(locale, yield this.hooks.headers.promise({}, Object.assign(Object.assign({}, waterfall), { locale })));
})));
}
const headersStr = yield this.hooks.renderHeaders.promise(localizedHeaders);
const proxyHeaders = proxyScript
? yield this.hooks.proxyHeaders.promise(headers, Object.assign(Object.assign({}, waterfall), { locale: const_1.DEFAULT_LOCALE_KEY }))
: undefined;
const proxyScriptFile = proxyScript
? yield this.hooks.proxyScriptFile.promise('', Object.assign(Object.assign({}, waterfall), { locale: const_1.DEFAULT_LOCALE_KEY }))
: undefined;
const proxyHeadersStr = proxyHeaders
? yield this.hooks.renderProxyHeaders.promise(proxyHeaders)
: undefined;
if (userjsFile !== originalFile) {
compilation.emitAsset(userjsFile, new ConcatSource(headersStr, '\n', sourceAsset.source), {
minimized: true,
});
chunk.files.add(userjsFile);
}
else {
compilation.updateAsset(userjsFile, new ConcatSource(headersStr, '\n', sourceAsset.source), {
minimized: true,
});
}
if (metajs !== false) {
compilation.emitAsset(metajsFile, new RawSource(headersStr), {
minimized: true,
});
chunk.auxiliaryFiles.add(metajsFile);
}
if (proxyScriptFile !== undefined && proxyHeadersStr !== undefined) {
compilation.emitAsset(proxyScriptFile, new RawSource(proxyHeadersStr), {
minimized: true,
});
chunk.auxiliaryFiles.add(proxyScriptFile);
}
});
}
}
exports.UserscriptPlugin = UserscriptPlugin;
//# sourceMappingURL=plugin.js.map
;