UNPKG

electron-view-renderer-spc

Version:
220 lines (190 loc) 7.48 kB
/** * ElectronViewRenderer module */ const {app, protocol} = require('electron') const log = require('captains-log')() const ejs = require('ejs') const path = require('path') const url = require('url') const parseFilePath = (urlString) => { const parsedUrl = url.parse(urlString) let fileName = parsedUrl.pathname if (process.platform === 'win32') fileName = fileName.substr(1) fileName = fileName.replace(/(?:\s|%20)/g, ' ') return fileName } class ElectronViewRenderer { get renderers() { return this._renderers } get currentRenderer() { return this._currentRenderer } get viewPath() { return this._viewPath } get viewProtcolName() { return this._viewProtcolName } get useAssets() { return this._useAssets } get assetsPath() { return this._assetsPath } get assetsProtocolName() { return this._assetsProtocolName } /** * @constructor * @param {Object} [options] - object instance options * @param {string} [options.viewPath = 'views'] - * The path to the view directory where your template files live. * Example: './app/views' or 'views' * @param {string} [options.viewProtcolName = 'view'] - * The name of the protocol used to capture the requested rendering * Example: 'view:///index' (note the extra slash signifying no host) * @param {boolean} [options.useAssets = false] - * This option add an additional listener for 'asset://' protocol * Example 1: 'asset://css/main.css' (note that a host 'css' is added and * will be added in the search path before the remainder of the path * after the path set by options.assetsPath) * Example 1: 'asset:///main.css' (note that a host is not added and * the search path will be the path main.css after the path set * by options.assetsPath) * @param {string} [options.assetsPath = 'assets'] - defines the location * where the assets will be searched for * @param {string} [options.assetsProtocolName = 'asset'] - * The name of the protocol used to capture the requested asset path * and re point it to the path set by options.assetsPath. This is * really usefull when your assets are not in the same directory as your * view files * Example: 'asset://css/main.css' or 'asset://js/index.js' */ constructor({ viewPath = 'views', viewProtcolName = 'view', useAssets = false, assetsPath = 'assets', assetsProtocolName = 'asset', ignoreExtensions = [] } = {}) { this._renderers = {} this._currentRenderer = {} this._views = {} this._viewProtcolName = viewProtcolName this._useAssets = useAssets this._assetsPath = assetsPath this._assetsProtocolName = assetsProtocolName this._viewPath = viewPath this._populateDefaultRenderers() this._ingnoreExtensions = ignoreExtensions; } /** * Allows user to define a template renderer. * * @param {string} name - required, name of renderer. Example: 'ejs' * @param {Object} data - required * @param {string} data.extension - * @callback data.rendererAction - required, used to define how the processed * file should be processed based on the filePath and viewData parameters. * The callback parameter must be called, and expects the rendered HTML * output after parsing. * @param {string} filePath - The path and file name to requested template file * @param {Object} viewData - Additional view data in case it is supported by renderer * @param {function} callback - required callback to be called with the rendered HTML */ add(name, {extension = null, rendererAction}) { if (!name) throw new Error('Renderer name required') const data = { extension: extension, rendererAction: rendererAction, name: name, } this._renderers[name] = data } load(browserWindow, view, viewData, {query = {}} = {}) { this._views[view] = { viewData: viewData } query._view = view return browserWindow.loadURL(url.format({ pathname: view, protocol: 'view:', query: query, slashes: true, })) } renderTemplate(request) { return new Promise((resolve, reject) => { try { const renderer = this.currentRenderer const parsedUrl = url.parse(request.url, true) const fileName = parseFilePath(request.url) const extension = renderer.extension || `.${renderer.name}` const filePath = path.join(this.viewPath, `${fileName}${extension}`) const viewData = (this._views[parsedUrl.query._view] && this._views[parsedUrl.query._view].viewData) ? this._views[parsedUrl.query._view].viewData : undefined; //add the ability to ignore extensions ie: map files which were getting pug added to them. //just send back an empty string to make electron happy. if (this._ingnoreExtensions.length > 0) { let foundIgnore = false; this._ingnoreExtensions.some(function (ext) { if (filePath.substr(filePath.length - ext.length) === ext) { resolve({ mimeType: 'text/html', data: new Buffer(""), }); foundIgnore = true; } }); if (foundIgnore) { return; } } renderer.rendererAction(filePath, viewData, (renderedHTML) => { resolve({ mimeType: 'text/html', data: new Buffer(renderedHTML), }) }); } catch(ex) { reject(ex); } }) } setupViewProtocol() { protocol.registerBufferProtocol(this.viewProtcolName, (request, callback) => { this.renderTemplate(request).then((resolution) => { callback(resolution) }).catch((error) => log.error(error)) }, (error) => { if (error) log.error('Failed to register view protocol') }) } setupAssetsProtocol() { protocol.registerFileProtocol(this.assetsProtocolName, (request, callback) => { const hostName = url.parse(request.url).hostname const fileName = parseFilePath(request.url) const filePath = path.join(this.assetsPath, hostName, fileName) callback({path: filePath}) }, (error) => { if (error) log.error('Failed to register asset protocol') }) } use(name) { this._currentRenderer = this.renderers[name] app.on('ready', () => { this.setupViewProtocol() if (this.useAssets) this.setupAssetsProtocol() }) } _populateEJSRenderer() { this.add('ejs', { extension: '.ejs', viewPath: 'views', rendererAction: (filePath, viewData, callback) => { ejs.renderFile(filePath, viewData, {}, (error, html) => { if (error) { if (error.file) error.message += `\n\nERROR @(${error.file}:${error.line}:${error.column})` throw new Error(error) } callback(html) }) } }) } _populateHAMLRenderer() { // TODO: add HAML Renderer } _populatePugRenderer() { // TODO: add Pug Renderer } _populateDefaultRenderers() { this._populateEJSRenderer() this._populateHAMLRenderer() this._populatePugRenderer() } } module.exports = ElectronViewRenderer