webos-webpack-plugin
Version:
Webpack plugin to make WebOS Basic App packaging easier
290 lines (238 loc) • 8.08 kB
text/typescript
import { validate } from "schema-utils";
import { Compiler } from "webpack";
import {
AppInfo,
AppInfoAssets,
OptionalAppInfoAssets,
Options,
Defaults,
} from "./types";
import { Packager } from "./packager";
import { assert } from "./helpers";
import path = require("node:path");
import fs = require("node:fs");
import { schema } from "./types/schema";
type Webpack = Compiler["webpack"];
type Compilation = InstanceType<Webpack["Compilation"]>;
type WebpackLogger = ReturnType<Compiler["getInfrastructureLogger"]>;
class WebOSWebpackPlugin {
private _compiler: Compiler | null = null;
private _compilation: Compilation | null = null;
private _assets: AppInfoAssets | null = null;
private _optionalAssets: OptionalAppInfoAssets | null = null;
private options: Required<Options>;
static name = "WebOSWebpackPlugin";
static defaultOptions: Defaults<Options> = {
outputFile: "appinfo.json",
preserveSrc: false,
};
constructor(options: Options) {
this.options = {
outputFile:
options.outputFile ||
WebOSWebpackPlugin.defaultOptions.outputFile,
preserveSrc:
options.preserveSrc ||
WebOSWebpackPlugin.defaultOptions.preserveSrc,
appInfo: options.appInfo,
};
this.assets = {
icon: options.appInfo.icon,
};
this.optionalAssets = {
splashBackground: options.appInfo.splashBackground,
largeIcon: options.appInfo.largeIcon,
};
validate(schema as Record<string, unknown>, this.options, {
name: "WebOS Plugin",
});
}
apply(compiler: Compiler) {
const pluginName = WebOSWebpackPlugin.name;
const { webpack } = compiler;
const { Compilation } = webpack;
this.compiler = compiler;
this.compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
this.compilation = compilation;
this.compilation.hooks.processAssets.tapAsync(
{
name: pluginName,
stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
},
(assets, done) => {
this.generateAppInfoSync();
this.copyAssets();
done();
}
);
});
this.compiler.hooks.afterEmit.tapAsync(pluginName, async (_, done) => {
await this.packApp();
if (!this.options.preserveSrc) {
this.removeAssetsFromDist();
}
done();
});
}
private removeAssetsFromDist() {
this.compilation.getAssets().forEach((asset) => {
const fullPathToAsset = this.dist + "/" + asset.name;
fs.unlink(fullPathToAsset, (err) => {
if (!err) {
return;
}
this.reportWebpackError(err.message);
});
});
}
private generateAppInfoSync() {
this.logger.info("Generating appinfo.json");
this.compilation.emitAsset(
this.options.outputFile,
new this.compiler.webpack.sources.RawSource(
Buffer.from(
JSON.stringify(this.prepareAppInfoToEmit(), null, "\t")
)
)
);
}
private prepareAppInfoToEmit(): AppInfo {
return {
...this.options.appInfo,
...this.getAppInfoForAssets(),
};
}
private getAppInfoForAssets(): AppInfoAssets {
const filenamesForRequiredAssets: AppInfoAssets = {
icon: this.getFilenameFromPath(this.options.appInfo.icon),
};
return {
...filenamesForRequiredAssets,
...this.filenamesForOptionalAssets(),
};
}
private filenamesForOptionalAssets(): OptionalAppInfoAssets {
return this.optionalAssetsKeys().reduce<OptionalAppInfoAssets>(
(acc, nextKey) => {
const value = this.options.appInfo[nextKey];
if (!value) {
return acc;
}
acc[nextKey] = this.getFilenameFromPath(value);
return acc;
},
{}
);
}
private getFilenameFromPath(pathToFile: string) {
return path.basename(pathToFile);
}
private async packApp() {
this.logger.info("Packing webOS application");
try {
const packager = new Packager();
await packager.pack(this.dist, this.dist);
this.logger.info("webOS application packed successfully!");
} catch (error) {
this.reportWebpackError((error as Error).message);
}
}
private copyAssets() {
this.copyRequiredAssets();
this.copyOptionalAssets();
}
private copyRequiredAssets() {
this.copyAndPasteToDist(this.assets.icon);
}
private copyOptionalAssets() {
this.optionalAssetsKeys().forEach((key) => {
const asset = this.optionalAssets[key];
if (asset) {
this.copyAndPasteToDist(asset);
}
});
}
private copyAndPasteToDist(pathToFile: string) {
try {
const file = this.copy(pathToFile);
this.compilation.emitAsset(
this.getFilenameFromPath(pathToFile),
new this.compiler.webpack.sources.RawSource(file)
);
} catch (err) {
this.reportWebpackError((err as Error).message);
}
}
private copy(pathToFile: string): Buffer {
const fileBuffer = fs.readFileSync(pathToFile);
if (!fileBuffer) {
throw new Error(`Failed to read from ${path}`);
}
return fileBuffer;
}
private optionalAssetsKeys() {
return Object.keys(
this.optionalAssets
) as (keyof OptionalAppInfoAssets)[];
}
private get optionalAssets(): OptionalAppInfoAssets {
assert(
this._optionalAssets,
() =>
new Error(
"Investigate: optional assets can be empty, but must not be nullish"
)
);
return this._optionalAssets;
}
private set optionalAssets(value: OptionalAppInfoAssets) {
this._optionalAssets = value;
}
private get assets(): AppInfoAssets {
assert(
this._assets,
() =>
new Error(
"Investigate: there is no assets somehow, but they are required"
)
);
return this._assets;
}
private set assets(value: AppInfoAssets) {
this._assets = value;
}
private get dist(): string {
const distPath = this.compilation.options.output.path;
assert(distPath, () => new Error("Investigate: no dist"));
return distPath;
}
private get logger(): WebpackLogger {
return this.compiler.getInfrastructureLogger(WebOSWebpackPlugin.name);
}
private get compiler(): Compiler {
assert(this._compiler, () =>
this.reportWebpackError("There is no compiler initialized")
);
return this._compiler;
}
private get compilation(): Compilation {
assert(this._compilation, () =>
this.reportWebpackError("There is no compilation initialized")
);
return this._compilation;
}
private set compiler(value: Compiler) {
this._compiler = value;
}
private set compilation(value: Compilation) {
this._compilation = value;
}
private reportWebpackError(msg: string) {
const messageToLog = `WebOSWebpackPlugin: ${msg}`;
this.logger.error(messageToLog);
this.compilation.errors.push(
new this.compiler.webpack.WebpackError(messageToLog)
);
}
}
module.exports = { WebOSWebpackPlugin };