UNPKG

webos-webpack-plugin

Version:

Webpack plugin to make WebOS Basic App packaging easier

290 lines (238 loc) 8.08 kB
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 };