Rails 6 + webpacker + yarn + Fancytree + LESS 

We had to migrate a gem from using fancytree-rails as a ruby gem to a new rails 6 gem using webpacker and jquery.fancytree coming from npm. On top of that jquery.fancytree is using LESS (CSS) and you have to do a few configurations.

App is available at https://github.com/thebravoman/rails6_webpacker_fancytree_less

Here is how to do it it a few simple commands

Table of contents

Create a new rails project

We want to have Fancrytree in this project.

   $ rails new project_with_less_and_fancytree
   $ cd project_with_less_and_fancytree
   $ rails g scaffold books
   $ rails db:migrate
 

Add fancytree yarn package

$ yarn add jquery.fancytree
   yarn add v1.22.4
   [1/4] Resolving packages...
   [2/4] Fetching packages...
   info fsevents@1.2.12: The platform "linux" is incompatible with this module.
   info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.
   [3/4] Linking dependencies...
   warning " > webpack-dev-server@3.10.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
   warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
   warning " > jquery.fancytree@2.35.0" has unmet peer dependency "jquery@>=1.9".
   [4/4] Building fresh packages...
   success Saved lockfile.
   success Saved 1 new dependency.
   info Direct dependencies
   └─ jquery.fancytree@2.35.0
   info All dependencies
   └─ jquery.fancytree@2.35.0
   Done in 3.29s.

I like yarn.

Add less and less-loader

Later to include fancytree we would have to do things like

import 'jquery.fancytree/dist/skin-lion/ui.fancytree.less'

This means fancytree uses LESS. So we need to process this .less files. Oh, css, oh you evil you.

yarn add less

$ yarn add less
   yarn add v1.22.4
   [1/4] Resolving packages...
   warning less > request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142
   [2/4] Fetching packages...
   info fsevents@1.2.12: The platform "linux" is incompatible with this module.
   info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.
   [3/4] Linking dependencies...
   warning " > jquery.fancytree@2.35.0" has unmet peer dependency "jquery@>=1.9".
   warning " > less-loader@5.0.0" has unmet peer dependency "webpack@^2.0.0 || ^3.0.0 || ^4.0.0".
   warning " > webpack-dev-server@3.10.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
   warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
   [4/4] Building fresh packages...
   success Saved lockfile.
   success Saved 4 new dependencies.
   info Direct dependencies
   └─ less@3.11.1
   info All dependencies
   ├─ asap@2.0.6
   ├─ image-size@0.5.5
   ├─ less@3.11.1
   └─ promise@7.3.1
   Done in 5.80s.

yarn add less-loader

You need less and less-loader

$ yarn add less-loader
   yarn add v1.22.4
   [1/4] Resolving packages...
   [2/4] Fetching packages...
   info fsevents@1.2.12: The platform "linux" is incompatible with this module.
   info "fsevents@1.2.12" is an optional dependency and failed compatibility check. Excluding it from installation.
   [3/4] Linking dependencies...
   warning " > jquery.fancytree@2.35.0" has unmet peer dependency "jquery@>=1.9".
   warning " > webpack-dev-server@3.10.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0".
   warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0".
   warning " > less-loader@5.0.0" has unmet peer dependency "less@^2.3.1 || ^3.0.0".
   warning " > less-loader@5.0.0" has unmet peer dependency "webpack@^2.0.0 || ^3.0.0 || ^4.0.0".
   [4/4] Building fresh packages...
   success Saved lockfile.
   success Saved 2 new dependencies.
   info Direct dependencies
   └─ less-loader@5.0.0
   info All dependencies
   ├─ clone@2.1.2
   └─ less-loader@5.0.0
   Done in 3.26s.
 

Add less-loader to webpack environment

They must be registered. Probably in another file, but here in enrovonments.js is fine this tutorial.

// config/webpack/environments.js

const { environment } = require('@rails/webpacker')

// THIS IS THE NEW CODE
const less_loader= {
  test: /\.less$/,
  use: ['css-loader', 'less-loader']
};
environment.loaders.append('less', less_loader)
// END: THIS IS THE NEW CODE

module.exports = environment

Use fancytree

Check out the documentation at https://github.com/mar10/fancytree/wiki#use-a-module-loader

But basically you must require fancytree and use it.

// NOTE: This seems to be working
// app/javascripts/packs/application.js

//... some other code.

// THIS IS THE NEW CODE ADDED AT THE BOTTOM OF application.js
// Import LESS or CSS:
import 'jquery.fancytree/dist/skin-lion/ui.fancytree.less'

