返回介绍

14. Generating Production Code — All Targets

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

Development mode always outputs individual files for each namespace so that they can be hot loaded in isolation. When you’re ready to deploy code to a real server you want to run the Closure Compiler on it to generate a single minified result for each module.

By default the release mode output file should just be a drop-in replacements for the development mode file: there is no difference in the way you include them in your HTML. You may use filename hashing to improve caching characteristics on browser targets.

Generating Minified Output
$ shadow-cljs release build-id

14.1. Release Configuration

Usually you won’t need to add any extra configuration to create a release version for your build. The default config already captures everything necessary and should only require extra configuration if you want to override the defaults.

Each :target already provides good defaults optimized for each platform so you’ll have less to worry about.

14.1.1. Optimizations

You can choose the optimization level using the :compiler-options section of the configuration:

ImportantYou do not usually need to set :optimizations since the :target already sets it to an appropriate level.
Important:optimizations only apply when using the release command. Development builds are never optimized by the Closure Compiler. Development builds are always set to :none.
{...
 :build
   {:build-id
     {...
      :compiler-options {:optimizations :simple}}}}

See the the Closure compiler’s documentation for more information on available optimization levels.

14.1.2. Release-Specific vs. Development Configuration

If you wish to have separate configuration values in a build when running a release build then you can override settings by including a :dev and/or :release section in the build section:

Example shadow-cljs.edn build config
{:source-paths ["src"]
 :dependencies []
 :builds
 {:app
  {:target :browser
   :output-dir "public/js"
   :asset-path "/js"
   :modules {:base {:entries [my.app.core]}}
   ;; Here is some dev-specific config
   :dev {:compiler-options {:devcards true}}
   ;; Here is some production config
   :release {:compiler-options {:optimizations :simple}}}}}

14.2. Externs

Since we want builds to be fully optimized by the Closure Compiler :advanced compilation we need to deal with Externs. Externs represent pieces of code that are not included when doing :advanced compilation. :advanced works by doing whole program optimizations but some code we just won’t be able to include so Externs inform the Compiler about this code. Without Externs the Compiler may rename or remove some code that it shouldn’t.

Typically all JS Dependencies are foreign and won’t be passed through :advanced and thus require Externs.

TipExterns are only required for :advanced, they are not required in :simple mode.

14.2.1. Externs Inference

To help deal with Externs the shadow-cljs compiler provides enhanced externs inference, which is enabled by default. The compiler will perform additional checks at compile time for your files only. It won’t warn you about possible externs issues in library code.

You’ll get warnings whenever the Compiler cannot figure out whether you are working with JS or CLJS code. If you don’t get any warnings you should be OK.

Example Code
(defn wrap-baz [x]
  (.baz x))
Example Warning
------ WARNING #1 --------------------------------------------------------------
 File: ~/project/src/demo/thing.cljs:23:3
--------------------------------------------------------------------------------
  21 |
  22 | (defn wrap-baz [x]
  23 |   (.baz x))
---------^----------------------------------------------------------------------
 Cannot infer target type in expression (. x baz)
--------------------------------------------------------------------------------

In :advanced the compiler might be renaming .baz to something "shorter" and Externs inform the Compiler that this is an external property that should not be renamed.

The warning tells you that the compiler did not recognize the property baz in the x binding. shadow-cljs can generate the appropriate externs if you add a typehint to the object you are performing native interop on.

Type-hint to help externs generation
(defn wrap-baz [x]
  (.baz ^js x))

The ^js typehint will cause the compiler to generate proper externs and the warning will go away. The property is now safe from renaming. You may either directly tag the interop form, or you may tag the variable name where it is first bound.

Multiple interop calls
(defn wrap-baz [x]
  (.foo ^js x)
  (.baz ^js x))

It can get tedious to annotate every single interop call, so you can annotate the variable binding itself. It will be used in the entire scope for this variable. Externs for both calls will still be generated. So, instead you do:

Annotate x directly
(defn wrap-baz [^js x]
  (.foo x)
  (.baz x))
ImportantDon’t annotate everything with ^js. Sometimes you may be doing interop on CLJS or ClosureJS objects. Those do not require externs. If you are certain you are working with a CLJS object use the ^clj hint instead. It is not the end of the world to use ^js incorrectly, but it may affect some optimizations when a variable is not renamed when it could be.

Calling a global using js/ does not require a typehint.

No hint required, externs inferred automatically
(js/Some.Thing.coolFunction)

Calls on :require bindings are also inferred automatically.

No hint required for :as and :refer bindings
(ns my.app
  (:require ["react" :as react :refer (createElement)]))
(react/createElement "div" nil "hello world")
(createElement "div" nil "hello world")

14.2.2. Manual Externs

Some libraries provide Externs as separate .js files. You can include them into your build via the :externs compiler options.

Manual Externs Config
{...
 :builds
 {:app
  {:target :browser
   ...
   :compiler-options {:externs ["path/to/externs.js" ...]}
   }}}
TipThe compiler looks for files relative to the project root first. It will also attempt to load them from the classpath if no file is found.

14.2.3. Simplified Externs

Writing Externs by hand can be challenging and shadow-cljs provides a way to write a more convenient way to write them. In combination with shadow-cljs check <your-build> you can quickly add the missing Externs.

Start by creating a externs/<your-build>.txt, so build :app would be externs/app.txt. In that file each line should be one word specifying a JS property that should not be renamed. Global variables should be prefixed by global:

Example externs/app.txt
# this is a comment
foo
bar
global:SomeGlobalVariable

In this example the compiler will stop renaming something.foo(), something.bar().

14.3. Build Report

shadow-cljs can generate a detailed report for your release builds which includes a detailed breakdown of the included sources and how much they each contributed to the overall size.

A sample report can be found here.

The report can either be generated by running a separate command or by configuring a build hook for your build.

Command Example
$ npx shadow-cljs run shadow.cljs.build-report <build-id> <path/to/output.html>
# example
$ npx shadow-cljs run shadow.cljs.build-report app report.html

The above example will generate a report.html in the project directory for the :app build.

TipThe generated HTML file is entirely self-contained and includes all the required data/js/css. No other external sources are required.
Build Hook Example
{...
 :builds
 {:app
  {:target :browser
   :output-dir "public/js"
   :modules ...
   :build-hooks
   [(shadow.cljs.build-report/hook)]
   }}}

This will generate a report.html in the configured public/js output directory for every release build automatically. This can be configured where this is written to by supplying an extra :output-to option. This path is then treated as relative to the project directory, not the :output-dir.

Build Hook with :output-to
{...
 :builds
 {:app
  {:target :browser
   :output-dir "public/js"
   :modules ...
   :build-hooks
   [(shadow.cljs.build-report/hook
      {:output-to "tmp/report.html"})]
   }}}

Only release builds will produce a report when using the hook, it does not affect watch or compile.

ImportantThe build report is generated by parsing the source maps, so the hook will automatically force the generation of source maps. The files won’t be linked from the .js files directly, unless you actually enabled them via :compiler-options {:source-map true} yourself.

The dedicated build report command runs separately from watches you may have running. You do not need to stop any of them nor do you need to stop the shadow-cljs server before building the report.

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

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

发布评论

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