Share your thoughts in the 2024 State of Clojure Survey!

Welcome! Please see the About page for a little more info on how this works.

0 votes
in ClojureScript by
I'm using ClojureScript with Figwheel and trying to use CraftyJs in ClojureScript.
This is my project.clj

(defproject my_project "0.1.0-SNAPSHOT"
  :description "FIXME: write this!"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}

  
  
  :min-lein-version "2.7.1"

  :dependencies [[org.clojure/clojure "1.9.0"]
                 [org.clojure/clojurescript "1.10.238"]
                 [org.clojure/core.async  "0.4.474"]]

  :plugins [[lein-figwheel "0.5.16"]
            [lein-cljsbuild "1.1.7" :exclusions [[org.clojure/clojure]]]]

  :source-paths ["src"]

  :cljsbuild {:builds
              [{:id "dev"
                :source-paths ["src"]

                ;; The presence of a :figwheel configuration here
                ;; will cause figwheel to inject the figwheel client
                ;; into your build
                :figwheel {:on-jsload "my_project.core/on-js-reload"
                           ;; :open-urls will pop open your application
                           ;; in the default browser once Figwheel has
                           ;; started and compiled your application.
                           ;; Comment this out once it no longer serves you.
                           :open-urls ["https://127.0.0.1:3449/index.html"]}

                :compiler {:main my_project.core
                           :asset-path "js/compiled/out"
                           :install-deps true
                           :npm-deps {:craftyjs "0.8.0"}
                           :output-to "resources/public/js/compiled/my_project.js"
                           :output-dir "resources/public/js/compiled/out"
                           :source-map-timestamp true
                           ;; To console.log CLJS data-structures make sure you enable devtools in Chrome
                           ;; https://github.com/binaryage/cljs-devtools
                           :preloads [devtools.preload]}}
               ;; This next build is a compressed minified build for
               ;; production. You can build this with:
               ;; lein cljsbuild once min
               {:id "min"
                :source-paths ["src"]
                :compiler {:output-to "resources/public/js/compiled/my_project.js"
                           :main my_project.core
                           :optimizations :advanced
                           :pretty-print false}}]}

  :figwheel {;; :http-server-root "public" ;; default and assumes "resources"
             ;; :server-port 3449 ;; default
             ;; :server-ip "127.0.0.1"

             :css-dirs ["resources/public/css"] ;; watch and update CSS

             ;; Start an nREPL server into the running figwheel process
             ;; :nrepl-port 7888

             ;; Server Ring Handler (optional)
             ;; if you want to embed a ring handler into the figwheel http-kit
             ;; server, this is for simple ring servers, if this

             ;; doesn't work for you just run your own server :) (see lein-ring)

             ;; :ring-handler hello_world.server/handler

             ;; To be able to open files in your editor from the heads up display
             ;; you will need to put a script on your path.
             ;; that script will have to take a file path and a line number
             ;; ie. in  ~/bin/myfile-opener
             ;; #! /bin/sh
             ;; emacsclient -n +$2 $1
             ;;
             ;; :open-file-command "myfile-opener"

             ;; if you are using emacsclient you can just use
             ;; :open-file-command "emacsclient"

             ;; if you want to disable the REPL
             ;; :repl false

             ;; to configure a different figwheel logfile path
             ;; :server-logfile "tmp/logs/figwheel-logfile.log"

             ;; to pipe all the output to the repl
             ;; :server-logfile false
             }


  ;; Setting up nREPL for Figwheel and ClojureScript dev
  ;; Please see:
  ;; https://github.com/bhauman/lein-figwheel/wiki/Using-the-Figwheel-REPL-within-NRepl
  :profiles {:dev {:dependencies [[binaryage/devtools "0.9.9"]
                                  [figwheel-sidecar "0.5.16"]
                                  [cider/piggieback "0.3.1"]]
                   ;; need to add dev source path here to get user.clj loaded
                   :source-paths ["src" "dev"]
                   ;; for CIDER
                   ;; :plugins [[cider/cider-nrepl "0.12.0"]]
                   :repl-options {:nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
                   ;; need to add the compliled assets to the :clean-targets
                   :clean-targets ^{:protect false} ["resources/public/js/compiled"
                                                     :target-path]}})


However when running lein figwheel i see following in the console:

Compiling build :dev to "resources/public/js/compiled/my_project.js" from ["src"]...
[eval]:85
            !id.startsWith(goog:);
                           ^^^^

SyntaxError: missing ) after argument list
    at createScript (vm.js:74:10)
    at Object.runInThisContext (vm.js:116:10)
    at Object.<anonymous> ([eval]-wrapper:6:22)
    at Module._compile (module.js:624:30)
    at evalScript (bootstrap_node.js:480:27)
    at startup (bootstrap_node.js:177:9)
    at bootstrap_node.js:626:3

Successfully compiled build :dev to "resources/public/js/compiled/my_project.js" in 19.529 seconds.
and i can't import the library from my ClojureScript, i also see this:

