From 59af21604b1103b38ce01a93d5270f684de5d890 Mon Sep 17 00:00:00 2001 From: Yurii Zusik Date: Thu, 7 Apr 2022 15:49:32 +0300 Subject: [PATCH] feat(storefront): bctheme-1064 modify stencil bundle for using components ui library --- lib/regions.spec.js | 2 +- lib/stencil-bundle.js | 211 +++++++++++++++++++++++-------------- lib/stencil-bundle.spec.js | 18 ++-- lib/template-assembler.js | 6 ++ 4 files changed, 148 insertions(+), 89 deletions(-) diff --git a/lib/regions.spec.js b/lib/regions.spec.js index 2eee105d..f571fc7b 100644 --- a/lib/regions.spec.js +++ b/lib/regions.spec.js @@ -32,7 +32,7 @@ describe('Stencil Bundle', () => { }); it('should return all regions with the right order.', async () => { - const assembleTemplatesTask = promisify(bundle.assembleTemplatesTask.bind(bundle)); + const assembleTemplatesTask = bundle.assembleTemplatesTask.bind(bundle); const generateManifest = promisify(bundle.generateManifest.bind(bundle)); const templates = await assembleTemplatesTask(); diff --git a/lib/stencil-bundle.js b/lib/stencil-bundle.js index e53029cd..c1417dc9 100644 --- a/lib/stencil-bundle.js +++ b/lib/stencil-bundle.js @@ -84,6 +84,8 @@ class Bundle { } this.tasks = tasks; + + this._getExternalLibs = this._getExternalLibs.bind(this); } /** @@ -151,51 +153,97 @@ class Bundle { }; } - assembleTemplatesTask(callback) { - console.log('Template Parsing Started...'); + /** + * helps to find any node modules dependencies with + * ui templates and returns its list + * + * @private + * @param {string} content + * @returns {Array} + */ + async _getExternalLibs(templatePath) { + const content = await fs.promises.readFile(templatePath, { encoding: 'utf-8' }); + const externalPathRegex = /{{2}>\s*(external)[^{]*?}{2}/g; + const externalTemplatesImports = content.match(externalPathRegex); - recursiveReadDir(this.templatesPath, ['!*.html'], (readdirError, files) => { - if (readdirError) { - return callback(readdirError); - } + if (!externalTemplatesImports) return []; - const partials = files.map((file) => { - return upath.toUnix( - file.replace(this.templatesPath + path.sep, '').replace(/\.html$/, ''), - ); - }); + return externalTemplatesImports.map((templateImport) => templateImport.split('/')[1]); + } - return async.map( - partials, - templateAssembler.assembleAndBundle.bind(null, this.templatesPath), - (err, results) => { - const ret = {}; + async assembleTemplatesTask(callback) { + console.log('Template Parsing Started...'); + const internalTemplatesList = await recursiveReadDir(this.templatesPath, ['!*.html']); + let externalLibs; - if (err) { - return callback(err); - } + try { + const removeDuplicates = (arr) => Array.from(new Set(arr.flat())); + const temp = internalTemplatesList.map(this._getExternalLibs); + const result = await Promise.all(temp); - partials.forEach((file, index) => { - ret[file] = results[index]; - }); + externalLibs = removeDuplicates(result); + } catch (error) { + callback(error); + } - return async.parallel( - [ - this._checkObjects.bind(this, results), - this._detectCycles.bind(this, results), - ], - (checkingError) => { - if (checkingError) { - callback(checkingError); - } - - console.log(`${'ok'.green} -- Template Parsing Finished`); - callback(null, ret); - }, - ); - }, + let externalLibPaths = []; + if (externalLibs.length) { + externalLibPaths = externalLibs.map((lib) => + recursiveReadDir(path.join(this.themePath, 'node_modules', lib, 'templates'), [ + '!*.html', + ]), + ); + } + // eslint-disable-next-line node/no-unsupported-features/es-builtins + const res = await Promise.allSettled([ + recursiveReadDir(this.templatesPath, ['!*.html']), + ...externalLibPaths, + ]); + const [{ value: internalTemplates }, ...externalTemplatesList] = res; + const internalPartials = internalTemplates.map((file) => { + return upath.toUnix( + file.replace(this.templatesPath + path.sep, '').replace(/\.html$/, ''), ); }); + const externalPartials = externalTemplatesList.reduce( + (partials, { value: externalTemplates }) => { + const extractedPartials = externalTemplates.map((file) => { + return upath.toUnix( + 'external' + file.split('node_modules')[1].replace(/\.html$/, ''), + ); + }); + partials.push(...extractedPartials); + + return partials; + }, + [], + ); + const allPartials = [...externalPartials, ...internalPartials]; + + let results; + try { + results = await async.map( + allPartials, + templateAssembler.assembleAndBundle.bind(null, this.templatesPath), + ); + + const ret = {}; + + allPartials.forEach((file, index) => { + ret[file] = results[index]; + }); + + await Promise.all([ + this._checkObjects.bind(this, results), + this._detectCycles.bind(this, results), + ]); + + console.log(`${'ok'.green} -- Template Parsing Finished`); + + return ret; + } catch (err) { + return callback(err); + } } async assembleSchema() { @@ -317,56 +365,59 @@ class Bundle { typeof this.options.dest === 'string' ? this.options.dest : this.themePath; const bundleZipPath = path.join(outputFolder, outputName); - async.parallel(this.tasks, (err, taskResults) => { - if (err) { - return callback(err); - } + this.tasks.templates = this.assembleTemplatesTask.bind(this, callback); - const archive = Archiver('zip'); - const fileStream = fs.createWriteStream(bundleZipPath); - archive.pipe(fileStream); - - // Create manifest will use taskResults to generate a manifest file - return this.generateManifest(taskResults, (manifestGenerationError, manifest) => { - if (manifestGenerationError) { - return callback(manifestGenerationError); - } - - // eslint-disable-next-line no-param-reassign - taskResults.manifest = manifest; - // zip theme files - this._bundleThemeFiles(archive, this.themePath); - - // zip all generated files - const failedTemplates = this._bundleParsedFiles(archive, taskResults); - - fileStream.on('close', () => { - const stats = fs.statSync(bundleZipPath); - const { size } = stats; - - if (failedTemplates.length) { - return console.error( - `Error: Your bundle failed as templates generated from the files below are greater than or equal to 1 megabyte in size:\n${failedTemplates.join( - '\n', - )}`, - ); - } + async + .parallel(this.tasks) + .then((taskResults) => { + const archive = Archiver('zip'); + const fileStream = fs.createWriteStream(bundleZipPath); + archive.pipe(fileStream); - if (size > MAX_SIZE_BUNDLE) { - return console.error( - `Error: Your bundle of size ${size} bytes is above the max size of ${MAX_SIZE_BUNDLE} bytes`, - ); + // Create manifest will use taskResults to generate a manifest file + return this.generateManifest(taskResults, (manifestGenerationError, manifest) => { + if (manifestGenerationError) { + return callback(manifestGenerationError); } - console.log(`${'ok'.green} -- Zipping Files Finished`); + // eslint-disable-next-line no-param-reassign + taskResults.manifest = manifest; + // zip theme files + this._bundleThemeFiles(archive, this.themePath); - return callback(null, bundleZipPath); - }); + // zip all generated files + const failedTemplates = this._bundleParsedFiles(archive, taskResults); + + fileStream.on('close', () => { + const stats = fs.statSync(bundleZipPath); + const { size } = stats; + + if (failedTemplates.length) { + return console.error( + `Error: Your bundle failed as templates generated from the files below are greater than or equal to 1 megabyte in size:\n${failedTemplates.join( + '\n', + )}`, + ); + } - // This triggers 'close' event in the file stream. No need to callback() - return archive.finalize(); + if (size > MAX_SIZE_BUNDLE) { + return console.error( + `Error: Your bundle of size ${size} bytes is above the max size of ${MAX_SIZE_BUNDLE} bytes`, + ); + } + + console.log(`${'ok'.green} -- Zipping Files Finished`); + + return callback(null, bundleZipPath); + }); + + // This triggers 'close' event in the file stream. No need to callback() + return archive.finalize(); + }); + }) + .catch((err) => { + console.log(err); }); - }); } /** diff --git a/lib/stencil-bundle.spec.js b/lib/stencil-bundle.spec.js index 29db8ec3..c56bfa17 100644 --- a/lib/stencil-bundle.spec.js +++ b/lib/stencil-bundle.spec.js @@ -70,7 +70,7 @@ describe('Stencil Bundle', () => { }); it('should assembleTemplates', async () => { - const result = await promisify(bundle.assembleTemplatesTask.bind(bundle))(); + const result = await bundle.assembleTemplatesTask.call(bundle); expect(result['pages/page']).toEqual( expect.objectContaining({ @@ -87,13 +87,15 @@ describe('Stencil Bundle', () => { }); it('should error when running assembleTemplates', async () => { - jest.spyOn(async, 'map').mockImplementation((coll, iteratee, cb) => - cb(new Error('our_error2')), - ); + const errorMessage = 'our_error2'; + const cb = (err) => { + if (err) throw err; + }; + jest.spyOn(async, 'map').mockImplementation(() => { + throw new Error(errorMessage); + }); - await expect(promisify(bundle.assembleTemplatesTask.bind(bundle))()).rejects.toThrow( - 'our_error2', - ); + await expect(bundle.assembleTemplatesTask.call(bundle, cb)).rejects.toThrow(errorMessage); }); it('should assemble the Schema', async () => { @@ -123,7 +125,7 @@ describe('Stencil Bundle', () => { }); it('should generate a manifest of files.', async () => { - const templates = await promisify(bundle.assembleTemplatesTask.bind(bundle))(); + const templates = await bundle.assembleTemplatesTask.call(bundle); const manifest = await promisify(bundle.generateManifest.bind(bundle))({ templates }); expect(manifest.templates).toEqual( diff --git a/lib/template-assembler.js b/lib/template-assembler.js index c5712c2e..626c96ec 100644 --- a/lib/template-assembler.js +++ b/lib/template-assembler.js @@ -9,6 +9,12 @@ const dynamicComponentRegex = /\{\{\s*?dynamicComponent\s*(?:'|")([_|\-|a-zA-Z0- const includeRegex = /{{2}>\s*([_|\-|a-zA-Z0-9/]+)[^{]*?}{2}/g; const packageMarker = 'external/'; +/** + * indicates if a template source is a node modules dependency + * + * @param {string} templateName + * @returns {Boolean} + */ const isExternalTemplate = (templateName) => { return templateName.startsWith(packageMarker); };