const $ = require('jquery');

const fancytree = require('jquery.fancytree');
require('jquery.fancytree/dist/modules/jquery.fancytree.edit');
require('jquery.fancytree/dist/modules/jquery.fancytree.filter');

console.log(fancytree.version);

$(function(){
  $('#tree').fancytree({
    extensions: ['edit', 'filter'],
    source: [
      {title: "Node 1", key: "1"},
      {title: "Folder 2", key: "2", folder: true, children: [
        {title: "Node 2.1", key: "3"},
        {title: "Node 2.2", key: "4"}
      ]}
    ],
  });
  const tree = fancytree.getTree('#tree');
  // Note: Loading and initialization may be asynchronous, so the nodes may not be accessible yet.
})
// END: THIS IS THE NEW CODE ADDED AT THE BOTTOM OF application.js

NOTE – import is kind of not working

There is another configuration at https://github.com/mar10/fancytree/wiki#use-a-module-loader but I could not make it work

// NOTE: This is not working
import 'jquery.fancytree/dist/skin-lion/ui.fancytree.less';  // CSS or LESS
import {createTree} from 'jquery.fancytree';
import 'jquery.fancytree/dist/modules/jquery.fancytree.edit';
import 'jquery.fancytree/dist/modules/jquery.fancytree.filter';

const tree = createTree('#tree', {
  extensions: ['edit', 'filter'],
  source: [
      {title: "Node 1", key: "1"},
      {title: "Folder 2", key: "2", folder: true, children: [
        {title: "Node 2.1", key: "3"},
        {title: "Node 2.2", key: "4"}
      ]}
    ],
});
// Note: Loading and initialization may be asynchronous, so the nodes may not be accessible yet.

Start the application

$ rails s
=> Booting Puma
=> Rails 6.0.2.2 application starting in development 
=> Run `rails server --help` for more startup options
Puma starting in single mode...
* Version 4.3.3 (ruby 2.6.5-p114), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop
Started GET "/" for ::1 at 2020-04-22 09:20:06 +0300
   (0.4ms)  SELECT sqlite_version(*)
   (0.2ms)  SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Processing by Rails::WelcomeController#index as HTML
  Rendering /home/user/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.2/lib/rails/templates/rails/welcome/index.html.erb
  Rendered /home/user/.rvm/gems/ruby-2.6.5/gems/railties-6.0.2.2/lib/rails/templates/rails/welcome/index.html.erb (Duration: 17.4ms | Allocations: 471)
Completed 200 OK in 45ms (Views: 25.4ms | ActiveRecord: 0.0ms | Allocations: 2931)


Started GET "/books" for ::1 at 2020-04-22 09:20:09 +0300
Processing by BooksController#index as HTML
  Rendering books/index.html.erb within layouts/application
  Book Load (0.2ms)  SELECT "books".* FROM "books"
  ↳ app/views/books/index.html.erb:13
  Rendered books/index.html.erb within layouts/application (Duration: 29.0ms | Allocations: 1230)
[Webpacker] Compiling...
[Webpacker] Compiled all packs in /home/user/axles/tmp/project_with_less_and_fancytree/public/packs
[Webpacker] Hash: 32e57f147dbdcbbf0c82
Version: webpack 4.43.0
Time: 2269ms
Built at: 04/22/2020 9:20:13 AM
                                     Asset       Size       Chunks                         Chunk Names
    js/application-bbe9c4a129ab949e0636.js    124 KiB  application  [emitted] [immutable]  application
js/application-bbe9c4a129ab949e0636.js.map    139 KiB  application  [emitted] [dev]        application
                             manifest.json  364 bytes               [emitted]              
Entrypoint application = js/application-bbe9c4a129ab949e0636.js js/application-bbe9c4a129ab949e0636.js.map
[./app/javascript/channels sync recursive _channel\.js$] ./app/javascript/channels sync _channel\.js$ 160 bytes {application} [built]
[./app/javascript/channels/index.js] 211 bytes {application} [built]
[./app/javascript/packs/application.js] 749 bytes {application} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 552 bytes {application} [built]
    + 3 hidden modules

Completed 200 OK in 3998ms (Views: 3994.1ms | ActiveRecord: 0.7ms | Allocations: 23364)

Open /books

Visit http://localhost:3000/books. You should see no books

