返回介绍

10. Targeting node.js

发布于 2023-07-23 16:03:27 字数 9359 浏览 0 评论 0 收藏 0

There is built-in support for generating code that is intended to be used as a stand-alone script, and also for code that is intended to be used as a library. See the section on common configuration for the base settings needed in a configuration file.

10.1. node.js Scripts

The :target :node-script produces single-file stand-alone output that can be run using node.js. The code is just ClojureScript, and an entry point is easy to define:

(ns demo.script)
(defn main [& cli-args]
  (prn "hello world"))

10.1.1. Build Options

You will need the same basic main configuration as in other targets (like :source-paths), but you’ll need some node-specific build target options:

:main

(required). The namespace-qualified symbol of your script’s entry point function.

:output-to

(required). The path and filename for the generated script.

:output-dir

(optional). The path for supporting files in development mode. Defaults to a cache directory.

Sample node script build
{:source-paths [...]
 ...
 :builds
 {:script
  {:target :node-script
   :main demo.script/main
   :output-to "out/demo-script/script.js"}}}

When compiled this results in a standalone out/demo-script/script.js file intended to be called via node script.js <command line args>. When run it will call (demo.script/main <command line args>) function on startup. This only ever produces the file specified in :output-to. Any other support files (e.g. for development mode) are written to a temporary support directory.

10.1.2. Hot Code Reload

You will often write scripts that run as servers or some other long-running process. Hot code reload can be quite useful when working with these, and it is simple to set up:

  1. Add start/stop callback functions.

  2. Configure the build use those hooks.

Here is an example http server in node:

Sample node script with start/stop hooks for hot code reload.
(ns demo.script
  (:require ["http" :as http]))
(defn request-handler [req res]
  (.end res "foo"))
; a place to hang onto the server so we can stop/start it
(defonce server-ref
  (volatile! nil))
(defn main [& args]
  (js/console.log "starting server")
  (let [server (http/createServer #(request-handler %1 %2))]
    (.listen server 3000
      (fn [err]
        (if err
          (js/console.error "server start failed")
          (js/console.info "http server running"))
        ))
    (vreset! server-ref server)))
(defn start
  "Hook to start. Also used as a hook for hot code reload."
  []
  (js/console.warn "start called")
  (main))
(defn stop
  "Hot code reload hook to shut down resources so hot code reload can work"
  [done]
  (js/console.warn "stop called")
  (when-some [srv @server-ref]
    (.close srv
      (fn [err]
        (js/console.log "stop completed" err)
        (done)))))
(js/console.log "__filename" js/__filename)

The associated configuration is (shadow-cljs.edn):

Adding hooks for hot code reload.
{...
 :builds
   { :script {... as before
              ; add in reload hooks
              :devtools {:before-load-async demo.script/stop
                         :after-load demo.script/start}}}}
WarningMany libraries hide state or do actions that prevent hot code reloading from working well. There is nothing the compiler can do to improve this since it has no idea what those libraries are doing. Hot code reload will only work well in situations where you can cleanly "stop" and "restart" the artifacts used.

10.2. node.js Libraries

The :target :node-library emits code that can be used (via require) as a standard node library, and is useful for publishing your code for re-use as a compiled Javascript artifact.

As with other modes the main configuration options apply and must be set. The target-specific options are:

:target

Use :node-library

:output-to

(required). The path and filename for the generated library.

:output-dir

(optional). The path for supporting files in development mode. Defaults to a cache directory.

The hot code reload story is similar to the script target, but may not work as well since it cannot as easily control all of the code that is loaded.

Controlling what code is actually exported is done via one of the following options:

  • :exports - a map of keyword to fully qualified symbols

  • :exports-var - a fully qualified symbol

  • :exports-fn - a fully qualified symbol

10.2.1. Single static "default" export

:exports-var will just return whatever is declared under that var. It can point to a defn or normal def.

Build config using :exports-var
{...
 :builds {:lib {:output-to "lib.js"
                :exports-var demo.ns/f
                ...}}}
Example CLJS
(ns demo.ns)
(defn f [...] ...)
;; OR
(def f #js {:foo ...})
Consuming the generated code
$ node
> var f = require('./lib.js');
f(); // the actual demo.ns/f function

It is effectively generating module.exports = demo.ns.f;

10.2.2. Multiple static named exports

Build configuration with multiple exports
{...
 :builds {:lib {:exports {:g       demo.ns/f
                          :h       other.ns/thing
                          :ns/ok?  another.ns/ok?}
                ...}}}

The keyword is used as the name of the entry in the exported object. No munging is done to this keyword name (but namespaces are dropped). So, the above example maps cljs f to g, etc.:

$ node
> var lib = require('./lib.js');
lib.g(); // call demo-ns/f
lib["ok?"](); // call another-ns/ok?

You can achieve the exact same thing by using :exports-var pointing to a def

(def exports #js {:g f
                  ...})

10.2.3. "Dynamic" exports

In addition you may specify :exports-fn as a fully qualified symbol. This should point to a function with no arguments which should return a JS object (or function). This function will only ever be called ONCE as node caches the return value.

(ns demo.ns
  (:require [demo.other :as other]))
(defn generate-exports []
  #js {:hello hello
       :foo other/foo})
{...
 :builds {:lib {:exports-fn demo.ns/generate-exports
                ...}}}
NoteThe exports config automatically tracks exported symbols and passes them on to the optimization stage. This means that anything listed in :exports will not be renamed by Google Closure optimizations.

10.2.4. Full Example

The example below creates a lib.js file intended to be consumed via the normal Node require mechanism.

(ns demo.lib)
(defn hello []
  (prn "hello")
  "hello")

The build configuration would be:

{...
 :builds {:library {:target    :node-library
                    :output-to "out/demo-library/lib.js"
                    :exports   {:hello demo.lib/hello}}}}

and the runtime use is as you would expect:

$ cd out/demo-library
$ node
> var x = require('./lib');
undefined
> x.hello()
hello
'hello'

As :node-script this will only create the file specified in :output-to. The :exports map maps CLJS vars to the name they should be exported to.

NoteDevelopment mode has the same setup as for node scripts (extra dependencies).

10.3. Creating npm packages

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文