UNPKG

@aire-ux/aire-code-panel

Version:
265 lines (247 loc) 10.5 kB
/* * Copyright 2000-2022 Vaadin Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ /** * This file contains functions for look up and handle the theme resources * for application theme plugin. */ const fs = require('fs'); const path = require('path'); const generateThemeFile = require('./theme-generator'); const { copyStaticAssets, copyThemeResources } = require('./theme-copy'); // matches theme name in './theme-my-theme.generated.js' const nameRegex = /theme-(.*)\.generated\.js/; let prevThemeName = undefined; let firstThemeName = undefined; /** * Looks up for a theme resources in a current project and in jar dependencies, * copies the found resources and generates/updates meta data for webpack * compilation. * * @param {object} options application theme plugin mandatory options, * @see {@link ApplicationThemePlugin} * * @param logger application theme plugin logger */ function processThemeResources(options, logger) { const themeName = extractThemeName(options.frontendGeneratedFolder); if (themeName) { if (!prevThemeName && !firstThemeName) { firstThemeName = themeName; } else if ( (prevThemeName && prevThemeName !== themeName && firstThemeName !== themeName) || (!prevThemeName && firstThemeName !== themeName) ) { // Warning message is shown to the developer when: // 1. He is switching to any theme, which is differ from one being set up // on application startup, by changing theme name in `@Theme()` // 2. He removes or comments out `@Theme()` to see how the app // looks like without theming, and then again brings `@Theme()` back // with a themeName which is differ from one being set up on application // startup. const warning = `Attention: Active theme is switched to '${themeName}'.`; const description = ` Note that adding new style sheet files to '/themes/${themeName}/components', may not be taken into effect until the next application restart. Changes to already existing style sheet files are being reloaded as before.`; logger.warn('*******************************************************************'); logger.warn(warning); logger.warn(description); logger.warn('*******************************************************************'); } prevThemeName = themeName; findThemeFolderAndHandleTheme(themeName, options, logger); } else { // This is needed in the situation that the user decides to comment or // remove the @Theme(...) completely to see how the application looks // without any theme. Then when the user brings back one of the themes, // the previous theme should be undefined to enable us to detect the change. prevThemeName = undefined; logger.debug('Skipping Vaadin application theme handling.'); logger.trace('Most likely no @Theme annotation for application or only themeClass used.'); } } /** * Search for the given theme in the project and resource folders. * * @param {string} themeName name of theme to find * @param {object} options application theme plugin mandatory options, * @see {@link ApplicationThemePlugin} * @param logger application theme plugin logger * @return true or false for if theme was found */ function findThemeFolderAndHandleTheme(themeName, options, logger) { let themeFound = false; for (let i = 0; i < options.themeProjectFolders.length; i++) { const themeProjectFolder = options.themeProjectFolders[i]; if (fs.existsSync(themeProjectFolder)) { logger.log("Searching themes folder '" + themeProjectFolder + "' for theme '" + themeName + "'"); const handled = handleThemes(themeName, themeProjectFolder, options, logger); if (handled) { if (themeFound) { throw new Error( "Found theme files in '" + themeProjectFolder + "' and '" + themeFound + "'. Theme should only be available in one folder" ); } logger.log("Found theme files from '" + themeProjectFolder + "'"); themeFound = themeProjectFolder; } } } if (fs.existsSync(options.themeResourceFolder)) { if (themeFound && fs.existsSync(path.resolve(options.themeResourceFolder, themeName))) { throw new Error( "Theme '" + themeName + "'should not exist inside a jar and in the project at the same time\n" + 'Extending another theme is possible by adding { "parent": "my-parent-theme" } entry to the theme.json file inside your theme folder.' ); } logger.debug( "Searching theme jar resource folder '" + options.themeResourceFolder + "' for theme '" + themeName + "'" ); handleThemes(themeName, options.themeResourceFolder, options, logger); themeFound = true; } return themeFound; } /** * Copies static resources for theme and generates/writes the * [theme-name].generated.js for webpack to handle. * * Note! If a parent theme is defined it will also be handled here so that the parent theme generated file is * generated in advance of the theme generated file. * * @param {string} themeName name of theme to handle * @param {string} themesFolder folder containing application theme folders * @param {object} options application theme plugin mandatory options, * @see {@link ApplicationThemePlugin} * @param {object} logger plugin logger instance * * @throws Error if parent theme defined, but can't locate parent theme * * @returns true if theme was found else false. */ function handleThemes(themeName, themesFolder, options, logger) { const themeFolder = path.resolve(themesFolder, themeName); if (fs.existsSync(themeFolder)) { logger.debug('Found theme ', themeName, ' in folder ', themeFolder); const themeProperties = getThemeProperties(themeFolder); // If theme has parent handle parent theme immediately. if (themeProperties.parent) { const found = findThemeFolderAndHandleTheme(themeProperties.parent, options, logger); if (!found) { throw new Error( "Could not locate files for defined parent theme '" + themeProperties.parent + "'.\n" + 'Please verify that dependency is added or theme folder exists.' ); } } copyStaticAssets(themeName, themeProperties, options.projectStaticAssetsOutputFolder, logger); copyThemeResources(themeFolder, options.projectStaticAssetsOutputFolder, logger); const themeFile = generateThemeFile(themeFolder, themeName, themeProperties, !options.devMode); fs.writeFileSync(path.resolve(options.frontendGeneratedFolder, 'theme-' + themeName + '.generated.js'), themeFile); return true; } return false; } function getThemeProperties(themeFolder) { const themePropertyFile = path.resolve(themeFolder, 'theme.json'); if (!fs.existsSync(themePropertyFile)) { return {}; } const themePropertyFileAsString = fs.readFileSync(themePropertyFile); if (themePropertyFileAsString.length === 0) { return {}; } return JSON.parse(themePropertyFileAsString); } /** * Extracts current theme name from auto-generated 'theme.js' file located on a * given folder. * @param frontendGeneratedFolder folder in project containing 'theme.js' file * @returns {string} current theme name */ function extractThemeName(frontendGeneratedFolder) { if (!frontendGeneratedFolder) { throw new Error( "Couldn't extract theme name from 'theme.js'," + ' because the path to folder containing this file is empty. Please set' + ' the a correct folder path in ApplicationThemePlugin constructor' + ' parameters.' ); } const generatedThemeFile = path.resolve(frontendGeneratedFolder, 'theme.js'); if (fs.existsSync(generatedThemeFile)) { // read theme name from the 'generated/theme.js' as there we always // mark the used theme for webpack to handle. const themeName = nameRegex.exec(fs.readFileSync(generatedThemeFile, { encoding: 'utf8' }))[1]; if (!themeName) { throw new Error("Couldn't parse theme name from '" + generatedThemeFile + "'."); } return themeName; } else { return ''; } } /** * Finds all the parent themes located in the project themes folders and in * the JAR dependencies with respect to the given custom theme with * {@code themeName}. * @param {string} themeName given custom theme name to look parents for * @param {object} options application theme plugin mandatory options, * @see {@link ApplicationThemePlugin} * @returns {string[]} array of paths to found parent themes with respect to the * given custom theme */ function findParentThemes(themeName, options) { const existingThemeFolders = [options.themeResourceFolder, ...options.themeProjectFolders].filter((folder) => fs.existsSync(folder) ); return collectParentThemes(themeName, existingThemeFolders, false); } function collectParentThemes(themeName, themeFolders, isParent) { let foundParentThemes = []; themeFolders.forEach((folder) => { const themeFolder = path.resolve(folder, themeName); if (fs.existsSync(themeFolder)) { const themeProperties = getThemeProperties(themeFolder); if (themeProperties.parent) { foundParentThemes.push(...collectParentThemes(themeProperties.parent, themeFolders, true)); if (!foundParentThemes.length) { throw new Error( "Could not locate files for defined parent theme '" + themeProperties.parent + "'.\n" + 'Please verify that dependency is added or theme folder exists.' ); } } // Add a theme path to result collection only if a given themeName // is supposed to be a parent theme if (isParent) { foundParentThemes.push(themeFolder); } } }); return foundParentThemes; } module.exports = { processThemeResources, extractThemeName, findParentThemes };