bertramdev / asset-pipeline

The core implementation of the asset pipeline for the jvm
193 stars 91 forks source link

Port SASS plugin to use the Dart Sass JS compiler #287

Open longwa opened 2 years ago

longwa commented 2 years ago

Given that libsass is deprecated and the official recommendation is to use the Dart Sass compiler, it seems reasonable to try and port the asset pipeline sass plugin to use this compiler.

This would also allow for native compatibility with the Apple M1 machines, which a lot of developers are starting to use (including me).

Per my discussion with @davydotcom, I have started prototyping a solution using J2V8 Javet to execute JavaScript natively in the JVM.

At a high level, my initial implementation is:

There are some technical challenges still with this approach:

longwa commented 2 years ago

The author of the Javet framework suggested on a J2V8 issue that his library might be more up-to-date and suitable:

https://github.com/eclipsesource/J2V8/issues/498

https://github.com/caoccao/Javet

I'm able to load the sass.js bundle and execute successfully using Javet, so I'm going to continue down that road.

caoccao commented 2 years ago

So far, Javet doesn't support Apple Silicon because I don't own such device. It would be great if you could help support that. I look forward to your pull requests. Good luck!

longwa commented 2 years ago

@caoccao Absolutely, assuming I can get the Dart Sass compiler fully working with Javet, I will definitely work on porting natively to the M1. Thanks for Javet and the MacOS version, I honestly don't know what our options would be without something like this.

caoccao commented 2 years ago

Sure, there is always a way out and that keeps motivating me on maintaining Javet.

By the way, as the JS infra is moving towards Rust, I wonder if asset-pipeline will embrace that trend in the future. I tested swc (with ~20X performance improvement) and confirmed it can work well with Javet on Linux and Windows. So, basically, the Rust based JS infra can be supported by Javet natively. Hope that makes sense to this project.

longwa commented 2 years ago

@caoccao I was wondering, is there a way with Javet to choose, at runtime, which native library is used?

I'd like to be able to distribute this asset-pipeline plugin for all platforms and choose the native Javet implementation at runtime as opposed to forcing people of this library to choose a platform and arch.

longwa commented 2 years ago

I have a working version on x86 that successfully compiles our project assets (which have a pretty complex set of imports including BS 4.6).

caoccao commented 2 years ago

@caoccao I was wondering, is there a way with Javet to choose, at runtime, which native library is used?

I'd like to be able to distribute this asset-pipeline plugin for all platforms and choose the native Javet implementation at runtime as opposed to forcing people of this library to choose a platform and arch.

Yes, Javet allows custom native library deployment.

Javet classes can be completely separated from the native libraries. The current packaging strategy is a trade-off between package size and end-user experience.

  1. All native libraries (Android, Mac OS, Linux and Windows x Node.js and V8) can be packaged together, of course, the jar file size can reach 100+MB.
  2. Some Javet users just customize the jar file by embedding the native libraries they need. The jar file size can be less than 10MB.
  3. Some Javet users just package the classes in the jar file and tell Javet where to load the native libraries. This allows Javet to work with the native image.

I guess option 3 might be the one you are looking for. Here is what I would do if I were you.

Please let me know if you have any questions. You may reach me at discord.

longwa commented 2 years ago

I was able to build a native M1 version of Javet from scratch (building V8 and Node.js for M1 as well). All of the sass-asset tests are passing and our main project using BS 4.6 is also compiling successfully on the M1.

Just have to solve the packaging problem which Sam has given direction on above as well as a problem where the Gradle daemon is holding onto the JNI library and causing a failure when trying to start another daemon.

Caused by: java.lang.UnsatisfiedLinkError: Native Library /private/var/folders/19/ph165v_n45j288nr69_djr5h0000gn/T/javet/88301/libjavet-node-macos-arm64.v.1.0.5.dylib already loaded in another classloader

I think unloading and loading the library on demand is probably the fix for both issues.

caoccao commented 2 years ago

Congratulations on your wonderful progress!

