160 lines
5.5 KiB
TypeScript
160 lines
5.5 KiB
TypeScript
import * as webpack from "webpack";
|
|
import * as path from "path";
|
|
import * as fs from "fs-extra";
|
|
import * as sha1 from "sha1";
|
|
|
|
import {
|
|
GeneratedSprite,
|
|
generateSprite,
|
|
generateSpriteCss, generateSpriteDts, generateSpriteJs,
|
|
generateSpriteSvg,
|
|
SpriteCssOptions,
|
|
SpriteDtsOptions
|
|
} from "./generator";
|
|
|
|
export interface Options {
|
|
modulePrefix?: string, /* defaults to svg-sprites/ */
|
|
dtsOutputFolder: string,
|
|
configurations: {
|
|
[key: string] : SvgSpriteConfiguration
|
|
}
|
|
}
|
|
|
|
const Module = require("webpack/lib/Module");
|
|
const { RawSource } = require("webpack-sources");
|
|
|
|
interface SvgSpriteConfiguration {
|
|
folder: string;
|
|
cssClassPrefix: string;
|
|
|
|
dtsOptions: SpriteDtsOptions;
|
|
cssOptions: SpriteCssOptions[];
|
|
}
|
|
|
|
class SvgSpriteModule extends Module {
|
|
private readonly pluginConfig: Options;
|
|
private readonly configName: string;
|
|
private readonly config: SvgSpriteConfiguration;
|
|
private sprite: GeneratedSprite;
|
|
private spriteSvg: string;
|
|
private spriteCss: string;
|
|
private spriteJs: string;
|
|
private spriteAssetName: string;
|
|
private spriteAssetUrl: string;
|
|
|
|
constructor(context: string, pluginConfig: Options, configName: string, config: SvgSpriteConfiguration) {
|
|
super("javascript/dynamic", context);
|
|
this.pluginConfig = pluginConfig;
|
|
this.configName = configName;
|
|
this.config = config;
|
|
}
|
|
|
|
identifier() {
|
|
return `SVG sprite ` + this.configName;
|
|
}
|
|
|
|
readableIdentifier(requestShortener) {
|
|
return `SVG sprite ` + this.configName;
|
|
}
|
|
|
|
needRebuild() {
|
|
return false;
|
|
}
|
|
|
|
build(options, compilation, resolver, fs_, callback) {
|
|
console.info("Building SVG sprite for configuration %s", this.configName);
|
|
|
|
this.built = true;
|
|
this.buildMeta = {
|
|
async: false,
|
|
exportsType: undefined
|
|
};
|
|
this.buildInfo = {};
|
|
|
|
if(this.spriteAssetName) {
|
|
delete compilation.assets[this.spriteAssetName];
|
|
}
|
|
|
|
(async () => {
|
|
const files = (await fs.readdir(this.config.folder)).map(e => path.join(this.config.folder, e));
|
|
this.sprite = await generateSprite(files);
|
|
|
|
this.spriteSvg = await generateSpriteSvg(this.sprite);
|
|
this.spriteAssetName = "sprite-" + sha1(this.spriteSvg).substr(-20) + ".svg";
|
|
this.spriteAssetUrl = (compilation.options.output.publicPath || "") + this.spriteAssetName;
|
|
|
|
compilation.assets[this.spriteAssetName] = new RawSource(this.spriteSvg);
|
|
|
|
this.spriteCss = "";
|
|
for(const cssOption of this.config.cssOptions) {
|
|
this.spriteCss += await generateSpriteCss(cssOption, this.config.cssClassPrefix, this.sprite, this.spriteAssetUrl);
|
|
}
|
|
|
|
this.spriteJs = await generateSpriteJs(this.config.dtsOptions, this.sprite, this.spriteAssetUrl, this.config.cssClassPrefix);
|
|
|
|
const dtsContent = await generateSpriteDts(this.config.dtsOptions, this.configName, this.sprite, this.config.cssClassPrefix, this.pluginConfig.modulePrefix);
|
|
await fs.writeFile(path.join(this.pluginConfig.dtsOutputFolder, this.configName + ".d.ts"), dtsContent);
|
|
})().then(() => {
|
|
callback();
|
|
}).catch(error => {
|
|
console.error(error);
|
|
callback("failed to generate sprite");
|
|
});
|
|
}
|
|
|
|
source() {
|
|
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"));
|
|
return new RawSource(lines.join("\n"));
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
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/";
|
|
}
|
|
|
|
apply(compiler: webpack.Compiler) {
|
|
compiler.hooks.thisCompilation.tap("SpriteGenerator", (compilation, { normalModuleFactory }) => {
|
|
normalModuleFactory.hooks.factory.tap("SpriteGenerator", factory => (data, callback) => {
|
|
if(data.request.startsWith(this.options.modulePrefix)) {
|
|
const configName = data.request.substr(this.options.modulePrefix.length);
|
|
if(this.options.configurations[configName] === undefined) {
|
|
callback("Missing SVG configuration " + configName);
|
|
return;
|
|
}
|
|
callback(null, new SvgSpriteModule(data.request, this.options, configName, this.options.configurations[configName]));
|
|
return;
|
|
}
|
|
|
|
factory(data, callback);
|
|
});
|
|
});
|
|
}
|
|
} |