Uncaught Error: Undefined nameToPath for craftyjs
    at visitNode (base.js:1357)
    at Object.goog.writeScripts_ (base.js:1369)
    at Object.goog.require [as require_figwheel_backup_] (base.js:706)
    at index.html:14
I already tried to manually delete the compiled JS output folder

5 Answers

0 votes
by
_Comment made by: mfikes_

Hey Marty,

I think you can't directly use CraftyJS as an NPM dependency with ClojureScript. If you look on the CraftyJS website it shows additionally using Browserify.

Here is what happens if you try to use it as an NPM dep using minimal tooling:

{code:title=deps.edn}
{:deps {org.clojure/clojurescript {:mvn/version "1.10.339"}}}


{code:title=compiler-opts.edn}
{:npm-deps {:craftyjs "0.8.0"}
 :install-deps true}



$ clj -m cljs.main -co compiler-opts.edn -r
ClojureScript 1.10.339
cljs.user=> (require 'craftyjs)
events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: Can't resolve 'fs' in '/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/craftyjs/src/graphics'
    at onError (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/Resolver.js:61:15)
    at loggingCallbackWrapper (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/createInnerCallback.js:31:19)
    at runAfter (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/Resolver.js:158:4)
    at innerCallback (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/Resolver.js:146:3)
    at loggingCallbackWrapper (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/createInnerCallback.js:31:19)
    at next (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/tapable/lib/Tapable.js:252:11)
    at innerCallback (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/Resolver.js:144:11)
    at loggingCallbackWrapper (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/createInnerCallback.js:31:19)
    at next (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/tapable/lib/Tapable.js:249:35)
    at resolver.doResolve.createInnerCallback (/Users/mfikes/Desktop/CLJS-2792-npm-deps/node_modules/enhanced-resolve/lib/DescriptionFilePlugin.js:44:6)

Error: goog.require could not find: craftyjs
     (goog/base.js:711:20)
     require (clojure/browser/repl.cljs:226:33)
cljs.user=>


If you look at the CraftyJS code, it has a {{require('fs')}} call (which Browserify evidently helps sidestep.)

But, you can easy use CraftyJS as a foreign lib. To do so, set your compiler options instead to be:

{code:title=compiler-opts.edn}
{:foreign-libs [{:file "https://github.com/craftyjs/Crafty/releases/download/0.8.0/crafty.js"
                 :provides ["craftyjs"]}]}


Then, you can drive CraftyJS right from the REPL:


$ clj -m cljs.main -co compiler-opts.edn -r
ClojureScript 1.10.339
cljs.user=> (set! (.-innerHTML (.getElementById js/document "app")) "")
""
cljs.user=> (require 'craftyjs)

cljs.user=> (.init js/Crafty)
#object[Crafty]
cljs.user=> (def player (-> js/Crafty
(.e "2D, Canvas, Color, Fourway")
(.attr #js {:x 100 :y 100 :w 50 :h 50})
(.color "blue")
(.fourway 3)))
#'cljs.user/player
cljs.user=>


After the above, you can control the box using your arrow keys.
0 votes
by

Comment made by: martyglaubitz

Thanks for the tip! But are you sure that

:file "https://github.com/craftyjs/Crafty/releases/download/0.8.0/crafty.js"

is supposed to work? On my windows machine i can only pass a local file path there...

0 votes
by

Comment made by: mfikes

Hi Marty. Yes {{:file}} can be a URL. See https://script.clojure.org/reference/compiler-options#foreign-libs

0 votes
by
_Comment made by: timothypratley_

Is this something that could be supported in the future?

Doing `npm install craftyjs --save` creates node_modules/craftyjs/src/crafty.js which is suitable for being used as the foreign-lib (its the same as the file at the end of the url). So it seems in principle that it would be convenient to be able to say "Get me <x> from node, and treat dist/y as a foreign-lib.

Which makes me wonder what :npm-deps currently does... presumably it is using the source of the package rather than the dist. That makes sense to me for things that don't use browserify (or intermediary build tools). But there are useful packages that do use browserify and these leave us to relying on a URL to a file or some manual steps to get the package and build it first. Would it be possible to specify a dependency that should be fetched, built and use a dist file instead of src? The benefit would be that we could use both styles of npm dependencies via the same mechanism.

It might need to have a different name like :npm-libs because I think it would need more than just a version... ie something like :npm-libs {"asciidoctor.js" {:version "1.5.9", :lib "dist/browser/asciidoctor.js"}}

I arrived at this thread trying to use asciidoctor.js which uses Browserify to build node_modules/asciidoctor.js/dist/browser/asciidoctor.js. The asciidoctor.js package is interesting because it targets both NodeJS and the browser, producing two output files [which also interestingly still require other stuff, but the browser doesn't require things like `fs`].
0 votes
by
Reference: https://clojure.atlassian.net/browse/CLJS-2792 (reported by alex+import)
...