Started GET "/books" for ::1 at 2020-04-22 09:23:56 +0300
Processing by BooksController#index as HTML
  Rendering books/index.html.erb within layouts/application
  Book Load (0.2ms)  SELECT "books".* FROM "books"
  ↳ app/views/books/index.html.erb:13
  Rendered books/index.html.erb within layouts/application (Duration: 1.7ms | Allocations: 633)
[Webpacker] Compiling...
[Webpacker] Compilation failed:
Hash: 60e4cd172f04061a66be
Version: webpack 4.43.0
Time: 4365ms
Built at: 04/22/2020 9:24:02 AM
                                     Asset       Size       Chunks                         Chunk Names
    js/application-6ffd14b1620a1ad7ff96.js    717 KiB  application  [emitted] [immutable]  application
js/application-6ffd14b1620a1ad7ff96.js.map    841 KiB  application  [emitted] [dev]        application
                             manifest.json  364 bytes               [emitted]              
Entrypoint application = js/application-6ffd14b1620a1ad7ff96.js js/application-6ffd14b1620a1ad7ff96.js.map
[./app/javascript/channels sync recursive _channel\.js$] ./app/javascript/channels sync _channel\.js$ 160 bytes {application} [built]
[./app/javascript/channels/index.js] 211 bytes {application} [built]
[./app/javascript/packs/application.js] 1.52 KiB {application} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 552 bytes {application} [built]
    + 9 hidden modules

Change books.html.erb

Add a div element with id=tree

Books

<%# app/vies/books/index.html.erb %>
<p id="notice"><%= notice %></p>

<!-- THIS HERE IS WHAT WE ARE ADDING -->

<div id="tree"></div>

<!-- END: THIS HERE IS WHAT WE ARE ADDING -->

<h1>Books</h1>

<table>
  <thead>
    <tr>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @books.each do |book| %>
      <tr>
        <td><%= link_to 'Show', book %></td>
        <td><%= link_to 'Edit', edit_book_path(book) %></td>
        <td><%= link_to 'Destroy', book, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

Final picture

Showing how books index works with fancytree

Errors that might occur

No less-loader

If no less loader is available the following could occur.

Started GET "/books" for ::1 at 2020-04-22 08:52:40 +0300
   (0.1ms)  SELECT sqlite_version(*)
Processing by BooksController#index as HTML
  Rendering books/index.html.erb within layouts/application
  Book Load (0.2ms)  SELECT "books".* FROM "books"
  ↳ app/views/books/index.html.erb:13
  Rendered books/index.html.erb within layouts/application (Duration: 2.1ms | Allocations: 762)
[Webpacker] Compiling...
[Webpacker] Compilation failed:
Hash: 6210a48eff6aa0097a4c
Version: webpack 4.43.0
Time: 1464ms
Built at: 04/22/2020 8:52:43 AM
                                     Asset       Size       Chunks                         Chunk Names
    js/application-8dcd2b9e8cc222d43650.js    718 KiB  application  [emitted] [immutable]  application
js/application-8dcd2b9e8cc222d43650.js.map    841 KiB  application  [emitted] [dev]        application
                             manifest.json  364 bytes               [emitted]              
Entrypoint application = js/application-8dcd2b9e8cc222d43650.js js/application-8dcd2b9e8cc222d43650.js.map
[./app/javascript/channels sync recursive _channel\.js$] ./app/javascript/channels sync _channel\.js$ 160 bytes {application} [built]
[./app/javascript/channels/index.js] 211 bytes {application} [built]
[./app/javascript/packs/application.js] 1.07 KiB {application} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 552 bytes {application} [built]
    + 9 hidden modules

ERROR in ./node_modules/jquery.fancytree/dist/skin-lion/ui.fancytree.less 28:0
Module parse failed: Unexpected token (28:0)
File was processed with these loaders:
 * ./node_modules/less-loader/dist/cjs.js
You may need an additional loader to handle the result of these loaders.
|  * Helpers
|  *----------------------------------------------------------------------------*/
> .fancytree-helper-hidden {
|   display: none;
| }
 @ ./app/javascript/packs/application.js 19:0-59

no less available

If less was not installed this would happen

Started GET "/books" for ::1 at 2020-04-22 09:26:54 +0300
Processing by BooksController#index as HTML
  Rendering books/index.html.erb within layouts/application
  Book Load (0.1ms)  SELECT "books".* FROM "books"
  ↳ app/views/books/index.html.erb:13
  Rendered books/index.html.erb within layouts/application (Duration: 2.1ms | Allocations: 617)
