- 1. Introduction
- 2. Installation
- 3. Usage
- 4. REPL
- 5. Configuration
- 6. Build Configuration
- 7. Targeting the Browser
- 8. Targeting JavaScript Modules
- 9. Targeting React Native
- 10. Targeting node.js
- 11. Embedding in the JS Ecosystem The :npm-module Target
- 12. Testing
- 13. JavaScript Integration
- 14. Generating Production Code All Targets
- 15. Editor Integration
- 16. Troubleshooting
- 17. Publishing Libraries
- 18. What to do when things don’t work?
- 19. Hacking
14. Generating Production Code — All Targets
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:
Important | You 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:
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.
Tip | Externs 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.
(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.
(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:
Annotatex
directly(defn wrap-baz [^js x]
(.foo x)
(.baz x))
Important | Don’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.
(js/Some.Thing.coolFunction)
Calls on :require
bindings are also inferred automatically.
: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.
{...
:builds
{:app
{:target :browser
...
:compiler-options {:externs ["path/to/externs.js" ...]}
}}}
Tip | The 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:
# 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.
Tip | The generated HTML file is entirely self-contained and includes all the required data/js/css. No other external sources are required. |
{...
: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
.
{...
: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
.
Important | The 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论