Logo

dev-resources.site

for different kinds of informations.

Converting JS Libraries to Clojure: Part 1

Published at
11/20/2023
Categories
clojure
javascript
clojurescript
learning
Author
bop
Author
3 person written this
bop
open
Converting JS Libraries to Clojure: Part 1

What are we doing here?

This is a series of articles with two purposes: to improve my Clojure and ClojureScript knowledge and to help JavaScript developers learn Clojure.

The first library we will be transforming is titleize. We are starting with the simplest library I could think I ever used and maybe you used too.

Also because sindresorhus has the largest, high-quality, open-source codebase of reusable js/ts snippets and libraries, I've seen.

The recommendation here is not necessarily to use it in a project but to know the basics of Clojure code that can be used on both Clojure and ClojureScript environments. Maybe if you have a Clojure(Script) project you can install this library, but if you just want a titleize function in your JavaScript code, just npm install the original one.

The titleize function

Let's start by digging into the original code. This is the titleize function. It has no dependencies. A very simple
straightforward JavaScript regex shenanigans. Take a look:

export default function titleize(string) {
    if (typeof string !== 'string') {
        throw new TypeError('Expected a string');
    }

    return string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, x => x.toUpperCase());
}
Enter fullscreen mode Exit fullscreen mode

So the first thing to do is to create this function. I don't want to spend too much your time so let's just fire a REPL
and start doing stuff there. So run this:

$ clj

So now on the REPL, you can test some code. Run some sum example like (+ 1 2) and make sure you receive 3 and then we
can start.

String functions

So first let's require Clojure's string standard library with the following code.

(require [clojure.string :as string])
Enter fullscreen mode Exit fullscreen mode

In the original code, we can see the author using the toLowerCase and toUpperCase methods from JavaScript's string type. The equivalents in Clojure would be:

(string/lower-case "BEING DEV IS NICE") ; => being dev is nice
(string/upper-case "being dev is cool") ; => BEING DEV IS COOL
Enter fullscreen mode Exit fullscreen mode

Ok. We could also refer to the functions directly.

(require [clojure.string :refer [lower-case upper-case]])

(lower-case "BEING DEV IS NICE") ; => being dev is nice
(upper-case "being dev is cool") ; => BEING DEV IS COOL
Enter fullscreen mode Exit fullscreen mode

Then we can see on the original implementation that we also have a usage of replaceAll with some regex. The
equivalent will also be from clojure.string which is the replace function.

Regex

In Clojure, the regex syntax is a little bit different. Of course, it will depend on the application but for now, we will ignore the modifiers (we can see the /g on the titleize implementation) and just transform that to our beloved clojure syntax:

Original:

/(?:^|\s|-)\S/g
Enter fullscreen mode Exit fullscreen mode

Clojure:

#"(?:^|\s|-)\S"
Enter fullscreen mode Exit fullscreen mode

Not trusting me? Ok. Make sure you know by yourself then:

