How to install Highlight.js to Rails 7 with importmap?

I created a simple Rails 7 application with Post, Comment models using Tailwindcss.

And I have a problem with importing the highlight.js library to render syntax code in Trix editor.

This is config/importmap.rb:

# Pin npm packages by running ./bin/importmap

pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "trix"
pin "@rails/actiontext", to: "actiontext.js"
pin "highlight.js", to: "https://ga.jspm.io/npm:[email protected]/es/index.js"

And javascrip/application.js

// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
import "controllers"
import "trix"
import "@rails/actiontext"

import "highlight.js"

import hljs from "highlight.js/lib/core"
import javascript from 'highlight.js/lib/languages/javascript'
import bash from 'highlight.js/lib/languages/bash'
import ruby from 'highlight.js/lib/languages/ruby'

hljs.registerLanguage('javascript', javascript)
hljs.registerLanguage('bash', bash)
hljs.registerLanguage('ruby', ruby)

document.addEventListener('turbo:load', (event) => {
    document.querySelectorAll('pre').forEach(function(preElement) {
        const languageRegex = /(?!lang\-\\w\*)lang-\w*\W*/gm
        const codeElement = document.createElement('code')

        let preElementTextNode = preElement.removeChild(preElement.firstChild)
        let language = preElementTextNode.textContent.match(languageRegex)

        if (language) {
            language = language[0].toString().trim()
            preElementTextNode.textContent = preElementTextNode.textContent.replace(language, '')
            codeElement.classList.add(language, 'line-numbers')
        }

        codeElement.append(preElementTextNode)
        preElement.append(codeElement)
    })

    document.querySelectorAll('pre code').forEach((el) => {
        hljs.highlightElement(el)
    })
})

The message error in a browser:

Uncaught Error: Unable to resolve specifier 'highlight.js/lib/core' from http://localhost:3000/assets/application-18666563d3b8c368b2de6f038dc38276f2eb21cb75d45593f9efd1e4200f55c4.js
    throwUnresolved es-module-shims.js:792
    d es-module-shims.js:655
    L es-module-shims.js:646
    promise callback*getOrCreateLoad es-module-shims.js:644
    d es-module-shims.js:659
    L es-module-shims.js:646
    promise callback*getOrCreateLoad es-module-shims.js:644
    topLevelLoad es-module-shims.js:393
    processScript es-module-shims.js:766
    processScriptsAndPreloads es-module-shims.js:668
    ze es-module-shims.js:373
    promise callback* es-module-shims.js:335
    <anonymous> es-module-shims.js:2

Am I wrong anything?

Thanks.


Solution 1:

It looks like you import highlight.js in your application.js then attempt to import it again from the pinned location which is not the pattern recommended in importmaps documentation.

Try either importing the entirety of highlight.js or just import the specific languages you want.

Try updating the imports on your application.js file and removing the language specific

import hljs from 'highlight.js';
//import hljs from "highlight.js/lib/core"
//import javascript from 'highlight.js/lib/languages/javascript'
//import bash from 'highlight.js/lib/languages/bash'
//import ruby from 'highlight.js/lib/languages/ruby'

or

import {javascript, ruby, bash} from 'highlight.js'

Solution 2:

according to the code from your pin url [email protected] index, you should use import HighlightJS, beside that, this index js file already import js, bash and ruby (and also ~ other 40 popular languages), so you no need to register by yourself.

pin "highlight.js", to: "https://ga.jspm.io/npm:[email protected]/es/index.js"
// application.js
import { HighlightJS } from "highlight.js"

document.addEventListener('turbo:load', (event) => {
  document.querySelectorAll('pre').forEach(function(preElement) {
    // your code
    // after extract the lang, for example: lang-ruby
    // you could highlight code as below
    HighlightJS.highlightElement(codeElement, language.split("-")[1])
  }
}
application.html/erb
<%= stylesheet_link_tag "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.4.0/styles/default.min.css" %>
<%= javascript_importmap_tags %>

Note: about the line-number, i tried highlightjs-line-numbers.js but it looks like this plugin just work with CommonJS (your index js file is es6).