How to add tailwindcss to KotlinJS - kotlin

I am unable to add the tailwindcss library to my KotlinJS project. I tried multiple things.
I have multiple dependencies defined in my build.gradle.kts
implementation(npm("postcss", "latest"))
implementation(npm("postcss-loader", "latest"))
implementation(npm("tailwindcss", "1.8.10"))
I tried creating a tailwindcss.js in my webpack.config.d with this content
config.module.rules.push({
test: /\.css$/i,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'tailwindcss'
],
],
},
},
}
]
}
);
But that doesn't do anything. I also tried modifying this with multiple options, but I was never able to get tailwindcss to compile. I also tried disabling and enabling the KotlinJS CSS support in build.gradle.kts
I can't find any info on how to add postcss to KotlinJS project.
Thank you for any help.

A basic integration can be achieved with the node-gradle plugin.
In your build.gradle.kts:
plugins {
id("com.github.node-gradle.node") version "3.0.0-rc2"
}
Also in build.gradle.kts define a task called "tailwindcss" that calls the tailwind CLI via npx. For example:
val tailwindCss = tasks.register<com.github.gradle.node.npm.task.NpxTask>("tailwindcss") {
// Output CSS location
val generatedFile = "build/resources/main/static/css/tailwind-generated.css"
// Location of the tailwind config file
val tailwindConfig = "css/tailwind.css"
command.set("tailwind")
args.set(listOf("build", tailwindConfig, "-o", generatedFile))
dependsOn(tasks.npmInstall)
// The location of the source files which Tailwind scans when running ```purgecss```
inputs.dir("src/main/kotlin/path/to/your/presentation/files")
inputs.file(tailwindConfig)
outputs.file(generatedFile)
}
Finally, in build.gradle.kts bind the task to your processResources step, so that it runs automatically. Note you may want to refine this later, because running tailwind every time the processResources step is invoked will slow down your dev cycle.
tasks.processResources {
dependsOn(tailwindCss)
}
Now we need a minimal package.json in the root of your project. For example:
{
"name": "MyProject",
"devDependencies": {
"tailwindcss": "^1.7.0"
}
}
Finally, we configure our tailwind config in the location defined by our NpxTask, in the example ```css/tailwind.css"
#tailwind base;
#tailwind components;
#tailwind utilities;
So now after the processResource step is run, gradle will invoke the Tailwind npx task, consume your source and write the CSS to the location you specified.

The accepted answer seems to not work anymore. Also, using the Node Gradle plugin is sub-optimal (KotlinJS already maintains its own package.json and yarn installation).
I managed to get Tailwind to work with KotlinJS thanks for this repository (GitHub) with a few small updates that you can find here (GitLab).

The linked I posted is the answer, the whole repository. It is not just a part of it
If you instead want me to copy/paste the whole repository instead here you're
= Kotlin/JS + Tailwind CSS =
This is a small sample repository to show the idiomatic way of
configuring these two systems together.
== Running it ==
. Run `./gradlew run`.
. Open `http://localhost:8080/` in your browser.
. 🎉 Notice we're using Tailwind CSS classes successfully.
== How To ==
Steps taken to make this work:
=== Dependencies ===
Add the following dependencies to your JS target (`jsMain` dependencies) in your Gradle file:
[source,kotlin]
----
implementation("org.jetbrains:kotlin-extensions:1.0.1-pre.148-kotlin-1.4.21")
implementation(npm("postcss", "8.2.6"))
implementation(npm("postcss-loader", "4.2.0")) // 5.0.0 seems not to work
implementation(npm("autoprefixer", "10.2.4"))
implementation(npm("tailwindcss", "2.0.3"))
----
* `kotlin-extensions` is necessary to get the JavaScript link:https://github.com/JetBrains/kotlin-wrappers/blob/master/kotlin-extensions/src/main/kotlin/kotlinext/js/CommonJS.kt#L20[`require`] function.
** Make sure the version number matches your version of the Kotlin multiplatform plugin at the top of your Gradle file.
** Kotlin Multiplatform 1.4.30 gave me `No descriptor found for library` errors. Try 1.4.21.
** Find the latest versions link:https://bintray.com/kotlin/kotlin-js-wrappers/kotlin-extensions[here].
* `postcss` and `autoprefixer` are link:https://tailwindcss.com/docs/installation#install-tailwind-via- npm[dependencies] as mentioned in the Tailwind CSS docs.
* `postcss-loader` is required because Kotlin/JS is built on top of Webpack.
** Note that while 5.0.0 is out, using it gave me build errors. The latest 4.x seems to work.
* `tailwindcss` is obviously what we're here for.
=== Add Tailwind as a PostCSS plugin ===
Just do link:https://tailwindcss.com/docs/installation#add-tailwind-as-a-post-css-plugin[this step].
If unsure, create this file in your project root:
[source,javascript]
----
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
}
}
----
=== Create your configuration file (optional) ===
link:https://tailwindcss.com/docs/installation#create-your-configuration-file[Official documentation].
Creating the `tailwind.config.js` file is a little tricky because simply `npx` won't work, as we haven't installed any
`node_modules`. Fortunately, Kotlin/JS has already done this for us.
Run the following:
[source,shell]
----
$ ./gradlew kotlinNpmInstall
$ ( cd build/js/ && npx tailwindcss init && mv tailwind.config.js ../../ )
----
This generates `tailwind.config.js` in the `build/js/` directory and then moves it up two directories to the project
root. Kotlin/JS generates the node modules into build/js/node_modules when the kotlinNpmInstall task runs.
This assumes your JavaScript module is `js`. If it's not, you'll need to change the `cd build/js/` part. If you're not
sure where your node_modules directory is, run find . -maxdepth 3 -name node_modules.
You should now have all your dependencies set up and config files created.
=== Create and Reference a Regular CSS File ===
_If you already have a CSS file that you're loading in your app, you can skip this step._
Create `app.css` in your `jsMain/resources/` directory. Put something obvious in there so you know
when it's loaded:
[source,css]
----
body {
background-color: red;
}
----
This file will get copied into the same folder as your transpiled JavaScript files.
In your JavaScript file (`client.kt` in this package), add:
[source,javascript]
----
kotlinext.js.require("./app.css")
----
to your main method. You can of course import the require method if you prefer.
If you run `./gradlew run`, you should be able to see a red page at `http://localhost:8080/`.
We're almost there, but we have two more steps: tell Webpack to use PostCSS and to finally inject Tailwind CSS.
=== Using PostCSS with Webpack ===
We want to "monkeypatch" the Webpack configuration that Kotlin/JS generates for us. This hook is
documented in the link:https://kotlinlang.org/docs/js-project-setup.html#webpack-bundling[webpack bundling] section. Basically, if we create .js files in webpack.config.d/, they'll be automatically
merged into build/js/packages/projectName/webpack.config.js, which exists after a build and you can go inspect.
The "problem", if you have `cssSupport.enabled = true` in your Gradle file (which you should!), is that this line
generates a webpack rule matching /\.css$/. We can't simply create another rule matching the same files...that
won't work.
So, we need to find the original rule and modify it. Create the following file relative to your project root:
[source,javascript]
----
// in webpack.config.d/postcss-loader.config.js
(() => {
const cssRule = config.module.rules.find(r => "test.css".match(r.test));
if (!cssRule) {
throw new Error("Could not resolve webpack rule matching .css files.");
}
cssRule.use.push({
loader: "postcss-loader",
options: {}
});
})();
----
We use an IIFE so that our new variable doesn't potentially interfere with other unseen variables.
Now PostCSS is working!
With PostCSS configured and the `tailwindcss` npm module in our dependencies, all that's left now
is to use it.
=== Importing Tailwind CSS ===
We're basically smooth sailing from here. Follow the link:https://tailwindcss.com/docs/installation#include-tailwind-in-your-css[Include Tailwind in your CSS] directions.
Just stick the following in your `app.css`:
[source,css]
----
#tailwind base;
#tailwind components;
#tailwind utilities;
----
If you start the server again, it should **Just Work**! It's a bit hard to tell, but if you check the devtools,
you should see the tw classes loading and massive js.js file being loaded (9.20mb!) which contains all of Tailwind CSS.
== Areas for Improvement ==
=== Modifications to app.css ===
Changes made to app.css don't get picked up unless you do a full `./gradlew clean` first, which is painful.
Adding the following line to build.gradle.kts seems to fix this:
[source,kotlin]
----
tasks.withType(KotlinWebpack::class.java).forEach { t ->
t.inputs.files(fileTree("src/jsMain/resources"))
}
----
=== Getting --continuous working ===
Even with the above fix, --continuous doesn't seem to work. 🤷
== Future Topics ==
* link:https://tailwindcss.com/docs/installation#building-for-production[Building for Production]