Regarding this error, if everything runs smoothly, it can be ignored. Javet default logger just logs it as an error when the JNI lib is being loaded to the same JVM twice. Basically, it doesn't mean things are breaking.

You may inject your custom logger into Javet to mute this error, I think. Or, I may update Javet to read a system property for skipping this error.

Behind the scene, JVM only allows one memory copy of a particular JNI lib regardless of which classloader the lib is being loaded to. I think Gradle reuses the same JVM process when restarting a new daemon and this issue occurs.

IntrepidDemian commented 2 years ago

I just want drop in here and say thank you for working on this. I just got my new work MacBook Pro M1x and ran across this in setting up my local dev environment but haven't had time to allocate to figure out the issue due to year end commitments. I look forward to the progress on this and again, thank you. 🥇

longwa commented 2 years ago

Linking the M1 Javet support ticket: https://github.com/caoccao/Javet/issues/120

longwa commented 2 years ago

Waiting for Javet 1.0.6 before opening a PR for the new plugin. There are a few fixes for dynamically loading the native libraries needed in that release as well as the native M1 support.

azavisha-snap commented 2 years ago

Thank you for your contributions @longwa. You saved me a lot of time! Looking forward to this release.

caoccao commented 2 years ago

Waiting for Javet 1.0.6 before opening a PR for the new plugin. There are a few fixes for dynamically loading the native libraries needed in that release as well as the native M1 support.

Javet v1.0.6 was released.

longwa commented 2 years ago

I have integrated the 1.0.6 version of Javet and have our team using it on our Macs (both x86 and ARM) as well as using it in our CI/CD pipelines. I'm going to give it a day or two internally to make sure there are not major issues and then I'll push forward with the PR.

longwa commented 2 years ago

PR https://github.com/bertramdev/asset-pipeline/pull/290 has been opened for this issue.

mkobel commented 2 years ago

PR #299 allows to resolve absolute paths as in the old sass-asset-pipeline

mkobel commented 2 years ago

PR #299 allows to resolve absolute paths as in the old sass-asset-pipeline

Is there anything I can do/improve for the acceptance of my PR #299 ?

mkobel commented 2 years ago

PR https://github.com/bertramdev/asset-pipeline/pull/299 allows to resolve absolute paths as in the old sass-asset-pipeline @longwa @davydotcom Is there anything I can do/improve for the acceptance of my PR https://github.com/bertramdev/asset-pipeline/pull/299 ?

galenodemelo commented 11 months ago

For those who might experience it in the future: Grails 5 + Java 11.0.18-amzn (Corretto) don't work well because it's missing some native methods.

The solution, in our case, was to move to Temurin version. So far, so good.

I'd like to take the opportunity to thank you guys for the great work <3

Stacktrace ``` Caused by: java.lang.UnsatisfiedLinkError: 'long com.caoccao.javet.interop.V8Native.createV8Runtime(java.lang.Object)' at com.caoccao.javet.interop.V8Native.createV8Runtime(Native Method) at com.caoccao.javet.interop.V8Host.createV8Runtime(V8Host.java:266) at asset.pipeline.dart.SassProcessor.process(SassProcessor.groovy:80) at asset.pipeline.Processor$process$0.call(Unknown Source) at asset.pipeline.Processor$process$0.call(Unknown Source) at asset.pipeline.AbstractAssetFile.processedStream(AbstractAssetFile.groovy:171) at asset.pipeline.AbstractAssetFile.processedStream(AbstractAssetFile.groovy) at asset.pipeline.DirectiveProcessor.fileContents(DirectiveProcessor.groovy:305) at asset.pipeline.DirectiveProcessor$fileContents$18.callCurrent(Unknown Source) at asset.pipeline.DirectiveProcessor.loadContentsForTree(DirectiveProcessor.groovy:127) at asset.pipeline.DirectiveProcessor.compile(DirectiveProcessor.groovy:65) at asset.pipeline.DirectiveProcessor$compile$6.call(Unknown Source) at asset.pipeline.AssetCompiler$_compile_closure4.doCall(AssetCompiler.groovy:160) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ```