Skip to content

Commit

Permalink
fix: make Vite work with custom frontend directory (#14152) (CP: 23.1) (
Browse files Browse the repository at this point in the history
#14222)

Fixes Vite configuration to work with a custom frontend directory.
Also rewrite theme css to resolve urls for assets defined in theme.json

Fixes #14046
Fixes #14102
  • Loading branch information
vaadin-bot committed Jul 26, 2022
1 parent 47c7287 commit f827649
Show file tree
Hide file tree
Showing 11 changed files with 593 additions and 80 deletions.
Expand Up @@ -174,7 +174,7 @@ function generateThemeFile(themeFolder, themeName, themeProperties, productionMo
}
themeProperties.documentCss.forEach((cssImport) => {
const variable = 'module' + i++;
imports.push(`import ${variable} from '${cssImport}';\n`);
imports.push(`import ${variable} from '${cssImport}?inline';\n`);
// Due to chrome bug https://bugs.chromium.org/p/chromium/issues/detail?id=336876 font-face will not work
// inside shadowRoot so we need to inject it there also.
globalCssCode.push(`if(target !== document) {
Expand Down
Expand Up @@ -14,6 +14,7 @@
"url": "https://github.com/vaadin/flow/issues"
},
"files": [
"theme-loader.js"
"theme-loader.js",
"theme-loader-utils.js"
]
}
@@ -0,0 +1,83 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');

// Collect groups [url(] ['|"]optional './|../', file part and end of url
const urlMatcher = /(url\(\s*)(\'|\")?(\.\/|\.\.\/)(\S*)(\2\s*\))/g;


function assetsContains(fileUrl, themeFolder, logger) {
const themeProperties = getThemeProperties(themeFolder);
if (!themeProperties) {
logger.debug('No theme properties found.');
return false;
}
const assets = themeProperties['assets'];
if (!assets) {
logger.debug('No defined assets in theme properties');
return false;
}
// Go through each asset module
for (let module of Object.keys(assets)) {
const copyRules = assets[module];
logger.log('asset ' + module);
// Go through each copy rule
for (let copyRule of Object.keys(copyRules)) {
logger.log('rule ' + copyRules[copyRule] + " ---> file " + fileUrl);
// if file starts with copyRule target check if file with path after copy target can be found
if (fileUrl.startsWith(copyRules[copyRule])) {
const targetFile = fileUrl.replace(copyRules[copyRule], '');
const files = glob.sync(path.resolve('node_modules/', module, copyRule), { nodir: true });

logger.log('targetFile ' + targetFile);
for (let file of files) {
if (file.endsWith(targetFile)) 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);
}


function rewriteCssUrls(source, handledResourceFolder, themeFolder, logger, options) {
source = source.replace(urlMatcher, function (match, url, quoteMark, replace, fileUrl, endString) {
let absolutePath = path.resolve(handledResourceFolder, replace, fileUrl);
const existingThemeResource = absolutePath.startsWith(themeFolder) && fs.existsSync(absolutePath);
if (
existingThemeResource || assetsContains(fileUrl, themeFolder, logger)
) {
// Adding ./ will skip css-loader, which should be done for asset files
const skipLoader = existingThemeResource ? '' : './';
const frontendThemeFolder = skipLoader + 'themes/' + path.basename(themeFolder);
logger.debug(
'Updating url for file',
"'" + replace + fileUrl + "'",
'to use',
"'" + frontendThemeFolder + '/' + fileUrl + "'"
);
const pathResolved = absolutePath.substring(themeFolder.length).replace(/\\/g, '/');

// keep the url the same except replace the ./ or ../ to themes/[themeFolder]
return url + (quoteMark??'') + frontendThemeFolder + pathResolved + endString;
} else if (options.devMode) {
logger.log("No rewrite for '", match, "' as the file was not found.");
}
return match;
});
return source;
}

module.exports = { rewriteCssUrls };
@@ -1,9 +1,7 @@
const loaderUtils = require('loader-utils');
const fs = require('fs');
const path = require('path');

// Collect groups [url(] [ |'|"]optional './|../', file part and end of url
const urlMatcher = /(url\()(\'|\")?(\.\/|\.\.\/)(\S*)(\2\))/g;
const { rewriteCssUrls } = require('./theme-loader-utils');

/**
* This custom loader handles rewriting urls for the application theme css files.
Expand Down Expand Up @@ -31,75 +29,6 @@ module.exports = function (source, map) {

logger.log("Using '", themeFolder, "' for the application theme base folder.");

source = source.replace(urlMatcher, function (match, url, quoteMark, replace, fileUrl, endString) {
let absolutePath = path.resolve(handledResourceFolder, replace, fileUrl);
if (
(fs.existsSync(absolutePath) && absolutePath.startsWith(themeFolder)) ||
assetsContains(fileUrl, themeFolder, logger)
) {
// Adding ./ will skip css-loader, which should be done for asset files
const skipLoader = fs.existsSync(absolutePath) && absolutePath.startsWith(themeFolder) ? '' : './';
const frontendThemeFolder = skipLoader + 'themes/' + path.basename(themeFolder);
logger.debug(
'Updating url for file',
"'" + replace + fileUrl + "'",
'to use',
"'" + frontendThemeFolder + '/' + fileUrl + "'"
);
const pathResolved = absolutePath.substring(themeFolder.length).replace(/\\/g, '/');

// keep the url the same except replace the ./ or ../ to themes/[themeFolder]
if (quoteMark) {
return url + quoteMark + frontendThemeFolder + pathResolved + endString;
}
return url + frontendThemeFolder + pathResolved + endString;
} else if (options.devMode) {
logger.log("No rewrite for '", match, "' as the file was not found.");
}
return match;
});

source = rewriteCssUrls(source, handledResourceFolder, themeFolder, logger, options);
this.callback(null, source, map);
};

function assetsContains(fileUrl, themeFolder, logger) {
const themeProperties = getThemeProperties(themeFolder);
if (!themeProperties) {
logger.debug('No theme properties found.');
return false;
}
const assets = themeProperties['assets'];
if (!assets) {
logger.debug('No defined assets in theme properties');
return false;
}
// Go through each asset module
for (let module of Object.keys(assets)) {
const copyRules = assets[module];
// Go through each copy rule
for (let copyRule of Object.keys(copyRules)) {
// if file starts with copyRule target check if file with path after copy target can be found
if (fileUrl.startsWith(copyRules[copyRule])) {
const targetFile = fileUrl.replace(copyRules[copyRule], '');
const files = require('glob').sync(path.resolve('node_modules/', module, copyRule), { nodir: true });

for (let file of files) {
if (file.endsWith(targetFile)) 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);
}
18 changes: 14 additions & 4 deletions flow-server/src/main/resources/vite.generated.ts
Expand Up @@ -9,6 +9,7 @@ import { readFileSync, existsSync, writeFileSync } from 'fs';
import * as net from 'net';

import { processThemeResources } from '#buildFolder#/plugins/application-theme-plugin/theme-handle';
import { rewriteCssUrls } from '#buildFolder#/plugins/theme-loader/theme-loader-utils';
import settings from '#settingsImport#';
import { defineConfig, mergeConfig, PluginOption, ResolvedConfig, UserConfigFn, OutputOptions, AssetInfo, ChunkInfo } from 'vite';
import { injectManifest } from 'workbox-build';
Expand Down Expand Up @@ -305,7 +306,7 @@ export { ${exports.map((binding) => `${binding} as ${binding}`).join(', ')} };`;
};
}

function themePlugin(): PluginOption {
function themePlugin(opts): PluginOption {
return {
name: 'vaadin:theme',
config() {
Expand Down Expand Up @@ -336,6 +337,15 @@ function themePlugin(): PluginOption {
}
}
},
async transform(raw, id, options) {
// rewrite urls for the application theme css files
const [bareId, query] = id.split('?');
if (!bareId?.startsWith(themeFolder) || !bareId?.endsWith(".css")) {
return;
}
const [themeName] = bareId.substring(themeFolder.length + 1).split('/');
return rewriteCssUrls(raw, path.dirname(bareId), path.resolve(themeFolder, themeName), console, opts);
}
}
}

Expand All @@ -361,7 +371,7 @@ const allowedFrontendFolders = [
frontendFolder,
addonFrontendFolder,
path.resolve(addonFrontendFolder, '..', 'frontend'), // Contains only generated-flow-imports
path.resolve(frontendFolder, '../node_modules')
path.resolve(__dirname, 'node_modules')
];

export const vaadinConfig: UserConfigFn = (env) => {
Expand All @@ -374,7 +384,7 @@ export const vaadinConfig: UserConfigFn = (env) => {
}

return {
root: 'frontend',
root: frontendFolder,
base: '',
resolve: {
alias: {
Expand Down Expand Up @@ -422,7 +432,7 @@ export const vaadinConfig: UserConfigFn = (env) => {
settings.offlineEnabled && buildSWPlugin(),
settings.offlineEnabled && injectManifestToSWPlugin(),
!devMode && statsExtracterPlugin(),
themePlugin(),
themePlugin({devMode}),
{
name: 'vaadin:force-remove-spa-middleware',
transformIndexHtml: {
Expand Down
1 change: 1 addition & 0 deletions flow-tests/test-custom-frontend-directory/pom.xml
Expand Up @@ -24,6 +24,7 @@
<modules>
<module>test-themes-custom-frontend-directory/pom.xml</module>
<module>test-themes-custom-frontend-directory/pom-generatedTsDir.xml</module>
<module>test-themes-custom-frontend-directory/pom-vite.xml</module>
</modules>
</profile>
</profiles>
Expand Down
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.vaadin</groupId>
<artifactId>test-custom-frontend-directory</artifactId>
<version>23.1-SNAPSHOT</version>
</parent>
<artifactId>flow-test-themes-custom-frontend-directory-vite</artifactId>
<name>Flow themes tests in Vite with custom frontend directory</name>
<packaging>war</packaging>
<properties>
<maven.deploy.skip>true</maven.deploy.skip>
<vaadin.devmode.liveReload.enabled>true</vaadin.devmode.liveReload.enabled>
<vaadin.pnpm.enable>false</vaadin.pnpm.enable>
</properties>

<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-test-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-html-components-testbench</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-dev-server</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-test-lumo</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

<build>
<resources>
<resource>
<directory>${project.basedir}/src/main/vite-resources</directory>
</resource>
</resources>
<testSourceDirectory>${project.basedir}/src/test-vite/java</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>flow-maven-plugin</artifactId>
<configuration>
<frontendDirectory>${project.basedir}/side-src/main/frontend</frontendDirectory>
<productionMode>false</productionMode>
</configuration>
<executions>
<execution>
<id>ensure-fronted-deleted</id>
<goals>
<goal>clean-frontend</goal>
</goals>
<phase>initialize</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<executions>
<execution>
<id>ensure-fronted-deleted</id>
<goals>
<goal>clean</goal>
</goals>
<phase>initialize</phase>
<configuration>
<directory>${project.basedir}/frontend</directory>
<failOnError>false</failOnError>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1 @@
com.vaadin.experimental.viteForFrontendBuild=true

0 comments on commit f827649

Please sign in to comment.