Related

How to add a loader in a Vue/Webpack app to support non JS files used in a dependency of a node module

I have a Vue 2 app that uses Webpack, and I am trying to use in it the node module PSD.js, which in itself utilizes CoffeeScript as part of it's dependencies. When I try to compile i get the error:
Module parse failed: Unexpected character '#' (1:0) You may need an appropriate loader to handle this file type,
referring to the the file ./node_modules/coffee-script/lib/coffee-script/register.js that PSD.js installed as part of it's dependencies when I did npm install psd.
Any ideas on how to make this work?
I understand I need to tell the Vue app how to handle .coffee files with a loader, but I have tried installing coffee-loader, coffee, set the vue.config.js to:
module.exports = {
publicPath: "./",
configureWebpack: {
target: "node-webkit",
node: false,
module: {
rules: [
// ...
{
test: /\.coffee$/,
use: [
{
loader: 'coffee-loader'
}
]
}
]
}
},
lintOnSave: false
};
yet still nothing works, I get the same error. I feel it is because I am not using CoffeeScript directly but rather a node module that I AM using, psd.js, is the one using it. That is why I cannot set lang="coffee" in the script tag attribute of my Vue module (I am using vanilla JS to run everything).
thnx in advance
ADDING MORE INFO:
I use a boilerplate framework to setup my app, and it initialises the vue/webpack app for me indirectly.
To reproduce, and even though this system is for Adobe plugins, you do not need the Adobe host app to see the issue, do:
npm install -g bombino
Then in a folder of your choosing run:
bombino
and fill in these params when asked:
? Name of panel? Hello World
? Use your custom templates or bombino defaults? Bombino
What tooling preset should be used? Vue-CLI
? Which Vue-CLI template should be used? bombino-vue-bare (Absolute minimum)
? Host apps to include: After Effects
? Base CEF Port (between 1024 and 65534) 8666
? Run npm install for you? Yes
then cd into Hello-World and run npm run serve. You should see the app is compiled correctly and is running on some port (8080 or higher if taken).
Now go back to the root folder and install psd.js: npm install psd
then go back into Hello-World and run npm run serve again. This time it will fail to compile with the error I started this question with. Even if you go and install coffee-loader by doing npm install --save coffeescript coffee-loader and change the vue.config.js to be like so:
publicPath: "./",
// Thanks Eric Robinson
configureWebpack: {
target: "node-webkit", // Set the target to node-webkit (https://webpack.js.org/configuration/target/)
node: false, // Don't set certain Node globals/modules to empty objects (https://webpack.js.org/configuration/node/),
module: {
rules: [
// ...
{
test: /\.coffee$/,
use: [
{
loader: 'coffee-loader'
}
]
}
]
}
},
lintOnSave: false
};
or if you do vue use coffee - all of these result in the same error: the compiler/packager doesn't know how to handle the .coffee file (used as a dependency by psd.js).
Thnx again to anyone who has info

