diff --git a/.eslintrc b/.eslintrc index c146e40..e37954c 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,7 +28,7 @@ "quotes": ["error", "single"], "semi": ["error", "always"], "complexity": ["error", { "max": 3 }], - "max-lines": ["error", { "max": 115 }], + "max-lines": ["error", { "max": 150 }], "max-statements": ["error", { "max": 5 }, { "ignoreTopLevelFunctions": true } ] @@ -37,7 +37,7 @@ { "files": [ "src/**/*.test.js" ], "rules": { - "max-lines": ["error", { "max": 250 }], + "max-lines": ["error", { "max": 275 }], "max-statements": ["error", { "max": 15 }, { "ignoreTopLevelFunctions": true } ] diff --git a/package-lock.json b/package-lock.json index f1bb9fa..fe5a384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@glorious/pitsby", - "version": "1.31.0", + "version": "1.32.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@glorious/pitsby", - "version": "1.31.0", + "version": "1.32.0", "license": "MIT", "dependencies": { "@babel/core": "^7.18.9", diff --git a/package.json b/package.json index 33fc909..5c0f6c4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@glorious/pitsby", - "version": "1.31.0", + "version": "1.32.0", "description": "Docs generator for AngularJS, React, Vue and Vanilla components", "author": "Rafael Camargo ", "repository": { diff --git a/src/cli/services/webapp-html-index-generator.js b/src/cli/services/webapp-html-index-generator.js index d3d56a7..03246a1 100644 --- a/src/cli/services/webapp-html-index-generator.js +++ b/src/cli/services/webapp-html-index-generator.js @@ -9,8 +9,8 @@ const _public = {}; _public.init = (config = {}) => { return new Promise((resolve, reject) => { - const linkTags = buildAssetTags(config.styles, buildLinkTag); - const scriptTags = buildAssetTags(config.scripts, buildScriptTag); + const linkTags = buildAssetTags(config.styles, 'stylesheet'); + const scriptTags = buildAssetTags(config.scripts, 'script'); const indexHtml = webappHtmlIndexCustomisation.init(buildIndexHtml( config, linkTags, @@ -26,29 +26,55 @@ _public.init = (config = {}) => { }); }; -function buildAssetTags(paths = [], tagBuilderAction){ +function buildAssetTags(items = [], type){ const tags = []; - paths.forEach(path => { - tags.push(tagBuilderAction(path)); + items.forEach(item => { + tags.push(buildAssetTag(item, type)); }); return tags; } -function buildLinkTag(path){ - return ``; +function buildAssetTag(item, type){ + const attrs = stringifyAssetTagAttrs(buildAssetTagAttrs(item, type)); + return shouldUseScriptTag(item, type) ? `` : ``; } -function buildScriptTag(path){ - return ``; +function shouldUseScriptTag(item, type){ + return type == 'script' && !isPreloadedAsset(item); +} + +function buildAssetTagAttrs(item, type){ + const attrs = typeof item == 'string' ? { [buildAssetPathAttrName(item, type)]: item } : item; + return shouldAppendStylesheetRelAttr(attrs, type) ? { ...attrs, rel: 'stylesheet' } : attrs; +} + +function buildAssetPathAttrName(item, type){ + return type == 'script' && !isPreloadedAsset(item) ? 'src' : 'href'; +} + +function isPreloadedAsset(item){ + return ['preload', 'prefetch'].includes(item.rel); +} + +function shouldAppendStylesheetRelAttr(attrs, type){ + return !attrs.rel && type == 'stylesheet'; +} + +function stringifyAssetTagAttrs(attrs){ + return Object.entries(attrs).map(([key, value]) => { + return value ? `${key}="${formatExternalAssetAttrValue(key, value)}"` : key; + }).join(' '); +} + +function formatExternalAssetAttrValue(attrName, attrValue){ + return ['href', 'src'].includes(attrName) ? formatExternalAssetPath(attrValue) : attrValue; } function handleExternalScriptTags(scriptTags, projects){ const vueConfig = getProjectEngineConfig(projects, 'vue'); const reactConfig = getProjectEngineConfig(projects, 'react'); - if(vueConfig) - scriptTags.unshift(cdnService.buildVueScriptTag(vueConfig.version)); - if(reactConfig) - scriptTags.unshift(cdnService.buildReactScriptTag(reactConfig.version)); + if(vueConfig) scriptTags.unshift(cdnService.buildVueScriptTag(vueConfig.version)); + if(reactConfig) scriptTags.unshift(cdnService.buildReactScriptTag(reactConfig.version)); return scriptTags; } @@ -56,10 +82,9 @@ function getProjectEngineConfig(projects = [], engine){ return projects.find(project => project.engine == engine); } -function buildExternalAssetPath(path){ - return assetsFilepathFilter.isRelativePath(path) ? - `/external/${parseRelativePath(path)}` : - path; +function formatExternalAssetPath(rawPath){ + const path = assetsFilepathFilter.isRelativePath(rawPath) ? `/external/${parseRelativePath(rawPath)}` : rawPath; + return `${path}?t=${Date.now()}`; } function parseRelativePath(path){ diff --git a/src/cli/services/webapp-html-index-generator.test.js b/src/cli/services/webapp-html-index-generator.test.js index 085e95a..f2ea1c8 100644 --- a/src/cli/services/webapp-html-index-generator.test.js +++ b/src/cli/services/webapp-html-index-generator.test.js @@ -98,6 +98,39 @@ describe('Webapp HTML Index Generator', () => { }); }); + it('should optionally accept tag attributes for external assets', done => { + const config = buildPitsbyConfigMock({ projects: [{ engine: 'angular' }] }); + const styles = [ + { rel: 'preload', href: './style.css', as: 'style' } + ]; + const scripts = [ + { crossorigin: '', src: './script.js' }, + { crossorigin: 'use-credentials', src: 'https://some.lib.com/from/cdn.min.js' }, + { rel: 'prefetch', href: './prefetch-script.js', as: 'script' }, + { rel: 'preload', href: './preload-script.js', as: 'script' }, + { type: 'module', src: './es6-script.js' } + ]; + webappHtmlIndexGenerator.init({ ...config, styles, scripts }).then(() => { + const expectedHTMLTags = [ + '', + '', + '', + '', + '', + '' + ]; + expectedHTMLTags.forEach(htmlTag => { + expect(fileService.write).toHaveBeenCalledWith( + buildHtmlIndexFilename(), + expect.stringContaining(htmlTag), + expect.any(Function), + expect.any(Function) + ); + }); + done(); + }); + }); + it('should not include any external assets if no assets have been given', done => { webappHtmlIndexGenerator.init().then(() => { expect(fileService.write).toHaveBeenCalledWith(