diff --git a/lib/stencil-bundle.js b/lib/stencil-bundle.js index e53029cd..e5dfe0fa 100644 --- a/lib/stencil-bundle.js +++ b/lib/stencil-bundle.js @@ -49,6 +49,7 @@ class Bundle { this.options = options; this.templatesPath = path.join(themePath, 'templates'); + this.externalTemplatesPath = path.join(themePath, 'node_modules/bc-ui-lib/templates'); this.themePath = themePath; this.themeConfig = themeConfig; this.configuration = rawConfig; @@ -154,19 +155,35 @@ class Bundle { assembleTemplatesTask(callback) { console.log('Template Parsing Started...'); - recursiveReadDir(this.templatesPath, ['!*.html'], (readdirError, files) => { - if (readdirError) { - return callback(readdirError); - } - - const partials = files.map((file) => { + // eslint-disable-next-line node/no-unsupported-features/es-builtins + Promise.allSettled([ + recursiveReadDir(this.externalTemplatesPath, ['!*.html']), + recursiveReadDir(this.templatesPath, ['!*.html']), + ]).then((res) => { + const [{ value: externalTemplates }, { value: internalTemplates }] = res; + const externalPartials = externalTemplates.map((file) => { + const startposition = file.indexOf('node_modules'); + const endPosition = file.lastIndexOf('templates') + 'templates/'.length; + const partialPrefix = file + .slice(startposition, endPosition) + .replace('node_modules', 'external'); + return upath.toUnix( + partialPrefix + + file + .replace(this.externalTemplatesPath + path.sep, '') + .replace(/\.html$/, ''), + ); + }); + const internalPartials = internalTemplates.map((file) => { return upath.toUnix( file.replace(this.templatesPath + path.sep, '').replace(/\.html$/, ''), ); }); + const allPartials = [...externalPartials, ...internalPartials]; + return async.map( - partials, + allPartials, templateAssembler.assembleAndBundle.bind(null, this.templatesPath), (err, results) => { const ret = {}; @@ -175,7 +192,7 @@ class Bundle { return callback(err); } - partials.forEach((file, index) => { + allPartials.forEach((file, index) => { ret[file] = results[index]; }); diff --git a/lib/template-assembler.js b/lib/template-assembler.js index b2932f44..c5712c2e 100644 --- a/lib/template-assembler.js +++ b/lib/template-assembler.js @@ -6,6 +6,44 @@ const upath = require('upath'); const partialRegex = /\{\{>\s*([_|\-|a-zA-Z0-9/]+)[^{]*?}}/g; const dynamicComponentRegex = /\{\{\s*?dynamicComponent\s*(?:'|")([_|\-|a-zA-Z0-9/]+)(?:'|").*?}}/g; +const includeRegex = /{{2}>\s*([_|\-|a-zA-Z0-9/]+)[^{]*?}{2}/g; +const packageMarker = 'external/'; + +const isExternalTemplate = (templateName) => { + return templateName.startsWith(packageMarker); +}; +const defineBaseRoute = (route) => route.split('/').slice(0, 3).join('/'); +const applyExternalPath = (templateName, templates) => { + return templates.map((t) => `${defineBaseRoute(templateName)}/${t}`); +}; +const replacePartialNames = (content, partialRoute) => { + return content.replace( + includeRegex, + (__, partialName) => `{{> ${defineBaseRoute(partialRoute)}/${partialName} }}`, + ); +}; + +/** + * Takes a templates folder and template name. It returns simple path for custom template if it's available + * or use default folder instead + * + * @param {string} templatesFolder + * @param {string} templateName + */ +function getCustomPath(templatesFolder, templateName) { + let customTemplatesDir; + let customTemplateName; + + if (templateName.startsWith(packageMarker)) { + customTemplatesDir = templatesFolder.replace('templates', 'node_modules'); + customTemplateName = templateName.slice(packageMarker.length); + } else { + customTemplatesDir = templatesFolder; + customTemplateName = templateName; + } + + return path.join(customTemplatesDir, `${customTemplateName}.html`); +} /** * Takes a list of templates and grabs their content. It returns simple key/val pair @@ -20,14 +58,17 @@ function getContent(templatesFolder, templatePaths, callback) { templatePaths, {}, (acc, templatePath, reduceCallback) => { - const file = path.join(templatesFolder, `${templatePath}.html`); + const file = getCustomPath(templatesFolder, templatePath); + fs.readFile(file, { encoding: 'utf-8' }, (err, content) => { if (err) { reduceCallback(err); return; } - acc[templatePath] = content; + acc[templatePath] = templatePath.startsWith(packageMarker) + ? replacePartialNames(content, templatePath) + : content; reduceCallback(null, acc); }); @@ -81,7 +122,7 @@ function getTemplatePaths(templatesFolder, templates, options, callback) { }); function resolvePartials(templateName, cb2) { - const templatePath = path.join(templatesFolder, `${templateName}.html`); + const templatePath = getCustomPath(templatesFolder, templateName); fs.readFile(templatePath, { encoding: 'utf-8' }, (err, content) => { const componentPaths = []; @@ -147,6 +188,10 @@ function getTemplatePaths(templatesFolder, templates, options, callback) { return; } + if (isExternalTemplate(templateName)) { + matches = applyExternalPath(templateName, matches); + } + async.each(matches, resolvePartials, cb2); }, );