# Copyright 2011 The Closure Script Authors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. class Closure # Scripts render with an instance named goog in the context. class Goog def initialize(env, sources, render_stack) @sources = sources @env = env @render_stack = render_stack @dependencies = [] @path = '' end # Specify an explicit host or path for loading scripts. # e.g. '//example.com' or '/proxy/path' # You may also include a protocol and path if necessary. e.g. # 'http://example.com:8080/proxy/path' # attr_reader :path def path= p @path = p if p.start_with?('/') @path = p if p.start_with?('http://') @path = p if p.start_with?('https://') raise 'goog.path not valid' unless @path == p end # You can add additional files to have their mtimes scanned. # Perhaps you want to use a .yml file to define build options. # Closure::Script calls this for every render so you don't need # to define compiler arguments in the same script that calls compile. # @param [String] root of file paths, optional def add_dependency(dependency, root = nil) root ||= File.dirname(@render_stack.last) dependency = File.expand_path dependency, root @dependencies << dependency unless @dependencies.include? dependency end # If your Script changes any javascript sources then call this. # This is a lazy refresh, you may call it repeatedly. def refresh @sources.invalidate @env end # Convert soy templates to javascript. Accepts all arguments that # SoyToJsSrcCompiler.jar support plus it expands filename globs. # All source filenames are relative to the script calling #soy_to_js. # @param [Array] args # @param [String] root of file paths, optional def soy_to_js(args, root = nil) root ||= File.dirname(@render_stack.last) Templates::compile(args, root) refresh end # Compile javascript. Accepts every argument that compiler.jar supports. # This method supports all compiler augmentations added by Closure Script. # Path options are expanded relative to the script calling #compile. # - `--ns namespace` expands in place to `--js filename` arguments which satisfy the namespace. # - `--module name:*:dep` File count will be filled in automatically. The * is replaced with the # count of files up to next --module or the end. # - `--js_output_file file` is compared against sources modification times to determine # if compilation is to be performed. # - `--compilation_level` when not supplied, the scripts are loaded raw. # @example myapp.js.erb # <% @response = goog.compile(%w{ # --js_output_file ../public/myapp.js # --ns myapp.HelloWorld # --compilation_level ADVANCED_OPTIMIZATIONS # }).to_response %> # @param [Array] args # @param [String] root of file paths, optional # @return [Compilation] def compile(args, root = nil) args = Array.new args # work on a copy root ||= File.dirname(@render_stack.last) Compiler::Util.expand_paths args, root mods = Compiler::Util.augment args, @sources, @env if Compiler::Util.arg_values(args, '--compilation_level').empty? # Raw mode comp = Compiler::Compilation.new @env if mods comp << Compiler::Util.module_path(@path) comp << Compiler::Util.module_info(mods) comp << Compiler::Util.module_uris_raw(mods, @sources) end js_counter = 0 args_index = 0 while args_index < args.length option, value = args[args_index, 2] if option == '--js' value = File.expand_path(value, root) script_tag = "" comp << "document.write(#{script_tag.dump});\n" js_counter += 1 # For modules, just the files for the first module break if mods and js_counter >= mods[0][:files].length end args_index = args_index + 2 end else # Compiled mode module_output_path_prefix = Compiler::Util.arg_values(args, '--module_output_path_prefix').last if mods and !module_output_path_prefix # raise this before compilation so we don't write to a weird place raise "--module_output_path_prefix is required when using --module" end comp = Compiler.compile args, @dependencies, @env if mods refresh # compilation may add new files, module_uris_compiled uses src_for prefix = File.expand_path module_output_path_prefix, root if comp.js_output_file File.open comp.js_output_file, 'w' do |f| f.write Compiler::Util.module_path @path f.write Compiler::Util.module_info mods f.write Compiler::Util.module_uris_compiled mods, @sources, prefix end else comp << Compiler::Util.module_path(@path) comp << Compiler::Util.module_info(mods) comp << Compiler::Util.module_uris_compiled(mods, @sources, prefix) end # Load the first module first_module_file = module_output_path_prefix + mods[0][:name] + '.js' first_module_file = File.expand_path first_module_file, root comp << '(function(){var e=document.createElement("script");e.type="text/javascript";e.src=' comp << (@path + src_for(first_module_file)).dump comp << ";document.head.appendChild(e);})();\n" end end comp end # Calculate the deps src for a filename. # @param (String) filename # @return (String) http path info with forward caching query. def src_for(filename) filename = File.expand_path filename @sources.src_for(filename, @env) end # Calculate files needed to satisfy a namespace. # This will be especially useful for module generation. # If you pass the filenames returned from last run, # additional files (if any) will be appended to satisfy # the new namespace. # @example cal_file_list.erb # <%= goog.files_for %w{myapp.Calendar} %> # @return (Array) def files_for(namespace, filenames=nil) @sources.files_for(namespace, filenames, @env) end # The Google Closure base.js script. # If you use this instead of a static link, you are free to relocate relative # to the Google Closure library without updating every html fixture page. # @example view_test.erb # # @return [String] def base_js @sources.base_js(@env) end # This is where base.js looks to find deps.js by default. You will always # be served a Closure Script generated deps.js from this location. # Very old Library versions may get confused by the forward caching query # string; either update your base.js, install a deps_response Script where # it's looking, or manually set CLOSURE_BASE_PATH. # @return [String] def deps_js @sources.deps_js(@env) end # You can serve a deps.js from anywhere you want to drop a script. # @example something.js.erb # <% @response = goog.deps_response %> # @return (Rack::Response) def deps_response @sources.deps_response(File.dirname(Rack::Utils.unescape(@env["PATH_INFO"])), @env) end # Advanced Scripts may need to know where all the sources are. # This has potential for a source browser, editor, and more. # @example # goog.each {|directory, path| ... } def each @sources.each do |directory, path| yield directory, path end end include Enumerable end end