Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,6 @@ private String[] _prepareURLs(long lastModified, String[] urls) {
if (urls[i].contains(contextPath + "/o/")) {
urls[i] = urls[i].replace(contextPath + "/o/", "/o/");
}

if (!urls[i].startsWith("module:")) {
urls[i] = "nocombo:" + urls[i];
}
}

return urls;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
import com.liferay.gradle.util.copy.StripPathSegmentsAction
import com.liferay.gradle.util.hash.HashUtil

import org.gradle.api.file.RelativePath

task buildLiferayAMDLoader(type: Copy)

File jsDestinationDir = file("tmp/META-INF/resources")

buildLiferayAMDLoader {
dependsOn npmInstall
eachFile new StripPathSegmentsAction(4)
from npmInstall.nodeModulesDir

include "@liferay/amd-loader/build/loader/loader.js.map"
include "@liferay/amd-loader/build/loader/loader.js"

includeEmptyDirs = false
into jsDestinationDir

eachFile {
details ->

def sourcePath = details.sourcePath

if (sourcePath.endsWith(".map")) {
sourcePath = sourcePath[0..-5]
}

def md5 = HashUtil.md5(
new File("${npmInstall.nodeModulesDir}/${sourcePath}"))

def hash = md5.asCompactString()[0..11]

def segments = details.relativePath.segments

def fileName = segments[-1]

def i = fileName.indexOf(".")

segments[-1] = "${fileName[0..i]}(${hash})${fileName[i..-1]}"

details.relativePath = new RelativePath(
true, segments[4..-1].toArray(new String[]{}))
}
}

