function get_processor(tag_name, search, replace) {
	/** @type {Preprocessor} */
	const preprocessor = ({ content, filename = '' }) => {
		let code = content.slice();
		const ms = new MagicString(code);

		const idx = ms.original.indexOf(search);
		if (idx == -1) throw new Error('search not found in src');
		ms.overwrite(idx, idx + search.length, replace, { storeName: true });

		// change line + column
		const indent = Array.from({ length: indent_size }).join(' ');
		ms.prependLeft(idx, '\n' + indent);

		const map_opts = { source: path.basename(filename), hires: true, includeContent: false };
		const map = ms.generateMap(map_opts);
		const attach_line =
			tag_name == 'style' || comment_multi
				? `\n/*# sourceMappingURL=${map.toUrl()} */`
				: `\n//# sourceMappingURL=${map.toUrl()}`; // only in script
		code = ms.toString() + attach_line;

		indent_size += 2;
		if (tag_name == 'script') comment_multi = !comment_multi;
		return { code };
	};

	return {
		[tag_name]: preprocessor
	};
}
@param {'script' | 'style'} tag_name
@param {string} search
@param {string} replace
@returns {PreprocessorGroup}