UNPKG

mmir-tooling

Version:
308 lines (254 loc) 12.1 kB
import { ViewBuildOptions, ViewEntry, ImplementationEntry , BuildAppConfig, ImplementationBuildEntry , ResourceConfig , RuntimeConfiguration , DirectoriesInfo , VirtualImplementationEntry , ViewBuildEntry } from '../index.d'; import { WebpackAppConfig } from '../index-webpack.d'; import path from 'path'; import fs from 'fs-extra'; import fileUtils from '../utils/filepath-utils'; import appConfigUtils from '../utils/module-config-init'; import directoriesUtil from '../tools/directories-utils'; import optionUtils from '../tools/option-utils'; import logUtils from '../utils/log-utils'; import { isWebpackConfig } from '../tools/type-utils'; type ViewDirInfo = {dir: string, ctrlName: string, isLayout: boolean}; const log = logUtils.log; const warn = logUtils.warn; var VirtualModulePlugin: any; function initVirtualModulePlugin(){ if(!VirtualModulePlugin){ try{ VirtualModulePlugin = require('webpack-virtual-modules'); } catch(err){ warn('view-utils: failed to initialize webpack-virtual-modules ', err); } } return !!VirtualModulePlugin; } function isPartialView(name: string): boolean { return name.charAt(0) == '~'; }; var regExprFileExt = /\.ehtml$/i; function readDir(dir: string, list: ViewEntry[], options?: ViewBuildOptions): void { const files = fs.readdirSync(dir); const dirs: ViewDirInfo[] = []; // log('read dir "'+dir+'" -> ', files); files.forEach(function(p){ var absPath = path.join(dir, p); if(fileUtils.isDirectory(absPath)){ var name = path.basename(absPath); var isLayout = /layouts/i.test(name); if(!isLayout && name){ name = name[0].toUpperCase() + name.substring(1); } dirs.push({dir: absPath, ctrlName: name, isLayout: isLayout}); } else { warn('view-utils.addFromDirectory(): unknow file in view root path: ', absPath); } }); // log('read sub-dirs -> ', dirs); var size = dirs.length; if(size > 0){ for(var i = 0; i < size; ++i){ readSubDir(dirs[i], list, options); } } } function readSubDir(dirs: ViewDirInfo, list: ViewEntry[], options?: ViewBuildOptions): void { var dir = dirs.dir; var files = fs.readdirSync(dir); // log('read dir "'+dir+'" -> ', files); files.forEach(function(p){ var absPath = path.join(dir, p); if(fileUtils.isDirectory(absPath)){ warn('view-utils.addFromDirectory(): invalid sub-directory in view-directory: ', absPath); } else if(regExprFileExt.test(absPath)) { var normalized = fileUtils.normalizePath(absPath); var fileName = path.basename(normalized).replace(/\.ehtml/i, ''); var isLayout = dirs.isLayout; var isPartial = false; let ctrlName: string, viewName: string; if(isLayout && fileName){ ctrlName = fileName[0].toUpperCase() + fileName.substring(1); viewName = ctrlName; } else { ctrlName = dirs.ctrlName; viewName = fileName; isPartial = isPartialView(viewName); if(isPartial){ viewName = viewName.substring(1); } } var id = dirs.ctrlName.toLowerCase() + '/' + fileName; var opt = options && options[id]; list.push({ id: id, ctrlName: ctrlName, viewName: viewName, file: normalized, viewImpl: isLayout? 'mmirf/layout' : isPartial? 'mmirf/partial' : 'mmirf/view', isLayout: isLayout, isPartial: isPartial, strict: opt && typeof opt.strict === 'boolean'? opt.strict : void(0) }); } else { warn('view-utils.addFromDirectory(): unknown view template file: ', absPath); } }); // log('results for dir "'+dir+'" -> ', ids, views); } function toAliasPath(view: ViewEntry): string { return path.normalize(view.file);//.replace(/\.ehtml$/i, '') } function toAliasId(view: ViewEntry): string { return 'mmirf/view/' + view.id;//FIXME formalize IDs for loading views in webpack (?) } function containsCtrl(ctrlName: string, ctrlList: Array<ImplementationBuildEntry | VirtualImplementationEntry>): boolean { return ctrlList.findIndex(function(c){ return (c as ImplementationEntry).name === ctrlName; }) !== -1; } function addCtrlStub(view: ViewEntry, ctrlList: Array<ImplementationBuildEntry | VirtualImplementationEntry>, ctrlMap: Map<string, ImplementationBuildEntry | VirtualImplementationEntry>): void { if(view.isLayout){ return; } if(containsCtrl(view.ctrlName, ctrlList)){ return; } var ctrl = ctrlMap.get(view.ctrlName); var isDebug = true;//DEBUG TODO make configurable/settable via options if(!ctrl){ ctrl = { moduleName: 'mmirf/controller/'+view.ctrlName[0].toLowerCase()+view.ctrlName.substring(1), contents: 'function '+view.ctrlName+'(){'+(isDebug? 'console.log("created stub controller '+view.ctrlName+'");' : '')+'}; ' + view.ctrlName + '.prototype.on_page_load = function(){'+(isDebug? 'console.log("invoked on_page_load() on stub controller '+view.ctrlName+'");' : '')+'};' + // 'window.'+view.ctrlName+' = '+view.ctrlName+';' + 'module.exports = '+view.ctrlName+';' }; ctrlMap.set(view.ctrlName, ctrl); } } export = { /** * add views from a base-directory that adheres to the structure: * <pre> * <dir>/<controller name 1>/view1.ehtml * /view2.ehtml * ... * <dir>/<controller name 2>/view1.ehtml * /view2.ehtml * ... * ... * <dir>/layouts/default.ehtml * /<controller name 1>.ehtml * ... * </pre> * @param {String} dir the direcotry from which to parse/collect the views * @param {String} appRootDir the root directory of the app (against which relative paths will be resolved) * @param {ViewOptionMap} [options] OPTIONAL * unsupported TODO * @return {Array<ViewEntry>} a list of ViewEntry objects: * { * id: String * ctrlName: String, * viewName: String, * file: String, * viewImpl: 'mmirf/layout' | 'mmirf/partial' | 'mmirf/view', * isLayout: Boolean * isPartial: Boolean * } */ viewTemplatesFromDir: function(dir: string, appRootDir: string, options?: ViewBuildOptions): ViewBuildEntry[] { if(!path.isAbsolute(dir)){ dir = path.resolve(appRootDir, dir); } var list = []; readDir(dir, list, options); return list; }, /** * apply the "global" options from `options` or default values to the entries * from `viewList` if its corresponding options-field is not explicitly specified. * * @param {ViewOptions} options the view options * @param {{Array<ViewEntry>}} viewList * @return {{Array<ViewEntry>}} */ applyDefaultOptions: function(options: ViewBuildOptions, viewList: ViewEntry[]): ViewEntry[] { //TODO impl. if/when addFromOpitions is implemented... viewList.forEach(function(v){ [ // {name: 'ignoreErrors', defaultValue: false}, //TODO impl. if/when addFromOpitions is implemented... // {name: 'force', defaultValue: false}, //TODO impl. if/when addFromOpitions is implemented... {name: 'strict', defaultValue: true} ].forEach(function(fieldInfo){ optionUtils.applySetting(fieldInfo.name, v, options, fieldInfo.defaultValue); }); }); return viewList; }, /** * add views to (webpack) app build configuration * * @param {Array<ViewEntry>} view list of ViewEntry objects: * view.id {String}: the view id * view.file {String}: the path to the eHTML view template (from which the executable view will be created) * @param {Array<ControllerEntry>} ctrls list of ControllerEntry objects: * ctrl.id {String}: the controller id * @param {[type]} appConfig the app configuration to which the views will be added * @param {[type]} directories the directories.json representation * @param {ResourcesConfig} resources the resources configuration * @param {[type]} runtimeConfiguration the configuration.json representation */ addViewsToAppConfig: function(views: ViewEntry[], ctrls: ImplementationBuildEntry[], appConfig: BuildAppConfig | WebpackAppConfig, directories: DirectoriesInfo, resources: ResourceConfig, _runtimeConfiguration: RuntimeConfiguration): void { if(!views || views.length < 1){ return; } var stubCtrlMap = new Map(); views.forEach(function(v){ var aliasId = toAliasId(v); if(isWebpackConfig(appConfig)){ appConfigUtils.addIncludeModule(appConfig, aliasId, toAliasPath(v)); } directoriesUtil.addView(directories, aliasId); if((appConfig as BuildAppConfig).includeViewTemplates){ directoriesUtil.addViewTemplate(directories, aliasId); } addCtrlStub(v, ctrls, stubCtrlMap); }); //FIXME set simpleViewEngine TODO support setting engine via appConfig resources.paths['mmirf/simpleViewEngine'] = 'env/view/simpleViewEngine'; if(!isWebpackConfig(appConfig)){ return; } // include dependencies for loading & rendering views: appConfig.includeModules.push('mmirf/storageUtils', 'mmirf/renderUtils'); appConfig.includeModules.push('mmirf/yield', 'mmirf/layout', 'mmirf/view', 'mmirf/partial');//TODO only include types that were actually parsed if(!appConfig.paths){ appConfig.paths = {}; } // replace default viewLoader with webpack-viewLoader: appConfig.paths['mmirf/viewLoader'] = path.resolve(__dirname, '..', 'runtime', 'webpackViewLoader.js'); //add generated stub controllers if necessary: if(stubCtrlMap.size > 0 && appConfig.controllers !== false){ if(initVirtualModulePlugin()){ if(!appConfig.webpackPlugins){ appConfig.webpackPlugins = []; } var virtualModules = {}; stubCtrlMap.forEach(function(ctrl, name){ var id = ctrl.moduleName; log('view-utils: adding view controller stub "'+name+'": ', id, ' -> ', ctrl);//DEBUG // appConfig.paths[id] = id;// path.resolve('./viewParser/webpackGenCtrl.js'); // appConfig.includeModules.push(id); appConfigUtils.addIncludeModule(appConfig, id, id); directoriesUtil.addCtrl(directories, ctrl.moduleName); virtualModules[ctrl.moduleName] = ctrl.contents; }); appConfig.webpackPlugins.push(new VirtualModulePlugin(virtualModules)); } else { warn('view-utils: cannot add stub controllers, because of misssing package virtual-module-webpack-plugin'); } } }, // getCtrlImpl: function(){ // return controllers.slice(); // } };