(type #"") ; => java.util.regex.Pattern
Enter fullscreen mode Exit fullscreen mode

Glueing everything together

Now let's write our function:

(require [clojure.string :refer [lower-case upper-case replace]])

(defn titleize [str]
    (replace (lower-case str) #"(?:^|\s|-)\S" upper-case))
Enter fullscreen mode Exit fullscreen mode

Done! Run the above on your REPL and then you can use it:

(titleize "the quick brown fox jumps over the lazy dog") ; => The Quick Brown Fox Jumps Over The Lazy Dog
Enter fullscreen mode Exit fullscreen mode

Let's refactor it a little

Our code is fine now. It works! But it still doesn't leverage one of the best things Clojure has: macros!

To make our code a little bit more readable and, I would say, better to maintain we will be using the thread first macro. Which can be simply described as:

The thread-first macro -> in Clojure allows you to write code in a more sequential and readable manner by threading the output of one function call into the first argument of the next function call. It simplifies nested function calls by enhancing code readability and reducing the need for intermediate variables. This macro assists in composing functions together in a natural left-to-right order, making the code easier to understand and maintain.

By applying that, our code will now look like this:

(defn titleize [str]
  (-> str
    (lower-case)
    (replace #"(?:^|\s|-)\S" upper-case)))
Enter fullscreen mode Exit fullscreen mode

So pretty! Right? Clojure has a bunch of useful macros.

Tests

The original library has a very simple test chain, comparing a bunch of strings that we have. The repo uses ava to run the tests. Take a look:

import test from 'ava';
import titleize from './index.js';

test('main', t => {
    t.is(titleize(''), '');
    t.is(titleize('unicorns and rainbows'), 'Unicorns And Rainbows');
    t.is(titleize('UNICORNS AND RAINBOWS'), 'Unicorns And Rainbows');
    t.is(titleize('unicorns-and-rainbows'), 'Unicorns-And-Rainbows');
    t.is(titleize('UNICORNS-AND-RAINBOWS'), 'Unicorns-And-Rainbows');
    t.is(titleize('unicorns   and rainbows'), 'Unicorns   And Rainbows');
});
Enter fullscreen mode Exit fullscreen mode

For Clojure we have the clojure.test library so we just need to copy the test and apply it on Clojure syntax:

(ns excelsia.titleize-test
  (:require [clojure.test :refer [deftest is testing]]
            [excelsia.titleize :as titleize]))

(def result-map {"" ""
                 "unicorns and rainbows" "Unicorns And Rainbows"
                 "UNICORNS AND RAINBOWS" "Unicorns And Rainbows"
                 "unicorns-and-rainbows" "Unicorns-And-Rainbows"
                 "UNICORNS-AND-RAINBOWS" "Unicorns-And-Rainbows"
                 "unicorns   and rainbows" "Unicorns   And Rainbows"})

(deftest titleize-test
  (testing "titleize"
    (doseq [[input expected] result-map]
      (is (= expected (titleize/titleize input))))))
Enter fullscreen mode Exit fullscreen mode

Now, I'm not going too deep on that, but you can see on the repository that we are running tests both on the Clojure environment and on the Node.js environment to make sure the code works properly both on Clojure and ClojureScript.

Summary

So we grabbed a very small JS library and ported it to Clojure library compatible with ClojureScript. Tell me your
feedbacks and other small libraries you may want to see converted to Clojure.

Code

https://github.com/excelsia-dev/titleize

clojurescript Article's
30 articles in total
Favicon
Querido Yo del Futuro: Hoy intentaremos configurar una aplicación fullstack en Clojure
Favicon
Why I chose Clojure/Script for building Vade Studio
Favicon
Converting JS Libraries to Clojure: Part 1
Favicon
Deploy your ClojureScript App to Cloudflare Workers
Favicon
shadow-cljs and running tests
Favicon
Giving new life to existing Om legacy SPAs with re-om
Favicon
Is Clojure the only language you need?
Favicon
Building an Application with ClojureScript
Favicon
How to Set up A Clojure Script and Phoenix Project
Favicon
How to create a library that works with Clojure and ClojureScript
Favicon
Setup shadow-cljs react project
Favicon
Logging readable ClojureScript (.cljs) errors to sentry!!
Favicon
How can I create a ClojureScript web app from scratch with Reagent and npm?
Favicon
Set up SSL/TLS for shadow-cljs https server
Favicon
ClojureScript on Cloudflare Workers
Favicon
Storybook.JS with Shadow-CLJS
Favicon
World Statistics Exercise
Favicon
The Pleasure of Clojure(Script): Part 1
Favicon
Using Specter on tree data structures in Clojure
Favicon
Clojure Re-Frame Exercise
Favicon
¿Por qué Clojure?
Favicon
Implementing the feed
Favicon
Try something new this week
Favicon
ClojureScript async MVU
Favicon
ClojureScript simple MVU loop
Favicon
Integrating ClojureScript with JavaScript tooling
Favicon
Understanding Transducers in JavaScript
Favicon
Casting visual spells with p5.js and ClojureScript, part 1
Favicon
ClojureScript REPL Workflow
Favicon
Developing, Testing and Deploying AWS Lambda Functions written in ClojureScript

Featured ones: