clementine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+ )