2020-08-09 11:21:29 +00:00
|
|
|
import * as path from "path";
|
2021-03-16 19:02:08 +00:00
|
|
|
import * as SystemFs from "fs-extra";
|
2021-03-16 18:20:57 +00:00
|
|
|
import sha1 from "sha1";
|
2021-03-16 19:02:08 +00:00
|
|
|
import webpack, {Compiler, Module, RuntimeGlobals} from "webpack";
|
2020-08-09 11:21:29 +00:00
|
|
|
|
|
|
|
import {
|
|
|
|
GeneratedSprite,
|
|
|
|
generateSprite,
|
|
|
|
generateSpriteCss, generateSpriteDts, generateSpriteJs,
|
|
|
|
generateSpriteSvg,
|
|
|
|
SpriteCssOptions,
|
|
|
|
SpriteDtsOptions
|
|
|
|
} from "./generator";
|
2021-03-16 19:02:08 +00:00
|
|
|
import Compilation = webpack.Compilation;
|
2020-08-09 11:21:29 +00:00
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
const { RawSource } = require("webpack-sources");
|
|
|
|
|
2020-08-09 11:21:29 +00:00
|
|
|
export interface Options {
|
|
|
|
modulePrefix?: string, /* defaults to svg-sprites/ */
|
|
|
|
dtsOutputFolder: string,
|
|
|
|
configurations: {
|
|
|
|
[key: string] : SvgSpriteConfiguration
|
2021-03-16 19:38:21 +00:00
|
|
|
},
|
|
|
|
publicPath?: string,
|
2020-08-09 11:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface SvgSpriteConfiguration {
|
|
|
|
folder: string;
|
|
|
|
cssClassPrefix: string;
|
|
|
|
|
|
|
|
dtsOptions: SpriteDtsOptions;
|
|
|
|
cssOptions: SpriteCssOptions[];
|
|
|
|
}
|
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
const TYPES = new Set(["javascript"]);
|
|
|
|
const RUNTIME_REQUIREMENTS = new Set([
|
|
|
|
RuntimeGlobals.module
|
|
|
|
]);
|
|
|
|
|
2020-08-09 11:21:29 +00:00
|
|
|
class SvgSpriteModule extends Module {
|
|
|
|
private readonly pluginConfig: Options;
|
|
|
|
private readonly configName: string;
|
|
|
|
private readonly config: SvgSpriteConfiguration;
|
2021-03-16 18:20:57 +00:00
|
|
|
|
2020-08-09 11:21:29 +00:00
|
|
|
private sprite: GeneratedSprite;
|
|
|
|
private spriteSvg: string;
|
|
|
|
private spriteCss: string;
|
|
|
|
private spriteJs: string;
|
2021-03-16 18:20:57 +00:00
|
|
|
|
2020-08-09 11:21:29 +00:00
|
|
|
private spriteAssetName: string;
|
|
|
|
private spriteAssetUrl: string;
|
|
|
|
|
|
|
|
constructor(context: string, pluginConfig: Options, configName: string, config: SvgSpriteConfiguration) {
|
2021-03-16 18:20:57 +00:00
|
|
|
super("javascript/dynamic", null);
|
2021-03-22 18:53:51 +00:00
|
|
|
this.context = context;
|
2020-08-09 11:21:29 +00:00
|
|
|
this.pluginConfig = pluginConfig;
|
|
|
|
this.configName = configName;
|
|
|
|
this.config = config;
|
2021-03-16 18:20:57 +00:00
|
|
|
this.buildInfo = {};
|
|
|
|
this.clearDependenciesAndBlocks();
|
|
|
|
}
|
|
|
|
|
|
|
|
getSourceTypes() {
|
|
|
|
return TYPES;
|
2020-08-09 11:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
identifier() {
|
2021-03-19 00:45:08 +00:00
|
|
|
return this.pluginConfig.modulePrefix + this.configName;
|
2020-08-09 11:21:29 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
readableIdentifier() {
|
2020-08-09 11:21:29 +00:00
|
|
|
return `SVG sprite ` + this.configName;
|
|
|
|
}
|
|
|
|
|
2021-03-19 00:39:24 +00:00
|
|
|
libIdent() {
|
2021-03-19 00:45:08 +00:00
|
|
|
return this.pluginConfig.modulePrefix + this.configName;
|
2021-03-19 00:39:24 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
needBuild(context, callback) {
|
2021-03-16 19:02:08 +00:00
|
|
|
context.fileSystemInfo.getContextHash(this.config.folder, (error, hash) => {
|
|
|
|
if(error) {
|
|
|
|
callback(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const needBuild = this.buildMeta?.directoryHash !== hash;
|
|
|
|
callback(null, needBuild);
|
|
|
|
})
|
2020-08-09 11:21:29 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
build(options, compilation: Compilation, resolver, fs, callback) {
|
|
|
|
this.buildAsync(options, compilation).then(() => {
|
|
|
|
callback();
|
|
|
|
}).catch(error => {
|
|
|
|
callback(error);
|
|
|
|
});
|
|
|
|
}
|
2020-08-09 11:21:29 +00:00
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
private async buildAsync(options_, compilation: Compilation) {
|
2020-08-09 11:21:29 +00:00
|
|
|
this.buildMeta = {
|
|
|
|
async: false,
|
|
|
|
exportsType: undefined
|
|
|
|
};
|
2021-03-16 19:02:08 +00:00
|
|
|
|
2020-08-09 12:01:48 +00:00
|
|
|
this.buildInfo = {
|
|
|
|
cacheable: true,
|
2021-03-16 18:20:57 +00:00
|
|
|
assets: {},
|
2020-08-09 12:01:48 +00:00
|
|
|
};
|
2020-08-09 11:21:29 +00:00
|
|
|
|
|
|
|
if(this.spriteAssetName) {
|
|
|
|
delete compilation.assets[this.spriteAssetName];
|
|
|
|
}
|
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
this.buildMeta.directoryHash = await new Promise((resolve, reject) => {
|
|
|
|
compilation.fileSystemInfo.getContextHash(this.config.folder, (err, hash) => {
|
|
|
|
if(err) {
|
|
|
|
reject(err);
|
|
|
|
} else {
|
|
|
|
resolve(hash);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
compilation.logger.info("Building SVG sprite for configuration %s (Hash: %s).", this.configName, this.buildMeta.directoryHash);
|
|
|
|
|
|
|
|
const files = await SystemFs.readdir(this.config.folder);
|
|
|
|
this.sprite = await generateSprite(files.map(file => path.join(this.config.folder, file)));
|
2020-08-09 11:21:29 +00:00
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
this.spriteSvg = await generateSpriteSvg(this.sprite);
|
|
|
|
this.spriteAssetName = "sprite-" + sha1(this.spriteSvg).substr(-20) + ".svg";
|
2021-03-16 19:38:21 +00:00
|
|
|
this.spriteAssetUrl = (this.pluginConfig.publicPath || "") + this.spriteAssetName;
|
2020-08-09 11:21:29 +00:00
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
this.buildInfo.assets[this.spriteAssetName] = new RawSource(this.spriteSvg);
|
2020-08-09 11:21:29 +00:00
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
this.spriteCss = "";
|
|
|
|
for(const cssOption of this.config.cssOptions) {
|
|
|
|
this.spriteCss += await generateSpriteCss(cssOption, this.config.cssClassPrefix, this.sprite, this.spriteAssetUrl);
|
|
|
|
}
|
2020-08-09 11:21:29 +00:00
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
this.spriteJs = await generateSpriteJs(this.config.dtsOptions, this.sprite, this.spriteAssetUrl, this.config.cssClassPrefix);
|
2020-08-09 11:21:29 +00:00
|
|
|
|
2021-03-16 19:02:08 +00:00
|
|
|
const dtsContent = await generateSpriteDts(this.config.dtsOptions, this.configName, this.sprite, this.config.cssClassPrefix, this.pluginConfig.modulePrefix, this.config.folder);
|
|
|
|
await SystemFs.writeFile(path.join(this.pluginConfig.dtsOutputFolder, this.configName + ".d.ts"), dtsContent);
|
|
|
|
compilation.logger.info("SVG sprite configuration %s contains %d/%d sprites", this.configName, this.sprite.entries.length, files.length);
|
2020-08-09 11:21:29 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
codeGeneration(context) {
|
|
|
|
const sources = new Map();
|
|
|
|
|
2020-08-09 11:21:29 +00:00
|
|
|
const encodedCss = this.spriteCss
|
|
|
|
.replace(/%/g, "%25")
|
|
|
|
.replace(/"/g, "%22")
|
|
|
|
.replace(/\n/g, "%0A");
|
|
|
|
|
|
|
|
let lines = [];
|
|
|
|
lines.push(`/* initialize css */`);
|
|
|
|
lines.push(`var element = document.createElement("style");`);
|
|
|
|
lines.push(`element.innerText = decodeURIComponent("${encodedCss}");`);
|
|
|
|
lines.push(`document.head.append(element);`);
|
|
|
|
lines.push(``);
|
|
|
|
lines.push(`/* initialize typescript objects */`);
|
|
|
|
lines.push(...this.spriteJs.split("\n"));
|
2021-03-16 18:20:57 +00:00
|
|
|
|
|
|
|
sources.set("javascript", new RawSource(lines.join("\n")));
|
|
|
|
return { sources: sources, runtimeRequirements: RUNTIME_REQUIREMENTS };
|
2020-08-09 11:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
size() {
|
|
|
|
return 12;
|
|
|
|
}
|
|
|
|
|
|
|
|
updateHash(hash, chunkGraph) {
|
|
|
|
hash.update("svg-sprite module");
|
|
|
|
hash.update(this.configName || "none");
|
|
|
|
hash.update(this.spriteCss || "none");
|
|
|
|
hash.update(this.spriteSvg || "none");
|
|
|
|
super.updateHash(hash, chunkGraph);
|
|
|
|
}
|
2021-03-16 18:20:57 +00:00
|
|
|
|
|
|
|
addReason(_requestModule, _dependency) { }
|
2021-03-16 19:02:08 +00:00
|
|
|
|
|
|
|
addCacheDependencies(
|
|
|
|
fileDependencies,
|
|
|
|
contextDependencies,
|
|
|
|
missingDependencies,
|
|
|
|
buildDependencies
|
|
|
|
) {
|
|
|
|
contextDependencies.add(this.config.folder);
|
|
|
|
}
|
2020-08-09 11:21:29 +00:00
|
|
|
}
|
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
|
2020-08-09 11:21:29 +00:00
|
|
|
export class SpriteGenerator {
|
|
|
|
readonly options: Options;
|
|
|
|
constructor(options: Options) {
|
|
|
|
this.options = options || {} as any;
|
|
|
|
this.options.configurations = this.options.configurations || {};
|
|
|
|
this.options.modulePrefix = this.options.modulePrefix || "svg-sprites/";
|
|
|
|
}
|
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
apply(compiler: Compiler) {
|
|
|
|
compiler.hooks.normalModuleFactory.tap("SpriteGenerator", normalModuleFactory => {
|
|
|
|
normalModuleFactory.hooks.resolve.tap("SpriteGenerator", resolveData => {
|
|
|
|
if(!resolveData.request.startsWith(this.options.modulePrefix)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-16 20:06:12 +00:00
|
|
|
const configName = resolveData.request.substring(this.options.modulePrefix.length);
|
2021-03-16 18:20:57 +00:00
|
|
|
if(!this.options.configurations[configName]) {
|
2020-08-09 11:21:29 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-03-16 18:20:57 +00:00
|
|
|
return new SvgSpriteModule(resolveData.request, this.options, configName, this.options.configurations[configName]);
|
2020-08-09 11:21:29 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|