How to bundle tailwind css inside a Vue Component Package

In one of my projects, I build a nice vue3 component that could be useful to several other projects. So I decided to publish it as an NPM package and share it with everyone.
I wrote the isolate component, build it and publish BUT I use Tailwind css to make the style.
When I publish and install the component everything is working BUT without the beauty of the css part.
I tried several configurations and alternative tools to generate the package that automatically add the tailwind as an inner dependency to my package.
Does someone have experience with this? how can build/bundle my component by adding the tailwind CSS instructions into it?
You're almost there
Since you've got your component working, the majority of the part has been done.
For configuring the styling of the component you need to identify the Tailwind CSS classes being used by your Vue component package and retain them in the final CSS that is generated by the Tailwind engine in your project.
Follow below steps in the project where you want to use your tailwind vue component package.
For Tailwind CSS V3
// tailwind.config.js
module.exports = [
//...
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
"./node_modules/package-name/**/*.{vue,js,ts,jsx,tsx}" // Add this line
// Replace "package-name" with the name of the dependency package
],
//...
]
For Tailwind CSS V2
// tailwind.config.js
module.exports = [
//...
purge: {
//...
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
"./node_modules/package-name/**/*.{vue,js,ts,jsx,tsx}" // Add this line
// Replace "package-name" with the name of the dependency package
],
//...
//...
}
]
The content property in the tailwind.config.js file defines file path pattern that the tailwind engine should look into, for generating the final CSS file.
For Pro users
You may also try to automate the above setup by writing an install script for your npm package to add this configuration to the tailwind.config.js file
References
Tailwind Docs - 3rd party integration
It's a bit difficult for someone to answer your question as you've not really shared the source code, but thankfully (and a bit incorrectly), you've published the src directory to npm.
The core issue here is that when you're building a component library, you are running npm run build:npm which translates to vue-cli-service build --target lib --name getjvNumPad src/index.js.
The index.js reads as follows:
import component from './components/numeric-pad.vue'
// Declare install function executed by Vue.use()
export function install (Vue) {
if (install.installed) return
install.installed = true
Vue.component('getjv-num-pad', component)
}
// Create module definition for Vue.use()
const plugin = {
install
}
// Auto-install when vue is found (eg. in browser via <script> tag)
let GlobalVue = null
if (typeof window !== 'undefined') {
GlobalVue = window.Vue
} else if (typeof global !== 'undefined') {
GlobalVue = global.Vue
}
if (GlobalVue) {
GlobalVue.use(plugin)
}
// To allow use as module (npm/webpack/etc.) export component
export default component
There is no mention of importing any CSS, hence no CSS included in the built version.
The simplest solution would be to include the index.css import in your index.js or the src/components/numeric-pad.vue file under the <style> section.
Lastly, I'm a bit rusty on how components are built, but you might find that Vue outputs the CSS as a separate file. In that case, you would also need to update your package.json to include an exports field.

