diff --git a/.gitignore b/.gitignore
index ae07d912..ac7a7af1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,5 +14,3 @@ asset-pipeline-site/src/assets/html/apidoc/
.jruby-container
.sass-cache
.sass-work
-node_modules/
-sass-dart-asset-pipeline/src/main/resources/js/
diff --git a/.sdkmanrc b/.sdkmanrc
index 3f5e703c..80e8bbc0 100644
--- a/.sdkmanrc
+++ b/.sdkmanrc
@@ -1 +1,2 @@
gradle=6.5
+java=8.0.312-zulu
diff --git a/asset-pipeline-core.ipr b/asset-pipeline-core.ipr
index 2da04487..2775d189 100644
--- a/asset-pipeline-core.ipr
+++ b/asset-pipeline-core.ipr
@@ -357,14 +357,12 @@
-
+
-
+
-
-
-
+
@@ -1478,9 +1476,12 @@
+
-
+
+
+
@@ -1566,9 +1567,12 @@
+
-
+
+
+
@@ -1695,16 +1699,22 @@
+
-
+
+
+
+
-
+
+
+
@@ -2204,9 +2214,12 @@
+
-
+
+
+
@@ -2794,9 +2807,12 @@
+
-
+
+
+
@@ -2909,9 +2925,12 @@
+
-
+
+
+
diff --git a/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelineExtension.groovy b/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelineExtension.groovy
index 620071ed..5c2bd2ae 100644
--- a/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelineExtension.groovy
+++ b/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelineExtension.groovy
@@ -93,6 +93,6 @@ interface AssetPipelineExtension {
@Input
@Optional
List getResolvers()
- void setResolvers(List value)
+ void setResolvers(List value)
}
diff --git a/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelinePlugin.groovy b/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelinePlugin.groovy
index d692a807..da4e05fe 100644
--- a/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelinePlugin.groovy
+++ b/asset-pipeline-gradle/src/main/groovy/asset/pipeline/gradle/AssetPipelinePlugin.groovy
@@ -48,7 +48,7 @@ class AssetPipelinePlugin implements Plugin {
def defaultConfiguration = project.extensions.create('assets', AssetPipelineExtensionImpl)
def config = AssetPipelineConfigHolder.config != null ? AssetPipelineConfigHolder.config : [:]
- config.cacheLocation = "${project.buildDir}/.assCache"
+ config.cacheLocation = "${project.buildDir}/.assetcache"
if (project.extensions.findByName('grails')) {
defaultConfiguration.assetsPath = 'grails-app/assets'
diff --git a/sass-dart-asset-pipeline/.gitignore b/sass-dart-asset-pipeline/.gitignore
new file mode 100644
index 00000000..9a548f75
--- /dev/null
+++ b/sass-dart-asset-pipeline/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+src/main/resources/js/
+javet-version.txt
diff --git a/sass-dart-asset-pipeline/build.gradle b/sass-dart-asset-pipeline/build.gradle
index a4fa9e93..b99626c8 100644
--- a/sass-dart-asset-pipeline/build.gradle
+++ b/sass-dart-asset-pipeline/build.gradle
@@ -13,11 +13,16 @@ apply plugin: 'groovy'
apply plugin: 'maven-publish'
apply plugin: 'java-library'
apply plugin: 'idea'
+
group = 'com.bertramlabs.plugins'
-ext.isReleaseVersion = !version.endsWith("SNAPSHOT")
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
+ext {
+ javetVersion = "1.0.6"
+ isReleaseVersion = !version.endsWith("SNAPSHOT")
+}
+
repositories {
mavenLocal()
mavenCentral()
@@ -45,10 +50,7 @@ apply plugin: 'com.github.node-gradle.node'
dependencies {
implementation 'org.codehaus.groovy:groovy-all:2.4.19'
-// implementation 'com.eclipsesource.j2v8:j2v8:6.2.1'
-// implementation 'com.eclipsesource.j2v8:j2v8_macosx_x86_64:4.6.0'
-// implementation "com.caoccao.javet:javet:1.0.4"
- implementation 'com.caoccao.javet:javet-macos:1.0.4' // Mac OS (x86_64 Only)
+ implementation "com.caoccao.javet:javet-core:${javetVersion}"
api project(':asset-pipeline-core')
api 'org.slf4j:slf4j-api:1.7.28'
testImplementation 'org.codehaus.groovy:groovy-all:2.4.19'
@@ -107,6 +109,14 @@ publishing {
}
+task copyJavetVersion() {
+ doLast {
+ new File("${projectDir}/src/main/resources", "javet-version.txt").text = javetVersion
+ }
+}
+
+compileGroovy.dependsOn copyJavetVersion
+
task(console, dependsOn: 'classes', type: JavaExec) {
main = 'groovy.ui.Console'
classpath = sourceSets.main.runtimeClasspath
@@ -121,5 +131,5 @@ test {
node {
download = true
- version = "14.18.1"
+ version = "16.13.0"
}
diff --git a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryUtil.groovy b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryUtil.groovy
new file mode 100644
index 00000000..2d762a31
--- /dev/null
+++ b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/NativeLibraryUtil.groovy
@@ -0,0 +1,106 @@
+package asset.pipeline.dart
+
+import asset.pipeline.AssetPipelineConfigHolder
+import com.caoccao.javet.enums.JSRuntimeType
+import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
+
+import java.nio.channels.Channels
+import java.nio.channels.FileChannel
+import java.nio.channels.ReadableByteChannel
+import java.nio.file.Files
+import java.nio.file.Paths
+import java.nio.file.StandardCopyOption
+import java.util.jar.JarEntry
+import java.util.jar.JarFile
+
+@Slf4j
+@CompileStatic
+class NativeLibraryUtil {
+ static final String JAVET_VERSION = NativeLibraryUtil.class.classLoader.getResource("javet-version.txt").openStream().text
+ static final String BASE_URL = AssetPipelineConfigHolder.config?.javetBaseUrl ?: "https://repo1.maven.org/maven2/com/caoccao/javet/javet-macos/${JAVET_VERSION}/"
+
+ static final String TMP_DIR = System.getProperty("java.io.tmpdir")
+ static final String OS_ARCH = System.getProperty("os.arch")
+ static final String OS_NAME = System.getProperty("os.name")
+
+ static final boolean IS_LINUX = OS_NAME.startsWith("Linux")
+ static final boolean IS_MACOS = OS_NAME.startsWith("Mac OS")
+ static final boolean IS_WINDOWS = OS_NAME.startsWith("Windows")
+ static final boolean IS_ARM64 = OS_ARCH.startsWith("arm64") || OS_ARCH.startsWith("armv8") || OS_ARCH == "aarch64"
+ static final boolean IS_X86_64 = OS_ARCH.matches(/^(x8664|amd64|ia32e|em64t|x64|x86_64)$/)
+
+ static final String ARCH_NAME = IS_ARM64 ? 'arm64' : 'x86_64'
+
+ static URL getJavetJARUrl() {
+ new URL(BASE_URL + getJavetFileName())
+ }
+
+ static String getJavetFileName() {
+ IS_MACOS ? "javet-macos-${JAVET_VERSION}.jar" : "javet-${JAVET_VERSION}.jar"
+ }
+
+ static String getLibraryName(JSRuntimeType runtimeType) {
+ if (IS_MACOS) {
+ return "libjavet-${runtimeType.name}-macos-${ARCH_NAME}.v.${JAVET_VERSION}.dylib"
+ }
+ if (IS_LINUX) {
+ return "libjavet-${runtimeType.name}-linux-${ARCH_NAME}.v.${JAVET_VERSION}.so"
+ }
+ if (IS_WINDOWS) {
+ return "libjavet-${runtimeType.name}-windows-${ARCH_NAME}.v.${JAVET_VERSION}.dll"
+ }
+
+ throw new IllegalStateException("Platform type not supported: ${OS_NAME} (${OS_ARCH}), expected Mac OS, Linux, or Windows")
+ }
+
+ static File downloadJavetJar(JSRuntimeType runtimeType) {
+ File jarFile = new File(TMP_DIR, javetFileName)
+ if (jarFile.exists()) {
+ log.debug("Jar file ${jarFile.path} exists, skipping download")
+ return jarFile
+ }
+
+ // Download the file
+ URL jarUrl = getJavetJARUrl()
+ log.info("Downloading sass-dart native library integration: ${jarUrl}")
+
+ copyToFile(jarUrl.openStream(), jarFile)
+
+ log.debug("Downloaded $jarUrl to $jarFile (${jarFile.size()} bytes)")
+ jarFile
+ }
+
+ static File extractNativeLibrary(JSRuntimeType runtimeType) {
+ File file = downloadJavetJar(runtimeType)
+ if (!file.exists()) {
+ throw new IllegalStateException("Javet jar file $file does not exist, perhaps the download failed")
+ }
+
+ String libraryName = getLibraryName(runtimeType)
+ File jniLibrary = new File(TMP_DIR, libraryName)
+ if (jniLibrary.exists()) {
+ log.debug("Native library $libraryName already exists, skipping extract")
+ return jniLibrary
+ }
+
+ // Extract from the JAR file, should be in the root
+ JarFile jarFile = new JarFile(file)
+ JarEntry jarEntry = jarFile.getJarEntry(libraryName)
+ if (!jarEntry) {
+ throw new IllegalStateException("Could not load native library: $libraryName")
+ }
+
+ copyToFile(jarFile.getInputStream(jarEntry), jniLibrary)
+
+ log.debug("Extracted native library $libraryName to ${jniLibrary.path}")
+ jniLibrary
+ }
+
+ static private void copyToFile(InputStream inputStream, File targetFile) {
+ ReadableByteChannel readableByteChannel = Channels.newChannel(inputStream)
+ FileOutputStream fileOutputStream = new FileOutputStream(targetFile)
+ FileChannel fileChannel = fileOutputStream.getChannel()
+ fileChannel.transferFrom(readableByteChannel, 0, Long.MAX_VALUE)
+ }
+}
diff --git a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassAssetFileLoader.groovy b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassAssetFileLoader.groovy
index 0c1fdf73..04f7e4aa 100644
--- a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassAssetFileLoader.groovy
+++ b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassAssetFileLoader.groovy
@@ -2,6 +2,7 @@ package asset.pipeline.dart
import asset.pipeline.AssetFile
import asset.pipeline.AssetHelper
+import asset.pipeline.CacheManager
import com.caoccao.javet.annotations.V8Function
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
@@ -30,19 +31,17 @@ class SassAssetFileLoader {
*
* @param url the import it appears in the source file
* @prev either 'stdin' for the first level imports or the original url from the parent for nested
- * @param assetFilePath the original assetFile.path that started the import chain
* @return https://sass-lang.com/documentation/js-api/modules#LegacyImporterResult
*/
@V8Function
@SuppressWarnings('unused')
- Map resolveImport(String url, String prev, String assetFilePath) {
- log.debug("Resolving import for url [{}], prev [{}], asset file path [{}]", url, prev, assetFilePath)
- println " > Importing [${url}], prev [$prev], asset file [${assetFilePath}]"
+ Map resolveImport(String url, String prev) {
+ log.debug("Importing for url [{}], prev [{}], base file [{}]", url, prev, baseFile?.path)
// The initial import has a path of stdin, but we need to convert that to the proper base path
// Otherwise, if we have a parent, append that to form the correct URL as the importer syntax doesn't send what's expected
if (prev == 'stdin') {
- prev = assetFilePath
+ prev = baseFile.path
}
else {
// Resolve the real base path for this import
@@ -60,6 +59,8 @@ class SassAssetFileLoader {
importMap[url] = prev
AssetFile imported = getAssetFromScssImport(prev, url)
+ CacheManager.addCacheDependency(baseFile.path, imported)
+
return [contents: imported.inputStream.text]
}
diff --git a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy
index ae796d9f..68170e1d 100644
--- a/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy
+++ b/sass-dart-asset-pipeline/src/main/groovy/asset/pipeline/dart/SassProcessor.groovy
@@ -19,11 +19,10 @@ import asset.pipeline.AbstractProcessor
import asset.pipeline.AssetCompiler
import asset.pipeline.AssetFile
import com.caoccao.javet.enums.JSRuntimeType
-import com.caoccao.javet.interception.logging.JavetStandardConsoleInterceptor
-import com.caoccao.javet.interop.V8Runtime
-import com.caoccao.javet.interop.engine.IJavetEngine
-import com.caoccao.javet.interop.engine.IJavetEnginePool
-import com.caoccao.javet.interop.engine.JavetEnginePool
+import com.caoccao.javet.interop.NodeRuntime
+import com.caoccao.javet.interop.V8Host
+import com.caoccao.javet.interop.loader.IJavetLibLoadingListener
+import com.caoccao.javet.interop.loader.JavetLibLoader
import com.caoccao.javet.values.reference.V8ValueObject
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
@@ -31,20 +30,31 @@ import groovy.util.logging.Slf4j
@Slf4j
@CompileStatic
class SassProcessor extends AbstractProcessor {
- final IJavetEnginePool javetEnginePool
final String sassCompiler
+ static {
+ File nativeLibrary = NativeLibraryUtil.extractNativeLibrary(JSRuntimeType.Node)
+
+ // Override Javet to use the library that we downloaded for this platform
+ JavetLibLoader.setLibLoadingListener(new IJavetLibLoadingListener() {
+ @Override
+ File getLibPath(JSRuntimeType jsRuntimeType) {
+ return nativeLibrary.parentFile
+ }
+
+ @Override
+ boolean isDeploy(JSRuntimeType jsRuntimeType) {
+ return false
+ }
+ })
+ }
+
SassProcessor(AssetCompiler precompiler) {
super(precompiler)
// Load script from classpath
URL resource = getClass().classLoader.getResource("js/compiler.js")
sassCompiler = resource.openStream().text
-
- // Setup a Javet engine for pooling
- javetEnginePool = new JavetEnginePool<>()
- javetEnginePool.getConfig().setJSRuntimeType(JSRuntimeType.Node)
- javetEnginePool.getConfig().setAllowEval(true)
}
/**
@@ -55,23 +65,19 @@ class SassProcessor extends AbstractProcessor {
*/
String process(String input, AssetFile assetFile) {
log.debug "Compiling $assetFile.path"
- println "Compiling $assetFile.path"
String output = null
- IJavetEngine javetEngine = javetEnginePool.getEngine()
+ NodeRuntime nodeRuntime = V8Host.getNodeInstance().createV8Runtime(true, JSRuntimeType.Node.runtimeOptions) as NodeRuntime
try {
- V8Runtime v8Runtime = javetEngine.getV8Runtime()
-
- // Create a Javet console interceptor.
- JavetStandardConsoleInterceptor javetConsoleInterceptor = new JavetStandardConsoleInterceptor(v8Runtime)
- javetConsoleInterceptor.register(v8Runtime.getGlobalObject())
+ nodeRuntime.allowEval(true)
// Bind the importer callback
SassAssetFileLoader loader = new SassAssetFileLoader(assetFile)
- V8ValueObject v8ValueObject = v8Runtime.createV8ValueObject()
+
+ V8ValueObject v8ValueObject = nodeRuntime.createV8ValueObject()
try {
- v8Runtime.getGlobalObject().set("importer", v8ValueObject)
+ nodeRuntime.getGlobalObject().set("importer", v8ValueObject)
v8ValueObject.bind(loader)
}
finally {
@@ -80,21 +86,17 @@ class SassProcessor extends AbstractProcessor {
// Setup the options passed to the SASS compiler
// https://sass-lang.com/documentation/js-api/interfaces/LegacyStringOptions
- v8Runtime.getGlobalObject().setProperty("compileOptions", [
- assetFilePath: assetFile.path,
- data: input,
- ])
+ nodeRuntime.getGlobalObject().setProperty("compileOptions", [data: input])
// Compile and retrieve the CSS output
- v8Runtime.getExecutor(sassCompiler).executeVoid()
- output = v8Runtime.getGlobalObject().get("css") as String
+ nodeRuntime.getExecutor(sassCompiler).executeVoid()
+ output = nodeRuntime.getGlobalObject().get("css") as String
- // Unregister the Javet console to V8 global object.
- javetConsoleInterceptor.unregister(v8Runtime.getGlobalObject())
- v8Runtime.lowMemoryNotification()
+ // Cleanup the global importer
+ nodeRuntime.getGlobalObject().delete("importer")
}
finally {
- javetEngine.close()
+ nodeRuntime.close()
}
output
diff --git a/sass-dart-asset-pipeline/src/main/js/compiler.js b/sass-dart-asset-pipeline/src/main/js/compiler.js
index 8ea40daf..1164ae87 100644
--- a/sass-dart-asset-pipeline/src/main/js/compiler.js
+++ b/sass-dart-asset-pipeline/src/main/js/compiler.js
@@ -1,11 +1,7 @@
const sass = require('sass');
// Call back for resolving imports via the Java asset pipeline resolvers
-compileOptions.importer = [
- function(url, prev) {
- return importer.resolveImport(url, prev, compileOptions.assetFilePath);
- }
-];
+compileOptions.importer = [importer.resolveImport]
// Compile and return the rendered CSS
result = sass.renderSync(compileOptions);
diff --git a/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy b/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy
index ccaf1b7c..c4f9b4e2 100644
--- a/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy
+++ b/sass-dart-asset-pipeline/src/test/groovy/asset/pipeline/dart/SassProcessorSpec.groovy
@@ -16,9 +16,10 @@
package asset.pipeline.dart
+import asset.pipeline.AssetHelper
+import asset.pipeline.AssetPipelineConfigHolder
+import asset.pipeline.fs.FileSystemAssetResolver
import spock.lang.Specification
-import asset.pipeline.fs.*
-import asset.pipeline.*
/**
* @author David Estes
@@ -34,7 +35,6 @@ class SassProcessorSpec extends Specification {
def processor = new SassProcessor()
when:
def output = processor.process(assetFile.inputStream.text,assetFile)
- println "Results \n ${output}"
then:
output.contains('margin')
}
@@ -48,7 +48,6 @@ class SassProcessorSpec extends Specification {
def processor = new SassProcessor()
when:
def output = processor.process(assetFile.inputStream.text,assetFile)
- println "Results \n ${output}"
then:
output.contains('.sub')
}
@@ -62,7 +61,6 @@ class SassProcessorSpec extends Specification {
def processor = new SassProcessor()
when:
def output = processor.process(assetFile.inputStream.text,assetFile)
- println "Results \n ${output}"
then:
output.contains('.bar')
}
@@ -76,7 +74,6 @@ class SassProcessorSpec extends Specification {
def processor = new SassProcessor()
when:
def output = processor.process(assetFile.inputStream.text, assetFile)
- println "Results \n ${output}"
then:
output.length() > 0
}
@@ -90,7 +87,6 @@ class SassProcessorSpec extends Specification {
def processor = new SassProcessor()
when:
def output = processor.process(assetFile.inputStream.text, assetFile)
- println "Results \n ${output}"
then:
output.contains('Twitter')
}