classes {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ public interface FrontendCachingConfiguration {
)
public long esModulesMaxAge();

@Meta.AD(
deflt = "3600", description = "labels-modules-max-age-help",
name = "labels-modules-max-age", required = false
)
public long labelsModulesMaxAge();

@Meta.AD(
deflt = "false",
description = "send-no-cache-for-css-style-sheets-help",
Expand All @@ -48,4 +54,10 @@ public interface FrontendCachingConfiguration {
)
public boolean sendNoCacheForESModules();

@Meta.AD(
deflt = "false", description = "send-no-cache-for-labels-modules-help",
name = "send-no-cache-for-labels-modules", required = false
)
public boolean sendNoCacheForLabelsModules();

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package com.liferay.frontend.js.web.internal.js.importmaps.extender;

import com.liferay.frontend.js.importmaps.extender.DynamicJSImportMapsContributor;
import com.liferay.frontend.js.web.internal.resource.handler.LanguageFrontendResourceRequestHandler;
import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.exception.PortalException;
import com.liferay.portal.kernel.frontend.hashed.files.HashedFilesRegistry;
Expand All @@ -31,14 +32,19 @@ public void writeGlobalImports(
HttpServletRequest httpServletRequest, Writer writer)
throws IOException {

writer.write("\"@liferay/language/\": \"");
writer.write(StringPool.QUOTE);
writer.write(
LanguageFrontendResourceRequestHandler.LANGUAGE_MODULE_PREFIX);
writer.write("\": \"");

String cdnHost = _getCDNHost(httpServletRequest);

writer.write(cdnHost);

writer.write(_portal.getPathContext(httpServletRequest));
writer.write("/o/js/language/\"");
writer.write(
LanguageFrontendResourceRequestHandler.LANGUAGE_URI_PREFIX);
writer.write(StringPool.QUOTE);

_hashedFilesRegistry.forEach(
(unhashedFileURI, hashedFileURI) -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* SPDX-FileCopyrightText: (c) 2025 Liferay, Inc. https://liferay.com
* SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
*/

package com.liferay.frontend.js.web.internal.resource;

import com.liferay.petra.io.StreamUtil;
import com.liferay.portal.kernel.language.Language;
import com.liferay.portal.kernel.util.ContentTypes;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import java.net.URL;

import java.nio.charset.StandardCharsets;

import java.util.ResourceBundle;

/**
* @author Iván Zaera Avellón
*/
public class JavaScriptFrontendResource implements FrontendResource {

public JavaScriptFrontendResource(
String eTag, boolean immutable, Language language, long maxAge,
ResourceBundle resourceBundle, boolean sendNoCache, URL url) {

if (resourceBundle != null) {
if (eTag != null) {
throw new IllegalArgumentException(
"Translated resources cannot have an eTag");
}

if (immutable) {
throw new IllegalArgumentException(
"Translated resources cannot be immutable");
}
}

_eTag = eTag;
_immutable = immutable;
_language = language;
_maxAge = maxAge;
_resourceBundle = resourceBundle;
_sendNoCache = sendNoCache;
_url = url;
}

@Override
public String getContentType() {
return ContentTypes.APPLICATION_JAVASCRIPT;
}

@Override
public String getETag() {
return _eTag;
}

@Override
public InputStream getInputStream() throws IOException {
if (_resourceBundle == null) {
return _url.openStream();
}

String content = _language.process(
() -> _resourceBundle, _resourceBundle.getLocale(),
StreamUtil.toString(_url.openStream()));

return new ByteArrayInputStream(
content.getBytes(StandardCharsets.UTF_8));
}

@Override
public long getMaxAge() {
return _maxAge;
}

@Override
public boolean isImmutable() {
return _immutable;
}

@Override
public boolean isSendNoCache() {
return _sendNoCache;
}

private final String _eTag;
private final boolean _immutable;
private final Language _language;
private final long _maxAge;
private final ResourceBundle _resourceBundle;
private final boolean _sendNoCache;
private final URL _url;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* SPDX-FileCopyrightText: (c) 2025 Liferay, Inc. https://liferay.com
* SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
*/

package com.liferay.frontend.js.web.internal.resource;

import com.liferay.petra.string.StringPool;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONException;
import com.liferay.portal.kernel.json.JSONFactory;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.language.Language;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.ContentTypes;
import com.liferay.portal.kernel.util.LocaleUtil;
import com.liferay.portal.kernel.util.StringUtil;
import com.liferay.portal.kernel.util.URLUtil;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import java.net.URL;

import java.nio.charset.StandardCharsets;

import java.util.Locale;

/**
* @author Iván Zaera Avellón
*/
public class LanguageFrontendResource implements FrontendResource {

public LanguageFrontendResource(
JSONFactory jsonFactory, Language language, String languageId,
long maxAge, boolean sendNoCache, URL url) {

_jsonFactory = jsonFactory;
_language = language;
_languageId = languageId;
_maxAge = maxAge;
_sendNoCache = sendNoCache;
_url = url;
}

@Override
public String getContentType() {
return ContentTypes.TEXT_JAVASCRIPT;
}

@Override
public String getETag() {
return null;
}

@Override
public InputStream getInputStream() throws IOException {
JSONArray languageKeysJSONArray = _getLanguageKeysJSONArray();

StringBuilder sb = new StringBuilder();

Locale locale = LocaleUtil.fromLanguageId(_languageId);

for (int i = 0; i < languageKeysJSONArray.length(); i++) {
String key = languageKeysJSONArray.getString(i);

String label = _language.get(locale, key);

sb.append(StringPool.APOSTROPHE);
sb.append(key.replaceAll("'", "\\\\'"));
sb.append("':'");
sb.append(label.replaceAll("'", "\\\\'"));
sb.append("',\n");
}

String content = StringUtil.replace(
_TPL_JAVA_SCRIPT, new String[] {"[$LABELS$]"},
new String[] {sb.toString()});

return new ByteArrayInputStream(
content.getBytes(StandardCharsets.UTF_8));
}

@Override
public long getMaxAge() {
return _maxAge;
}

@Override
public boolean isImmutable() {
return false;
}

@Override
public boolean isSendNoCache() {
return _sendNoCache;
}

private static String _loadTemplate(String name) {
try (InputStream inputStream =
LanguageFrontendResource.class.getResourceAsStream(
"dependencies/" + name)) {

return StringUtil.read(inputStream);
}
catch (Exception exception) {
_log.error("Unable to read template " + name, exception);
}

return StringPool.BLANK;
}

private JSONArray _getLanguageKeysJSONArray() throws IOException {
try {
JSONObject jsonObject = _jsonFactory.createJSONObject(
URLUtil.toString(_url));

return jsonObject.getJSONArray("keys");
}
catch (JSONException jsonException) {
throw new IOException(
"Invalid language JSON file " + _url, jsonException);
}
}

private static final String _TPL_JAVA_SCRIPT;

private static final Log _log = LogFactoryUtil.getLog(
LanguageFrontendResource.class);

static {
_TPL_JAVA_SCRIPT = _loadTemplate("all.js.tpl");
}

private final JSONFactory _jsonFactory;
private final Language _language;
private final String _languageId;
private final long _maxAge;
private final boolean _sendNoCache;
private final URL _url;

}
Loading