Bundle size is big, how to reduce size of app.js?

I am using Vue.js and have only 4 components in my project.
I imported only bootstrap, jquery and lodash:
import { map } from 'lodash';
import 'bootstrap/js/dist/modal';
import $ from "jquery";
But npm run production creates
bundle of 400kb size.
npm run production is configured as shown below.
cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js
Is it possible to reduce bundle size to ~100KB ? If yes how?
You should add bundle analyzer to your webpack config.
That tool will help you to understand what is going on with your final bundle for example:
you have imported something accidentally and didn't noticed that
one of your dependencies is really big and you should avoid using it
you accidentally imported whole library when you just wanted to import single function from that library (that is common with lodash)
Here is an example of how you can add bundle analyzer to your webpack config:
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const isBundleAnalyze = true; // turn it too true only when you want to analyze your bundle, should be false by default
module.exports = {
// ... rest webpack config here
plugins: [
// ... rest webpack plugins here
...isBundleAnalyze ? [ new BundleAnalyzerPlugin() ] : []
]
};
Also check your final js file.
It should be a single line of code with simple variables. Something like this: !function(e){function t(t){for(var n,r,o=t[0],i=t[1],s=0,l=[];s<o.length;s++) if it doesn't looks like that it means that you configured your production webpack build incorrectly.
It's pretty obvious why your bundle is over 400kb, you are importing lodash and jquery, you are just missing moment.js (a little joke), but one thing that you can do is use only what you need.
First, if you are using Vue, or React, or any of those jQuery UI libraries you shouldn't be using jQuery unless is necessary.
Another thing that you can do is import only what you need, instead of:
import { map } from 'lodash';
try
import map from 'lodash/map';
or even better
import map from 'lodash.map';
https://www.npmjs.com/package/lodash.map
Lazy imports, read more here. This will allow splitting your bundle into pieces that can be called at execution time, reducing considerably your app size.
const Foo = () => import('./Foo.vue')
There is also SSR (Server Side Rendering), which is basically generating the initial HTML code of your app at build time and rendering outputting that, to show the users that something is on the site, but you also need to understand that, this won't do much, since the browser needs to parse the Javascript code (the hydration process) in order to make the site functional.
If you are using React as of April 2021, the React team announced React Server Components, which seems like a big thing coming up, I supposed that many other libraries will be moving components to the server (and I hope Vue does).
Again as of today don't use it on production.
Other answers mentioned the use of webpack-bundle-analyzer, here is a trick how to use it:
webpack.config.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const analyzing = process.env.NODE_ENV === 'analyze';
module.exports = {
plugin: [
...(analyzing ? [new BundleAnalyzerPlugin()] : [])
]
}
on your package.json
{
"scripts": {
"analyze": "NODE_ENV=analyze webpack build"
}
}
use CompressionWebpackPlugin and try gzip

how to override vue cli-service entry settings

I'm trying to integrate a vue project that I built with the vue cli into an existing .net app. I'm very new to vue, so I'm trying to follow guides and such, but am left with lots of questions.
While trying to compile this, I found that the vue cli-service node module has the following for setting the main.js file located in it's base.js file.
webpackConfig
.mode('development')
.context(api.service.context)
.entry('app')
.add('./src/main.js')
.end()
.output
.path(api.resolve(options.outputDir))
.filename(isLegacyBundle ? '[name]-legacy.js' : '[name].js')
.publicPath(options.publicPath)
I need to override this since my .net app doesn't have a src directory and the usage of this vue app won't follow that path structure. I'm not seeing a way to do it in my vue.config.js file. I would expect that if I can override it, that would be the spot.
I could overwrite the base.js file where this exists, but when a co-worker runs npm install, they would get the default value rather than what I have. The only option I see there is checking in all the node modules to git which we really don't want to do.
For anyone in a similar situation, I found what worked for me. It's not the ideal solution due to the fact that it forces you to build into a js folder. That resulted in the file being put in Scripts\build\vue\js. Would be nice to be able to just dump it in the vue folder, but at least this works. Code below.
vue.config.js
module.exports = {
publicPath : "/",
outputDir: "Scripts/build/vue", //where to put the files
// Modify Webpack config
// https://cli.vuejs.org/config/#chainwebpack
chainWebpack: config => {
// Not naming bundle 'app'
config.entryPoints.delete('app'); //removes what base.js added
},
// Overriding webpack config
configureWebpack: {
// Naming bundle 'bundleName'
entry: {
quote: './Scripts/Quote/index.js' //where to get the main vue app js file
},
optimization: {
splitChunks: false
}
},
filenameHashing: false,
pages: {
quoteApp: { //by using pages, it allowed me to name the output file quoteApp.js
entry: './Scripts/Quote/index.js',
filename: 'index.html'
}
}
}

Nuxt ignoring babel on build process

https://nuxtjs.org/api/configuration-build#babel
I originally left the presets as default.
I then followed the suggestions on
https://github.com/nuxt/nuxt.js/issues/1776
However this dealt more with pipelines
I am just trying to get it to convert the es6 to es5 (import chief among the reasons)
I get the same result or a complete failure no matter if i add the .babelrc, adjust package.json, adjust nuxt.config.js or a combination of them.
currently i have adjusted my nuxt.config.js to:
/*
** Build configuration
*/
build: {
babel: {
presets: ['#babel/preset-env'],
configFile: false,
babelrc: false,
plugins: ['#babel/plugin-syntax-dynamic-import']
}
}
When i upload the entire .nuxt folder to my server (running plesk using phusion passenger)
I get the following error
/var/www/vhosts/website.com/app/client/server.js:1
(function (exports, require, module, __filename, __dirname) { import { stringify } from 'querystring'
My site root is
/var/www/vhosts/website.com/app/client/
The first line of server.js
import { stringify } from 'querystring
Changing this to
var stringify = require("querystring").stringify
Eliminates the error however i would need to go through page after page to remove this. My understanding is i can progamically adjust this using babel. But no matter what ive tried the file stays the same.
I did use the Nuxt CLI to automatically set up babel and webpack but using the above build config is not the default. I have attempted to play with it but i get the same result
I added babel/polyfill to try and get around the import issues without any success