[Webpacker] Compiling...
[Webpacker] Compilation failed:
Hash: 1adef07918f113c9c28e
Version: webpack 4.43.0
Time: 1380ms
Built at: 04/22/2020 9:26:56 AM
                                     Asset       Size       Chunks                         Chunk Names
    js/application-b032c274e5b1d8d383da.js    721 KiB  application  [emitted] [immutable]  application
js/application-b032c274e5b1d8d383da.js.map    841 KiB  application  [emitted] [dev]        application
                             manifest.json  364 bytes               [emitted]              
Entrypoint application = js/application-b032c274e5b1d8d383da.js js/application-b032c274e5b1d8d383da.js.map
[./app/javascript/channels sync recursive _channel\.js$] ./app/javascript/channels sync _channel\.js$ 160 bytes {application} [built]
[./app/javascript/channels/index.js] 211 bytes {application} [built]
[./app/javascript/packs/application.js] 1.52 KiB {application} [built]
[./node_modules/webpack/buildin/module.js] (webpack)/buildin/module.js 552 bytes {application} [built]
    + 9 hidden modules

ERROR in ./node_modules/jquery.fancytree/dist/skin-lion/ui.fancytree.less
Module build failed (from ./node_modules/less-loader/dist/cjs.js):
Error: Cannot find module 'less'
Require stack:
- /home/kireto/axles/tmp/pesho2/node_modules/less-loader/dist/index.js
- /home/kireto/axles/tmp/pesho2/node_modules/less-loader/dist/cjs.js
- /home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/loadLoader.js
- /home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/LoaderRunner.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/NormalModule.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/NormalModuleFactory.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/Compiler.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/webpack.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack-cli/bin/utils/validate-options.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack-cli/bin/utils/convert-argv.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack-cli/bin/cli.js
- /home/kireto/axles/tmp/pesho2/node_modules/webpack/bin/webpack.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:982:15)
    at Function.Module._load (internal/modules/cjs/loader.js:864:27)
    at Module.require (internal/modules/cjs/loader.js:1044:19)
    at require (/home/kireto/axles/tmp/pesho2/node_modules/v8-compile-cache/v8-compile-cache.js:161:20)
    at Object.<anonymous> (/home/kireto/axles/tmp/pesho2/node_modules/less-loader/dist/index.js:8:36)
    at Module._compile (/home/kireto/axles/tmp/pesho2/node_modules/v8-compile-cache/v8-compile-cache.js:192:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Module.require (internal/modules/cjs/loader.js:1044:19)
    at require (/home/kireto/axles/tmp/pesho2/node_modules/v8-compile-cache/v8-compile-cache.js:161:20)
    at Object.<anonymous> (/home/kireto/axles/tmp/pesho2/node_modules/less-loader/dist/cjs.js:3:18)
    at Module._compile (/home/kireto/axles/tmp/pesho2/node_modules/v8-compile-cache/v8-compile-cache.js:192:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
    at Module.load (internal/modules/cjs/loader.js:1002:32)
    at Function.Module._load (internal/modules/cjs/loader.js:901:14)
    at Module.require (internal/modules/cjs/loader.js:1044:19)
    at require (/home/kireto/axles/tmp/pesho2/node_modules/v8-compile-cache/v8-compile-cache.js:161:20)
    at loadLoader (/home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/loadLoader.js:18:17)
    at iteratePitchingLoaders (/home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/LoaderRunner.js:169:2)
    at iteratePitchingLoaders (/home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/LoaderRunner.js:165:10)
    at /home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/LoaderRunner.js:176:18
    at loadLoader (/home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/loadLoader.js:47:3)
    at iteratePitchingLoaders (/home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/LoaderRunner.js:169:2)
    at runLoaders (/home/kireto/axles/tmp/pesho2/node_modules/loader-runner/lib/LoaderRunner.js:365:2)
    at NormalModule.doBuild (/home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/NormalModule.js:295:3)
    at NormalModule.build (/home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/NormalModule.js:446:15)
    at Compilation.buildModule (/home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/Compilation.js:739:10)
    at /home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/Compilation.js:981:14
    at /home/kireto/axles/tmp/pesho2/node_modules/webpack/lib/NormalModuleFactory.js:409:6
 @ ./app/javascript/packs/application.js 20:0-59

Completed 200 OK in 2801ms (Views: 2800.1ms | ActiveRecord: 0.1ms | Allocations: 5363)