clementine 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +4 -0
  3. data/README.md +52 -0
  4. data/Rakefile +1 -0
  5. data/clementine.gemspec +23 -0
  6. data/lib/clementine.rb +27 -0
  7. data/lib/clementine/clementine_rails.rb +8 -0
  8. data/lib/clementine/clojurescript_engine.rb +49 -0
  9. data/lib/clementine/clojurescript_engine_mri.rb +65 -0
  10. data/lib/clementine/clojurescript_template.rb +21 -0
  11. data/lib/clementine/options.rb +9 -0
  12. data/lib/clementine/version.rb +3 -0
  13. data/test/clojurescript_engine_test.rb +46 -0
  14. data/test/options_test.rb +22 -0
  15. data/vendor/assets/bin/cljsc.clj +21 -0
  16. data/vendor/assets/lib/clojure.jar +0 -0
  17. data/vendor/assets/lib/compiler.jar +0 -0
  18. data/vendor/assets/lib/goog.jar +0 -0
  19. data/vendor/assets/lib/js.jar +0 -0
  20. data/vendor/assets/src/clj/cljs/closure.clj +823 -0
  21. data/vendor/assets/src/clj/cljs/compiler.clj +1341 -0
  22. data/vendor/assets/src/clj/cljs/core.clj +702 -0
  23. data/vendor/assets/src/clj/cljs/repl.clj +162 -0
  24. data/vendor/assets/src/clj/cljs/repl/browser.clj +341 -0
  25. data/vendor/assets/src/clj/cljs/repl/rhino.clj +170 -0
  26. data/vendor/assets/src/cljs/cljs/core.cljs +3330 -0
  27. data/vendor/assets/src/cljs/cljs/nodejs.cljs +11 -0
  28. data/vendor/assets/src/cljs/cljs/nodejs_externs.js +2 -0
  29. data/vendor/assets/src/cljs/cljs/nodejscli.cljs +9 -0
  30. data/vendor/assets/src/cljs/cljs/reader.cljs +360 -0
  31. data/vendor/assets/src/cljs/clojure/browser/dom.cljs +106 -0
  32. data/vendor/assets/src/cljs/clojure/browser/event.cljs +100 -0
  33. data/vendor/assets/src/cljs/clojure/browser/net.cljs +182 -0
  34. data/vendor/assets/src/cljs/clojure/browser/repl.cljs +109 -0
  35. data/vendor/assets/src/cljs/clojure/set.cljs +162 -0
  36. data/vendor/assets/src/cljs/clojure/string.cljs +160 -0
  37. data/vendor/assets/src/cljs/clojure/walk.cljs +94 -0
  38. data/vendor/assets/src/cljs/clojure/zip.cljs +291 -0
  39. metadata +103 -0
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ .idea
4
+ Gemfile.lock
5
+ pkg/*
6
+ out/*
7
+ tmp/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in clementine.gemspec
4
+ gemspec
@@ -0,0 +1,52 @@
1
+ Clementine
2
+ ====
3
+
4
+ * https://github.com/yokolet/clementine
5
+ * http://yokolet.blogspot.com/2011/11/clojurescript-on-rails-asset-pipeline.html
6
+ * http://yokolet.blogspot.com/2011/11/tilt-template-for-clojurescript.html
7
+
8
+ Description
9
+ -----------
10
+
11
+ Clementine is a gem to use ClojureScript (https://github.com/clojure/clojurescript) from Ruby.
12
+ Clementine is a Tilt (https://github.com/rtomayko/tilt) Template, which is available to use
13
+ on Rails asset pipeline. Also, it is avilable to use in a Tilt way.
14
+
15
+ Clementine runs on Rails 3.1 and later.
16
+
17
+ Clementine supports JRuby and CRuby. When you use from CRuby, make sure java command is on your PATH.
18
+
19
+ Installation
20
+ -----------
21
+
22
+ Clone https://github.com/yokolet/clementine, then
23
+ edit your Gemfile with specific path to Clemetine.
24
+
25
+ For example:
26
+
27
+ ```ruby
28
+ gem 'clementine', :path => "/Users/yoko/Projects/clementine"
29
+ ```
30
+
31
+ Configuration
32
+ -----------
33
+
34
+ Create clementine.rb file in your ${Rails.root}/config/initializer directory.
35
+
36
+ Examples:
37
+
38
+ ```ruby
39
+ Clementine.options[:optimizations] = :simple
40
+ Clementine.options[:output_dir] = "assets/javascripts"
41
+ ```
42
+
43
+ Available options:
44
+
45
+ ```
46
+ KEY VALUES
47
+ ------------------ -----------------------
48
+ :optimazation :simple,:whitespace,:advanced
49
+ :target :nodejs
50
+ :output_dir directory name (:output_dir will be converted to ":output-dir")
51
+ :output_to file name (:output_to will be converted to ":output-to")
52
+ ```
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "clementine/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "clementine"
7
+ s.version = Clementine::VERSION
8
+ s.authors = ["Yoko Harada"]
9
+ s.email = ["yokolet@gmail.com"]
10
+ s.homepage = "https://github.com/yokolet/clementine"
11
+ s.summary = %q{clojurescript tilt template gem}
12
+ s.description = %q{clojurescript tilt template gem and available to use on Rails asset pipeline.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ # specify any dependencies here; for example:
20
+ # s.add_development_dependency "rspec"
21
+ # s.add_runtime_dependency "rest-client"
22
+ s.add_dependency "tilt"
23
+ end
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require "clementine/version"
3
+ require 'clementine/options'
4
+
5
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
6
+ require "java"
7
+
8
+ CLOJURESCRIPT_HOME = File.dirname(__FILE__) + "/../vendor/assets"
9
+ $: << CLOJURESCRIPT_HOME + "/lib"
10
+ require 'clojure'
11
+
12
+ %w{compiler.jar goog.jar js.jar}.each {|name| $CLASSPATH << CLOJURESCRIPT_HOME + "/lib/" + name}
13
+ %w{clj cljs}.each {|path| $CLASSPATH << CLOJURESCRIPT_HOME + "/src/" + path}
14
+
15
+ require "clementine/clojurescript_engine"
16
+ require "clementine/clojurescript_template"
17
+ require "clementine/clementine_rails" if defined?(Rails)
18
+ end
19
+ if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby"
20
+ CLOJURESCRIPT_HOME = File.dirname(__FILE__) + "/../vendor/assets"
21
+ CLASSPATH = []
22
+ %w{clojure.jar compiler.jar goog.jar js.jar}.each {|name| CLASSPATH << CLOJURESCRIPT_HOME + "/lib/" + name}
23
+ %w{clj cljs}.each {|path| CLASSPATH << CLOJURESCRIPT_HOME + "/src/" + path}
24
+ require "clementine/clojurescript_engine_mri"
25
+ require "clementine/clojurescript_template"
26
+ require "clementine/clementine_rails" if defined?(Rails)
27
+ end
@@ -0,0 +1,8 @@
1
+ module Clementine
2
+ class ClementineRails < Rails::Engine
3
+ initializer :register_clojurescript do |app|
4
+ app.assets.register_engine '.cljs', ClojureScriptTemplate
5
+ app.assets.register_engine '.clj', ClojureScriptTemplate
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,49 @@
1
+ %w{RT Keyword PersistentHashMap}.each do |name|
2
+ java_import "clojure.lang.#{name}"
3
+ end
4
+
5
+ module Clementine
6
+
7
+ class ClojureScriptEngine
8
+ def initialize(file, options)
9
+ @file = file
10
+ @options = options
11
+ end
12
+
13
+ def compile
14
+ @options = Clementine.options if @options.empty?
15
+ cl_opts = PersistentHashMap.create(convert_options(@options))
16
+ RT.loadResourceScript("cljs/closure.clj")
17
+ builder = RT.var("cljs.closure", "build")
18
+ builder.invoke(@file, cl_opts)
19
+ end
20
+
21
+ #private
22
+ def convert_options(options)
23
+ opts = {}
24
+ options = options.empty? ? default_opts : options
25
+ options.each do |k, v|
26
+ cl_key = Keyword.intern(Clementine.ruby2clj(k.to_s))
27
+ case
28
+ when (v.kind_of? Symbol)
29
+ cl_value = Keyword.intern(Clementine.ruby2clj(v.to_s))
30
+ else
31
+ cl_value = v
32
+ end
33
+ opts[cl_key] = cl_value
34
+ end
35
+ opts
36
+ end
37
+
38
+ def default_opts
39
+ key = "output_dir"
40
+ value = ""
41
+ if defined?(Rails)
42
+ value = File.join(Rails.root, "app", "assets", "javascripts", "clementine")
43
+ else
44
+ value = Dir.pwd
45
+ end
46
+ {key => value}
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,65 @@
1
+ module Clementine
2
+
3
+ class Error < StandardError; end
4
+
5
+ class ClojureScriptEngine
6
+ def initialize(file, options)
7
+ @file = file
8
+ @options = options
9
+ @classpath = CLASSPATH
10
+ end
11
+
12
+ def compile
13
+ @options = Clementine.options if @options.empty?
14
+ begin
15
+ cmd = "#{command} #{@file} #{convert_options(@options)} 2>&1"
16
+ result = `#{cmd}`
17
+ rescue Exception
18
+ raise Error, "compression failed: #{result}"
19
+ end
20
+ unless $?.exitstatus.zero?
21
+ raise Error, result
22
+ end
23
+ result
24
+ end
25
+
26
+ def nailgun_prefix
27
+ server_address = Nailgun::NailgunConfig.options[:server_address]
28
+ port_no = Nailgun::NailgunConfig.options[:port_no]
29
+ "#{Nailgun::NgCommand::NGPATH} --nailgun-port #{port_no} --nailgun-server #{server_address}"
30
+ end
31
+
32
+ def setup_classpath_for_ng
33
+ current_cp = `#{nailgun_prefix} ng-cp`
34
+ unless current_cp.include? "clojure.jar"
35
+ puts "Initializing nailgun classpath, required clementine dependencies missing"
36
+ `#{nailgun_prefix} ng-cp #{@classpath.join " "}`
37
+ end
38
+ end
39
+
40
+ def command
41
+ if defined? Nailgun
42
+ setup_classpath_for_ng
43
+ [nailgun_prefix, 'clojure.main', "#{CLOJURESCRIPT_HOME}/bin/cljsc.clj"].flatten.join(' ')
44
+ else
45
+ ["java", '-cp', "\"#{@classpath.join ":"}\"", 'clojure.main', "#{CLOJURESCRIPT_HOME}/bin/cljsc.clj"].flatten.join(' ')
46
+ end
47
+ end
48
+
49
+ private
50
+ def convert_options(options)
51
+ opts = ""
52
+ options.each do |k, v|
53
+ cl_key = ":" + Clementine.ruby2clj(k.to_s)
54
+ case
55
+ when (v.kind_of? Symbol)
56
+ cl_value = ":" + Clementine.ruby2clj(v.to_s)
57
+ else
58
+ cl_value = "\"" + v + "\""
59
+ end
60
+ opts += cl_key + " " + cl_value + " "
61
+ end
62
+ opts.chop!
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ require 'tilt/template'
2
+
3
+ module Clementine
4
+ class ClojureScriptTemplate < Tilt::Template
5
+ self.default_mime_type = 'application/javascript'
6
+
7
+ def self.engine_initialized?
8
+ true
9
+ end
10
+
11
+ def initialize_engine; end
12
+
13
+ def prepare
14
+ @engine = ClojureScriptEngine.new(@file, options)
15
+ end
16
+
17
+ def evaluate(scope, locals, &block)
18
+ @output ||= @engine.compile
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ module Clementine
2
+ extend self
3
+ @options = {}
4
+ attr_accessor :options
5
+
6
+ def ruby2clj(key)
7
+ key.sub(/_/, '-')
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Clementine
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,46 @@
1
+ require "test/unit"
2
+
3
+ class ClojureScriptEngineTest < Test::Unit::TestCase
4
+
5
+ # Called before every test method runs. Can be used
6
+ # to set up fixture information.
7
+ def setup
8
+ require "#{File.join(File.dirname(__FILE__), "..", "lib", "clementine")}"
9
+ require "#{File.join(File.dirname(__FILE__), "..", "lib", "clementine", "clojurescript_engine")}"
10
+ end
11
+
12
+ # Called after every test method runs. Can be used to tear
13
+ # down fixture information.
14
+
15
+ def teardown
16
+ # Do nothing
17
+ end
18
+
19
+ def test_default_option
20
+ expect = {"output_dir" => "#{Dir.pwd}"}
21
+ engine = Clementine::ClojureScriptEngine.new("", "")
22
+ assert_equal expect, engine.default_opts
23
+ end
24
+
25
+ def test_convert_options
26
+ options = {:optimizations => :advanced, :output_dir => "#{Dir.pwd}"}
27
+ engine = Clementine::ClojureScriptEngine.new("", "")
28
+ opts = engine.convert_options(options)
29
+ opts.each do |k, v|
30
+ assert_equal Java::clojure.lang.Keyword, k.class
31
+ assert k.to_s == ":optimizations" || k.to_s == ":output-dir"
32
+ assert v.to_s == ":advanced" || v.to_s == "#{Dir.pwd}"
33
+ end
34
+ end
35
+
36
+ def test_created_clojure_map
37
+ options = {:optimizations => :advanced, :output_dir => "#{Dir.pwd}"}
38
+ engine = Clementine::ClojureScriptEngine.new("", "")
39
+ opts = engine.convert_options(options)
40
+ map = PersistentHashMap.create(convert_options(opts))
41
+ map.each do |k, v|
42
+ assert_equal Java::clojure.lang.Keyword, k.class
43
+ assert k.to_s == ":optimizations" || k.to_s == ":output-dir"
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,22 @@
1
+ require "test/unit"
2
+
3
+ class OptionsTest < Test::Unit::TestCase
4
+
5
+ # Called before every test method runs. Can be used
6
+ # to set up fixture information.
7
+ def setup
8
+ require "#{File.join(File.dirname(__FILE__), "..", "lib", "clementine", "options")}"
9
+ end
10
+
11
+ # Called after every test method runs. Can be used to tear
12
+ # down fixture information.
13
+
14
+ def teardown
15
+ # Do nothing
16
+ end
17
+
18
+ def test_ruby2clj
19
+ assert_equal "output-dir", Clementine.ruby2clj("output_dir")
20
+ assert_equal "output-to", Clementine.ruby2clj("output_to")
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ ; Copyright (c) Rich Hickey. All rights reserved.
2
+ ; The use and distribution terms for this software are covered by the
3
+ ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4
+ ; which can be found in the file epl-v10.html at the root of this distribution.
5
+ ; By using this software in any fashion, you are agreeing to be bound by
6
+ ; the terms of this license.
7
+ ; You must not remove this notice, or any other, from this software.
8
+
9
+ (require '[cljs.closure :as closure])
10
+
11
+ (defn transform-cl-args
12
+ [args]
13
+ (let [source (first args)
14
+ opts-string (apply str (interpose " " (rest args)))
15
+ options (when (> (count opts-string) 1)
16
+ (try (read-string opts-string)
17
+ (catch Exception e (println e))))]
18
+ {:source source :options (merge {:output-to :print} options)}))
19
+
20
+ (let [args (transform-cl-args *command-line-args*)]
21
+ (closure/build (:source args) (:options args)))
Binary file
@@ -0,0 +1,823 @@
1
+ ; Copyright (c) Rich Hickey. All rights reserved.
2
+ ; The use and distribution terms for this software are covered by the
3
+ ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
4
+ ; which can be found in the file epl-v10.html at the root of this distribution.
5
+ ; By using this software in any fashion, you are agreeing to be bound by
6
+ ; the terms of this license.
7
+ ; You must not remove this notice, or any other, from this software.
8
+
9
+ (ns cljs.closure
10
+ "Compile ClojureScript to JavaScript with optimizations from Google
11
+ Closure Compiler producing runnable JavaScript.
12
+
13
+ The Closure Compiler (compiler.jar) must be on the classpath.
14
+
15
+ Use the 'build' function for end-to-end compilation.
16
+
17
+ build = compile -> add-dependencies -> optimize -> output
18
+
19
+ Two protocols are defined: IJavaScript and Compilable. The
20
+ Compilable protocol is satisfied by something which can return one
21
+ or more IJavaScripts.
22
+
23
+ With IJavaScript objects in hand, calling add-dependencies will
24
+ produce a sequence of IJavaScript objects which includes all
25
+ required dependencies from the Closure library and ClojureScript,
26
+ in dependency order. This function replaces the closurebuilder
27
+ tool.
28
+
29
+ The optimize function converts one or more IJavaScripts into a
30
+ single string of JavaScript source code using the Closure Compiler
31
+ API.
32
+
33
+ The produced output is either a single string of optimized
34
+ JavaScript or a deps file for use during development.
35
+ "
36
+ (:require [cljs.compiler :as comp]
37
+ [clojure.java.io :as io]
38
+ [clojure.string :as string])
39
+ (:import java.io.File
40
+ java.io.BufferedInputStream
41
+ java.net.URL
42
+ java.util.logging.Level
43
+ java.util.jar.JarFile
44
+ com.google.common.collect.ImmutableList
45
+ com.google.javascript.jscomp.CompilerOptions
46
+ com.google.javascript.jscomp.CompilationLevel
47
+ com.google.javascript.jscomp.ClosureCodingConvention
48
+ com.google.javascript.jscomp.JSSourceFile
49
+ com.google.javascript.jscomp.Result
50
+ com.google.javascript.jscomp.JSError
51
+ com.google.javascript.jscomp.CommandLineRunner))
52
+
53
+ (def name-chars (map char (concat (range 48 57) (range 65 90) (range 97 122))))
54
+
55
+ (defn random-char []
56
+ (nth name-chars (.nextInt (java.util.Random.) (count name-chars))))
57
+
58
+ (defn random-string [length]
59
+ (apply str (take length (repeatedly random-char))))
60
+
61
+ ;; Closure API
62
+ ;; ===========
63
+
64
+ (defmulti js-source-file (fn [_ source] (class source)))
65
+
66
+ (defmethod js-source-file String [^String name ^String source]
67
+ (JSSourceFile/fromCode name source))
68
+
69
+ (defmethod js-source-file File [_ ^File source]
70
+ (JSSourceFile/fromFile source))
71
+
72
+ (defmethod js-source-file BufferedInputStream [^String name ^BufferedInputStream source]
73
+ (JSSourceFile/fromInputStream name source))
74
+
75
+ (defn set-options
76
+ "TODO: Add any other options that we would like to support."
77
+ [opts ^CompilerOptions compiler-options]
78
+ (when (contains? opts :pretty-print)
79
+ (set! (.prettyPrint compiler-options) (:pretty-print opts)))
80
+ (when (contains? opts :print-input-delimiter)
81
+ (set! (.printInputDelimiter compiler-options)
82
+ (:print-input-delimiter opts))))
83
+
84
+ (defn make-options
85
+ "Create a CompilerOptions object and set options from opts map."
86
+ [opts]
87
+ (let [level (case (:optimizations opts)
88
+ :advanced CompilationLevel/ADVANCED_OPTIMIZATIONS
89
+ :whitespace CompilationLevel/WHITESPACE_ONLY
90
+ :simple CompilationLevel/SIMPLE_OPTIMIZATIONS)
91
+ compiler-options (doto (CompilerOptions.)
92
+ (.setCodingConvention (ClosureCodingConvention.)))]
93
+ (do (.setOptionsForCompilationLevel level compiler-options)
94
+ (set-options opts compiler-options)
95
+ compiler-options)))
96
+
97
+ (defn load-externs
98
+ "Externs are JavaScript files which contain empty definitions of
99
+ functions which will be provided by the envorinment. Any function in
100
+ an extern file will not be renamed during optimization.
101
+
102
+ Options may contain an :externs key with a list of file paths to
103
+ load. The :use-only-custom-externs flag may be used to indicate that
104
+ the default externs should be excluded."
105
+ [{:keys [externs use-only-custom-externs target]}]
106
+ (letfn [(filter-js [paths]
107
+ (for [p paths f (file-seq (io/file p))
108
+ :when (.endsWith (.toLowerCase (.getName f)) ".js")]
109
+ (.getAbsolutePath f)))
110
+ (add-target [ext]
111
+ (if (= :nodejs target)
112
+ (cons (.getFile (io/resource "cljs/nodejs_externs.js"))
113
+ (or ext []))
114
+ ext))
115
+ (load-js [ext]
116
+ (map #(js-source-file % (io/input-stream %)) ext))]
117
+ (let [js-sources (-> externs filter-js add-target load-js)]
118
+ (if use-only-custom-externs
119
+ js-sources
120
+ (into js-sources (CommandLineRunner/getDefaultExterns))))))
121
+
122
+ (defn ^com.google.javascript.jscomp.Compiler make-closure-compiler []
123
+ (let [compiler (com.google.javascript.jscomp.Compiler.)]
124
+ (do (com.google.javascript.jscomp.Compiler/setLoggingLevel Level/WARNING)
125
+ compiler)))
126
+
127
+ (defn report-failure [^Result result]
128
+ (let [errors (.errors result)
129
+ warnings (.warnings result)]
130
+ (doseq [next (seq errors)]
131
+ (println "ERROR:" (.toString ^JSError next)))
132
+ (doseq [next (seq warnings)]
133
+ (println "WARNING:" (.toString ^JSError next)))))
134
+
135
+ (defn parse-js-ns
136
+ "Given the lines from a JavaScript source file, parse the provide
137
+ and require statements and return them in a map. Assumes that all
138
+ provide and require statements appear before the first function
139
+ definition."
140
+ [lines]
141
+ (letfn [(conj-in [m k v] (update-in m [k] (fn [old] (conj old v))))]
142
+ (->> (for [line lines x (string/split line #";")] x)
143
+ (map string/trim)
144
+ (take-while #(not (re-matches #".*=[\s]*function\(.*\)[\s]*[{].*" %)))
145
+ (map #(re-matches #".*goog\.(provide|require)\('(.*)'\)" %))
146
+ (remove nil?)
147
+ (map #(drop 1 %))
148
+ (reduce (fn [m ns]
149
+ (if (= (first ns) "require")
150
+ (conj-in m :requires (last ns))
151
+ (conj-in m :provides (last ns))))
152
+ {:requires [] :provides []}))))
153
+
154
+ ;; Protocols for IJavaScript and Compilable
155
+ ;; ========================================
156
+
157
+ (defmulti to-url class)
158
+
159
+ (defmethod to-url File [^File f] (.toURL (.toURI f)))
160
+
161
+ (defmethod to-url String [s] (to-url (io/file s)))
162
+
163
+ (defprotocol IJavaScript
164
+ (-foreign? [this] "Whether the Javascript represents a foreign
165
+ library (a js file that not have any goog.provide statement")
166
+ (-url [this] "The URL where this JavaScript is located. Returns nil
167
+ when JavaScript exists in memory only.")
168
+ (-provides [this] "A list of namespaces that this JavaScript provides.")
169
+ (-requires [this] "A list of namespaces that this JavaScript requires.")
170
+ (-source [this] "The JavaScript source string."))
171
+
172
+ (extend-protocol IJavaScript
173
+
174
+ String
175
+ (-foreign? [this] false)
176
+ (-url [this] nil)
177
+ (-provides [this] (:provides (parse-js-ns (string/split-lines this))))
178
+ (-requires [this] (:requires (parse-js-ns (string/split-lines this))))
179
+ (-source [this] this)
180
+
181
+ clojure.lang.IPersistentMap
182
+ (-foreign? [this] (:foreign this))
183
+ (-url [this] (or (:url this)
184
+ (to-url (:file this))))
185
+ (-provides [this] (map name (:provides this)))
186
+ (-requires [this] (map name (:requires this)))
187
+ (-source [this] (if-let [s (:source this)]
188
+ s
189
+ (slurp (io/reader (-url this))))))
190
+
191
+ (defrecord JavaScriptFile [foreign ^URL url provides requires]
192
+ IJavaScript
193
+ (-foreign? [this] foreign)
194
+ (-url [this] url)
195
+ (-provides [this] provides)
196
+ (-requires [this] requires)
197
+ (-source [this] (slurp (io/reader url))))
198
+
199
+ (defn javascript-file [foreign ^URL url provides requires]
200
+ (JavaScriptFile. foreign url (map name provides) (map name requires)))
201
+
202
+ (defn map->javascript-file [m]
203
+ (javascript-file (:foreign m)
204
+ (to-url (:file m))
205
+ (:provides m)
206
+ (:requires m)))
207
+
208
+ (defn read-js
209
+ "Read a JavaScript file returning a map of file information."
210
+ [f]
211
+ (let [source (slurp f)
212
+ m (parse-js-ns (string/split-lines source))]
213
+ (map->javascript-file (assoc m :file f))))
214
+
215
+ (defprotocol Compilable
216
+ (-compile [this opts] "Returns one or more IJavaScripts."))
217
+
218
+ (defn build-index
219
+ "Index a list of dependencies by namespace and file name. There can
220
+ be zero or more namespaces provided per file."
221
+ [deps]
222
+ (reduce (fn [m next]
223
+ (let [provides (:provides next)]
224
+ (-> (if (seq provides)
225
+ (reduce (fn [m* provide]
226
+ (assoc m* provide next))
227
+ m
228
+ provides)
229
+ m)
230
+ (assoc (:file next) next))))
231
+ {}
232
+ deps))
233
+
234
+ (defn dependency-order-visit
235
+ [state ns-name]
236
+ (let [file (get state ns-name)]
237
+ (if (or (:visited file) (nil? file))
238
+ state
239
+ (let [state (assoc-in state [ns-name :visited] true)
240
+ deps (:requires file)
241
+ state (reduce dependency-order-visit state deps)]
242
+ (assoc state :order (conj (:order state) file))))))
243
+
244
+ (defn dependency-order
245
+ "Topologically sort a collection of dependencies."
246
+ [coll]
247
+ (let [state (build-index coll)]
248
+ (distinct (:order (reduce dependency-order-visit (assoc state :order []) (keys state))))))
249
+
250
+ ;; Compile
251
+ ;; =======
252
+
253
+ (defn empty-env []
254
+ {:ns (@comp/namespaces comp/*cljs-ns*) :context :statement :locals {}})
255
+
256
+ (defn compile-form-seq
257
+ "Compile a sequence of forms to a JavaScript source string."
258
+ [forms]
259
+ (comp/with-core-cljs
260
+ (with-out-str
261
+ (binding [comp/*cljs-ns* 'cljs.user]
262
+ (doseq [form forms]
263
+ (comp/emit (comp/analyze (empty-env) form)))))))
264
+
265
+ (defn output-directory [opts]
266
+ (or (:output-dir opts) "out"))
267
+
268
+ (def compiled-cljs (atom {}))
269
+
270
+ (defn compiled-file
271
+ "Given a map with at least a :file key, return a map with
272
+ {:file .. :provides .. :requires ..}.
273
+
274
+ Compiled files are cached so they will only be read once."
275
+ [m]
276
+ (let [path (.getAbsolutePath (:file m))
277
+ js (if (:provides m)
278
+ (map->javascript-file m)
279
+ (if-let [js (get @compiled-cljs path)]
280
+ js
281
+ (read-js (:file m))))]
282
+ (do (swap! compiled-cljs (fn [old] (assoc old path js)))
283
+ js)))
284
+
285
+ (defn compile-file
286
+ "Compile a single cljs file. If no output-file is specified, returns
287
+ a string of compiled JavaScript. With an output-file option, the
288
+ compiled JavaScript will written to this location and the function
289
+ returns a JavaScriptFile. In either case the return value satisfies
290
+ IJavaScript."
291
+ [^File file {:keys [output-file] :as opts}]
292
+ (if output-file
293
+ (let [out-file (io/file (output-directory opts) output-file)]
294
+ (compiled-file (comp/compile-file file out-file)))
295
+ (compile-form-seq (comp/forms-seq file))))
296
+
297
+ (defn compile-dir
298
+ "Recursively compile all cljs files under the given source
299
+ directory. Return a list of JavaScriptFiles in dependency order."
300
+ [^File src-dir opts]
301
+ (let [out-dir (output-directory opts)]
302
+ (dependency-order
303
+ (map compiled-file
304
+ (comp/compile-root src-dir out-dir)))))
305
+
306
+ (defn path-from-jarfile
307
+ "Given the URL of a file within a jar, return the path of the file
308
+ from the root of the jar."
309
+ [^URL url]
310
+ (last (string/split (.getFile url) #"\.jar!/")))
311
+
312
+ (defn jar-file-to-disk
313
+ "Copy a file contained within a jar to disk. Return the created file."
314
+ [url out-dir]
315
+ (let [out-file (io/file out-dir (path-from-jarfile url))
316
+ content (slurp (io/reader url))]
317
+ (do (comp/mkdirs out-file)
318
+ (spit out-file content)
319
+ out-file)))
320
+
321
+ (defn compile-from-jar
322
+ "Compile a file from a jar."
323
+ [this {:keys [output-file] :as opts}]
324
+ (or (when output-file
325
+ (let [out-file (io/file (output-directory opts) output-file)]
326
+ (when (.exists out-file)
327
+ (compiled-file {:file out-file}))))
328
+ (let [file-on-disk (jar-file-to-disk this (output-directory opts))]
329
+ (-compile file-on-disk opts))))
330
+
331
+ (extend-protocol Compilable
332
+
333
+ File
334
+ (-compile [this opts]
335
+ (if (.isDirectory this)
336
+ (compile-dir this opts)
337
+ (compile-file this opts)))
338
+
339
+ URL
340
+ (-compile [this opts]
341
+ (case (.getProtocol this)
342
+ "file" (-compile (io/file this) opts)
343
+ "jar" (compile-from-jar this opts)))
344
+
345
+ clojure.lang.PersistentList
346
+ (-compile [this opts]
347
+ (compile-form-seq [this]))
348
+
349
+ String
350
+ (-compile [this opts] (-compile (io/file this) opts))
351
+
352
+ clojure.lang.PersistentVector
353
+ (-compile [this opts] (compile-form-seq this))
354
+ )
355
+
356
+ (comment
357
+ ;; compile a file in memory
358
+ (-compile "samples/hello/src/hello/core.cljs" {})
359
+ ;; compile a file to disk - see file @ 'out/clojure/set.js'
360
+ (-compile (io/resource "clojure/set.cljs") {:output-file "clojure/set.js"})
361
+ ;; compile a project
362
+ (-compile (io/file "samples/hello/src") {})
363
+ ;; compile a project with a custom output directory
364
+ (-compile (io/file "samples/hello/src") {:output-dir "my-output"})
365
+ ;; compile a form
366
+ (-compile '(defn plus-one [x] (inc x)) {})
367
+ ;; compile a vector of forms
368
+ (-compile '[(ns test.app (:require [goog.array :as array]))
369
+ (defn plus-one [x] (inc x))]
370
+ {})
371
+ )
372
+
373
+ ;; Dependencies
374
+ ;; ============
375
+ ;;
376
+ ;; Find all dependencies from files on the classpath. Eliminates the
377
+ ;; need for closurebuilder. cljs dependencies will be compiled as
378
+ ;; needed.
379
+
380
+ (defn find-url
381
+ "Given a string, returns a URL. Attempts to resolve as a classpath-relative
382
+ path, then as a path relative to the working directory or a URL string"
383
+ [path-or-url]
384
+ (or (io/resource path-or-url)
385
+ (try (io/as-url path-or-url)
386
+ (catch java.net.MalformedURLException e
387
+ false))
388
+ (io/as-url (io/as-file path-or-url))))
389
+
390
+ (defn load-foreign-library*
391
+ "Given a library spec (a map containing the keys :file
392
+ and :provides), returns a map containing :provides, :requires, :file
393
+ and :url"
394
+ [lib-spec]
395
+ (merge lib-spec {:foreign true
396
+ :requires nil
397
+ :url (find-url (:file lib-spec))}))
398
+
399
+ (def load-foreign-library (memoize load-foreign-library*))
400
+
401
+ (defn load-library*
402
+ "Given a path to a JavaScript library, which is a directory
403
+ containing Javascript files, return a list of maps
404
+ containing :provides, :requires, :file and :url."
405
+ [path]
406
+ (letfn [(graph-node [f]
407
+ (-> (io/reader f)
408
+ line-seq
409
+ parse-js-ns
410
+ (assoc :file (.getPath f) :url (to-url f))))]
411
+ (let [js-sources (filter #(.endsWith (.getName %) ".js") (file-seq (io/file path)))]
412
+ (filter #(seq (:provides %)) (map graph-node js-sources)))))
413
+
414
+ (def load-library (memoize load-library*))
415
+
416
+ (defn library-dependencies [{:keys [libs foreign-libs]}]
417
+ (concat
418
+ (mapcat load-library libs)
419
+ (map load-foreign-library foreign-libs)))
420
+
421
+ (comment
422
+ ;; load one library
423
+ (load-library* "closure/library/third_party/closure")
424
+ ;; load all library dependencies
425
+ (library-dependencies {:libs ["closure/library/third_party/closure"]})
426
+ (library-dependencies {:foreign-libs [{:file "http://example.com/remote.js"
427
+ :provides ["my.example"]}]})
428
+ (library-dependencies {:foreign-libs [{:file "local/file.js"
429
+ :provides ["my.example"]}]})
430
+ (library-dependencies {:foreign-libs [{:file "cljs/nodejs_externs.js"
431
+ :provides ["my.example"]}]}))
432
+
433
+ (defn goog-dependencies*
434
+ "Create an index of Google dependencies by namespace and file name."
435
+ []
436
+ (letfn [(parse-list [s] (when (> (count s) 0)
437
+ (-> (.substring s 1 (dec (count s)))
438
+ (string/split #"'\s*,\s*'"))))]
439
+ (->> (line-seq (io/reader (io/resource "goog/deps.js")))
440
+ (map #(re-matches #"^goog\.addDependency\(['\"](.*)['\"],\s*\[(.*)\],\s*\[(.*)\]\);.*" %))
441
+ (remove nil?)
442
+ (map #(drop 1 %))
443
+ (remove #(.startsWith (first %) "../../third_party"))
444
+ (map #(hash-map :file (str "goog/"(first %))
445
+ :provides (parse-list (second %))
446
+ :requires (parse-list (last %))
447
+ :group :goog)))))
448
+
449
+ (def goog-dependencies (memoize goog-dependencies*))
450
+
451
+
452
+ (defn js-dependency-index
453
+ "Returns the index for all JavaScript dependencies. Lookup by
454
+ namespace or file name."
455
+ [opts]
456
+ (build-index (concat (goog-dependencies) (library-dependencies opts))))
457
+
458
+ (defn js-dependencies
459
+ "Given a sequence of Closure namespace strings, return the list of
460
+ all dependencies in dependency order. The returned list includes all
461
+ Google and third-party library dependencies.
462
+
463
+ Third-party libraries are configured using the :libs option where
464
+ the value is a list of directories containing third-party
465
+ libraries."
466
+ [opts requires]
467
+ (let [index (js-dependency-index opts)]
468
+ (loop [requires requires
469
+ visited requires
470
+ deps #{}]
471
+ (if (seq requires)
472
+ (let [node (get index (first requires))
473
+ new-req (remove #(contains? visited %) (:requires node))]
474
+ (recur (into (rest requires) new-req)
475
+ (into visited new-req)
476
+ (conj deps node)))
477
+ (cons (get index "goog/base.js") (dependency-order deps))))))
478
+
479
+ (comment
480
+ ;; find dependencies
481
+ (js-dependencies {} ["goog.array"])
482
+ ;; find dependencies in an external library
483
+ (js-dependencies {:libs ["closure/library/third_party/closure"]} ["goog.dom.query"])
484
+ )
485
+
486
+ (defn get-compiled-cljs
487
+ "Return an IJavaScript for this file. Compiled output will be
488
+ written to the working directory."
489
+ [opts {:keys [relative-path uri]}]
490
+ (let [js-file (comp/rename-to-js relative-path)]
491
+ (-compile uri (merge opts {:output-file js-file}))))
492
+
493
+ (defn cljs-dependencies
494
+ "Given a list of all required namespaces, return a list of
495
+ IJavaScripts which are the cljs dependencies in dependency
496
+ order. The returned list will not only include the explicitly
497
+ required files but any transitive depedencies as well. JavaScript
498
+ files will be compiled to the working directory if they do not
499
+ already exist.
500
+
501
+ Only load dependencies from the classpath."
502
+ [opts requires]
503
+ (let [index (js-dependency-index opts)]
504
+ (letfn [(ns->cp [s] (str (string/replace (munge s) \. \/) ".cljs"))
505
+ (cljs-deps [coll]
506
+ (->> coll
507
+ (remove #(contains? index %))
508
+ (map #(let [f (ns->cp %)] (hash-map :relative-path f :uri (io/resource f))))
509
+ (remove #(nil? (:uri %)))))]
510
+ (loop [required-files (cljs-deps requires)
511
+ visited (set required-files)
512
+ js-deps #{}]
513
+ (if (seq required-files)
514
+ (let [next-file (first required-files)
515
+ js (get-compiled-cljs opts next-file)
516
+ new-req (remove #(contains? visited %) (cljs-deps (-requires js)))]
517
+ (recur (into (rest required-files) new-req)
518
+ (into visited new-req)
519
+ (conj js-deps js)))
520
+ (dependency-order js-deps))))))
521
+
522
+ (comment
523
+ ;; only get cljs deps
524
+ (cljs-dependencies {} ["goog.string" "cljs.core"])
525
+ ;; get transitive deps
526
+ (cljs-dependencies {} ["clojure.string"])
527
+ ;; don't get cljs.core twice
528
+ (cljs-dependencies {} ["cljs.core" "clojure.string"])
529
+ )
530
+
531
+ (defn add-dependencies
532
+ "Given one or more IJavaScript objects in dependency order, produce
533
+ a new sequence of IJavaScript objects which includes the input list
534
+ plus all dependencies in dependency order."
535
+ [opts & inputs]
536
+ (let [requires (mapcat -requires inputs)
537
+ required-cljs (cljs-dependencies opts requires)
538
+ required-js (js-dependencies opts (set (concat (mapcat -requires required-cljs) requires)))]
539
+ (concat (map #(-> (javascript-file (:foreign %)
540
+ (or (:url %) (io/resource (:file %)))
541
+ (:provides %)
542
+ (:requires %))
543
+ (assoc :group (:group %))) required-js)
544
+ required-cljs
545
+ inputs)))
546
+
547
+ (comment
548
+ ;; add dependencies to literal js
549
+ (add-dependencies {} "goog.provide('test.app');\ngoog.require('cljs.core');")
550
+ (add-dependencies {} "goog.provide('test.app');\ngoog.require('goog.array');")
551
+ (add-dependencies {} (str "goog.provide('test.app');\n"
552
+ "goog.require('goog.array');\n"
553
+ "goog.require('clojure.set');"))
554
+ ;; add dependencies with external lib
555
+ (add-dependencies {:libs ["closure/library/third_party/closure"]}
556
+ (str "goog.provide('test.app');\n"
557
+ "goog.require('goog.array');\n"
558
+ "goog.require('goog.dom.query');"))
559
+ ;; add dependencies with foreign lib
560
+ (add-dependencies {:foreign-libs [{:file "samples/hello/src/hello/core.cljs"
561
+ :provides ["example.lib"]}]}
562
+ (str "goog.provide('test.app');\n"
563
+ "goog.require('example.lib');\n"))
564
+ ;; add dependencies to a JavaScriptFile record
565
+ (add-dependencies {} (javascript-file false
566
+ (to-url "samples/hello/src/hello/core.cljs")
567
+ ["hello.core"]
568
+ ["goog.array"]))
569
+ )
570
+
571
+ ;; Optimize
572
+ ;; ========
573
+
574
+ (defmulti javascript-name class)
575
+
576
+ (defmethod javascript-name URL [^URL url]
577
+ (if url (.getPath url) "cljs/user.js"))
578
+
579
+ (defmethod javascript-name String [s]
580
+ (if-let [name (first (-provides s))] name "cljs/user.js"))
581
+
582
+ (defmethod javascript-name JavaScriptFile [js] (javascript-name (-url js)))
583
+
584
+ (defn build-provides
585
+ "Given a vector of provides, builds required goog.provide statements"
586
+ [provides]
587
+ (apply str (map #(str "goog.provide('" % "');\n") provides)))
588
+
589
+
590
+ (defmethod js-source-file JavaScriptFile [_ js]
591
+ (when-let [url (-url js)]
592
+ (js-source-file (javascript-name url)
593
+ (if (-foreign? js)
594
+ (str (build-provides (-provides js)) (slurp url))
595
+ (io/input-stream url)))))
596
+
597
+ (defn optimize
598
+ "Use the Closure Compiler to optimize one or more JavaScript files."
599
+ [opts & sources]
600
+ (let [closure-compiler (make-closure-compiler)
601
+ externs (load-externs opts)
602
+ compiler-options (make-options opts)
603
+ inputs (map #(js-source-file (javascript-name %) %) sources)
604
+ result ^Result (.compile closure-compiler externs inputs compiler-options)]
605
+ (if (.success result)
606
+ (.toSource closure-compiler)
607
+ (report-failure result))))
608
+
609
+ (comment
610
+ ;; optimize JavaScript strings
611
+ (optimize {:optimizations :whitespace} "var x = 3 + 2; alert(x);")
612
+ ;; => "var x=3+2;alert(x);"
613
+ (optimize {:optimizations :simple} "var x = 3 + 2; alert(x);")
614
+ ;; => "var x=5;alert(x);"
615
+ (optimize {:optimizations :advanced} "var x = 3 + 2; alert(x);")
616
+ ;; => "alert(5);"
617
+
618
+ ;; optimize a ClojureScript form
619
+ (optimize {:optimizations :simple} (-compile '(def x 3) {}))
620
+
621
+ ;; optimize a project
622
+ (println (->> (-compile "samples/hello/src" {})
623
+ (apply add-dependencies {})
624
+ (apply optimize {:optimizations :simple :pretty-print true})))
625
+ )
626
+
627
+ ;; Output
628
+ ;; ======
629
+ ;;
630
+ ;; The result of a build is always a single string of JavaScript. The
631
+ ;; build process may produce files on disk but a single string is
632
+ ;; always output. What this string contains depends on whether the
633
+ ;; input has been optimized or not. If the :output-to option is set
634
+ ;; then this string will be written to the specified file. If not, it
635
+ ;; will be returned.
636
+ ;;
637
+ ;; The :output-dir option can be used to set the working directory
638
+ ;; where any files will be written to disk. By default this directory
639
+ ;; is 'out'.
640
+ ;;
641
+ ;; If inputs are optimized then the output string will be the complete
642
+ ;; application with all dependencies included.
643
+ ;;
644
+ ;; For unoptimized output, the string will be a Closure deps file
645
+ ;; describing where the JavaScript files are on disk and their
646
+ ;; dependencies. All JavaScript files will be located in the working
647
+ ;; directory, including any dependencies from the Closure library.
648
+ ;;
649
+ ;; Unoptimized mode is faster because the Closure Compiler is not
650
+ ;; run. It also makes debugging much simpler because each file is
651
+ ;; loaded in its own script tag.
652
+ ;;
653
+ ;; When working with uncompiled files, you will need to add additional
654
+ ;; script tags to the hosting HTML file: one which pulls in Closure
655
+ ;; library's base.js and one which calls goog.require to load your
656
+ ;; code. See samples/hello/hello-dev.html for an example.
657
+
658
+ (defn path-relative-to
659
+ "Generate a string which is the path to input relative to base."
660
+ [^File base input]
661
+ (let [base-path (comp/path-seq (.getCanonicalPath base))
662
+ input-path (comp/path-seq (.getCanonicalPath (io/file ^URL (-url input))))
663
+ count-base (count base-path)
664
+ common (count (take-while true? (map #(= %1 %2) base-path input-path)))
665
+ prefix (repeat (- count-base common 1) "..")]
666
+ (if (= count-base common)
667
+ (last input-path) ;; same file
668
+ (comp/to-path (concat prefix (drop common input-path)) "/"))))
669
+
670
+ (defn add-dep-string
671
+ "Return a goog.addDependency string for an input."
672
+ [opts input]
673
+ (letfn [(ns-list [coll] (when (seq coll) (apply str (interpose ", " (map #(str "'" (munge %) "'") coll)))))]
674
+ (str "goog.addDependency(\""
675
+ (path-relative-to (io/file (output-directory opts) "goog/base.js") input)
676
+ "\", ["
677
+ (ns-list (-provides input))
678
+ "], ["
679
+ (ns-list (-requires input))
680
+ "]);")))
681
+
682
+ (defn deps-file
683
+ "Return a deps file string for a sequence of inputs."
684
+ [opts sources]
685
+ (apply str (interpose "\n" (map #(add-dep-string opts %) sources))))
686
+
687
+ (comment
688
+ (path-relative-to (io/file "out/goog/base.js") {:url (to-url "out/cljs/core.js")})
689
+ (add-dep-string {} {:url (to-url "out/cljs/core.js") :requires ["goog.string"] :provides ["cljs.core"]})
690
+ (deps-file {} [{:url (to-url "out/cljs/core.js") :requires ["goog.string"] :provides ["cljs.core"]}])
691
+ )
692
+
693
+ (defn output-one-file [{:keys [output-to]} js]
694
+ (cond (nil? output-to) js
695
+ (string? output-to) (spit output-to js)
696
+ :else (println js)))
697
+
698
+ (defn output-deps-file [opts sources]
699
+ (output-one-file opts (deps-file opts sources)))
700
+
701
+ (defn ^String output-path
702
+ "Given an IJavaScript which is either in memory or in a jar file,
703
+ return the output path for this file relative to the working
704
+ directory."
705
+ [js]
706
+ (if-let [url ^URL (-url js)]
707
+ (path-from-jarfile url)
708
+ (str (random-string 5) ".js")))
709
+
710
+
711
+ (defn write-javascript
712
+ "Write a JavaScript file to disk. Only write if the file does not
713
+ already exist. Return IJavaScript for the file on disk."
714
+ [opts js]
715
+ (let [out-dir (io/file (output-directory opts))
716
+ out-name (output-path js)
717
+ out-file (io/file out-dir out-name)]
718
+ (do (when-not (.exists out-file)
719
+ (do (comp/mkdirs out-file)
720
+ (spit out-file (-source js))))
721
+ {:url (to-url out-file) :requires (-requires js) :provides (-provides js) :group (:group js)})))
722
+
723
+ (defn source-on-disk
724
+ "Ensure that the given JavaScript exists on disk. Write in memory
725
+ sources and files contained in jars to the working directory. Return
726
+ updated IJavaScript with the new location."
727
+ [opts js]
728
+ (let [url ^URL (-url js)]
729
+ (if (or (not url) (= (.getProtocol url) "jar"))
730
+ (write-javascript opts js)
731
+ js)))
732
+
733
+ (comment
734
+ (write-javascript {} "goog.provide('demo');\nalert('hello');\n")
735
+ ;; write something from a jar file to disk
736
+ (source-on-disk {}
737
+ {:url (io/resource "goog/base.js")
738
+ :source (slurp (io/reader (io/resource "goog/base.js")))})
739
+ ;; doesn't write a file that is already on disk
740
+ (source-on-disk {} {:url (io/resource "cljs/core.cljs")})
741
+ )
742
+
743
+ (defn output-unoptimized
744
+ "Ensure that all JavaScript source files are on disk (not in jars),
745
+ write the goog deps file including only the libraries that are being
746
+ used and write the deps file for the current project.
747
+
748
+ The deps file for the current project will include third-party
749
+ libraries."
750
+ [opts & sources]
751
+ (let [disk-sources (map #(source-on-disk opts %) sources)]
752
+ (let [goog-deps (io/file (output-directory opts) "goog/deps.js")]
753
+ (do (comp/mkdirs goog-deps)
754
+ (spit goog-deps (deps-file opts (filter #(= (:group %) :goog) disk-sources)))
755
+ (output-deps-file opts (remove #(= (:group %) :goog) disk-sources))))))
756
+
757
+ (comment
758
+
759
+ ;; output unoptimized alone
760
+ (output-unoptimized {} "goog.provide('test');\ngoog.require('cljs.core');\nalert('hello');\n")
761
+ ;; output unoptimized with all dependencies
762
+ (apply output-unoptimized {}
763
+ (add-dependencies {}
764
+ "goog.provide('test');\ngoog.require('cljs.core');\nalert('hello');\n"))
765
+ ;; output unoptimized with external library
766
+ (apply output-unoptimized {}
767
+ (add-dependencies {:libs ["closure/library/third_party/closure"]}
768
+ "goog.provide('test');\ngoog.require('cljs.core');\ngoog.require('goog.dom.query');\n"))
769
+ ;; output unoptimized and write deps file to 'out/test.js'
770
+ (output-unoptimized {:output-to "out/test.js"}
771
+ "goog.provide('test');\ngoog.require('cljs.core');\nalert('hello');\n")
772
+ )
773
+
774
+ (defn add-header [{:keys [hashbang target]} js]
775
+ (if (= :nodejs target)
776
+ (str "#!" (or hashbang "/usr/bin/nodejs") "\n" js)
777
+ js))
778
+
779
+ (defn build
780
+ "Given a source which can be compiled, produce runnable JavaScript."
781
+ [source opts]
782
+ (let [opts (if (= :nodejs (:target opts))
783
+ (merge {:optimizations :simple} opts)
784
+ opts)
785
+ compiled (-compile source opts)
786
+ compiled (concat
787
+ (if (coll? compiled) compiled [compiled])
788
+ (when (= :nodejs (:target opts))
789
+ [(-compile (io/resource "cljs/nodejscli.cljs") opts)]))
790
+ js-sources (if (coll? compiled)
791
+ (apply add-dependencies opts compiled)
792
+ (add-dependencies opts compiled))]
793
+ (if (:optimizations opts)
794
+ (->> js-sources
795
+ (apply optimize opts)
796
+ (add-header opts)
797
+ (output-one-file opts))
798
+ (apply output-unoptimized opts js-sources))))
799
+
800
+ (comment
801
+
802
+ (println (build '[(ns hello.core)
803
+ (defn ^{:export greet} greet [n] (str "Hola " n))
804
+ (defn ^:export sum [xs] 42)]
805
+ {:optimizations :simple :pretty-print true}))
806
+
807
+ ;; build a project with optimizations
808
+ (build "samples/hello/src" {:optimizations :advanced})
809
+ (build "samples/hello/src" {:optimizations :advanced :output-to "samples/hello/hello.js"})
810
+ ;; open 'samples/hello/hello.html' to see the result in action
811
+
812
+ ;; build a project without optimizations
813
+ (build "samples/hello/src" {:output-dir "samples/hello/out" :output-to "samples/hello/hello.js"})
814
+ ;; open 'samples/hello/hello-dev.html' to see the result in action
815
+ ;; notice how each script was loaded individually
816
+
817
+ ;; build unoptimized from raw ClojureScript
818
+ (build '[(ns hello.core)
819
+ (defn ^{:export greet} greet [n] (str "Hola " n))
820
+ (defn ^:export sum [xs] 42)]
821
+ {:output-dir "samples/hello/out" :output-to "samples/hello/hello.js"})
822
+ ;; open 'samples/hello/hello-dev.html' to see the result in action
823
+ )